PageSourceSearch

https://www.matteogracis.it/wp-content/plugins/pixelyoursite/dist/scripts/public.js

js matteogracis.it collected 2026-09-24 08:27:13 UTC 233,739 bytes, 5,510 lines download raw bytes

1/* global pysOptions */
2
3
4! function ($, options) {
5    if (options.debug) {
6        console.log('PYS:', options);
7    }
8    var uniqueId = {};
9
10    let gtm_variables = {};
11    let gtm_datalayername = "dynamicVariable";
12
13    // Resolved by cookieDomain() on first use, not here.
14    var probedCookieDomain = null;
15
16    /**
17     * Domain attribute for cookies we write. Probed once, on first use (not
18     * at load) so the probe's own write lands behind whatever consent gate
19     * the caller is behind.
20     *
21     * Not suppressed by options.cookie.disabled_all_cookie: deleting a
22     * cookie needs the same domain it was written on, so suppressing here
23     * would leave parent-scoped cookies in place while appearing removed.
24     *
25     * Memoised only after the probe runs, so a disabled_all_cookie or option
26     * arriving later over REST is still honored.
27     */
28    function cookieDomain() {
29        if (probedCookieDomain !== null) {
30            return probedCookieDomain;
31        }
32        if (!options || !options.hasOwnProperty("track_cookie_for_subdomains")
33            || !options.track_cookie_for_subdomains) {
34            return '';
35        }
36        probedCookieDomain = probeCookieDomain();
37        return probedCookieDomain;
38    }
39
40    /**
41     * Find the broadest domain this browser will actually accept a cookie
42     * on, walking from the broadest candidate inward.
43     *
44     * We used to rely on tldjs.getDomain(), but its bundled Public Suffix
45     * List predates ~2018 and returns the public suffix itself for newer
46     * domains (vercel.app, pages.dev, wpcomstaging.com), so cookies scoped
47     * there were silently dropped. Asking the browser instead keeps us in
48     * sync with its own current PSL.
49     *
50     * The probe cookie is written and removed synchronously, carries no
51     * data, and never outlives this call. If a consent tool blocks the
52     * write, cookieDomainAccepted() reports it rejected and we fall through
53     * to host-only — the correct outcome.
54     *
55     * Returns '' when nothing works (IP/single-label host, or all writes
56     * blocked); host-only is never rejected, so it's the safe floor.
57     */
58    function probeCookieDomain() {
59        var host = '';
60        try { host = String(location.hostname || ''); } catch (e) { return ''; }
61
62        // No Domain attribute is valid for an IP literal or a dotless host.
63        if (!host || host.indexOf('.') === -1) return '';
64        if (host.indexOf(':') !== -1) return '';          // IPv6 literal
65        if (/^[\d.]+$/.test(host)) return '';             // IPv4 literal
66
67        var labels = host.split('.');
68        // Broadest first: last two labels, then three, ... up to the full host.
69        for (var take = 2; take <= labels.length; take++) {
70            var candidate = '.' + labels.slice(labels.length - take).join('.');
71            if (cookieDomainAccepted(candidate)) return candidate;
72        }
73        return '';
74    }
75
76    /**
77     * Write a throwaway cookie at $candidate and report whether it stuck.
78     * A false negative is possible if the jar is at its per-domain limit;
79     * we then fall through to a narrower candidate and ultimately host-only.
80     */
81    function cookieDomainAccepted(candidate) {
82        var name = 'pys_dt' + Math.floor(Math.random() * 1e9);
83        var accepted = false;
84        try {
85            document.cookie = name + '=1; path=/; domain=' + candidate + '; SameSite=Lax';
86            accepted = document.cookie.indexOf(name + '=1') !== -1;
87        } catch (e) {
88            return false;
89        } finally {
90            // Remove either way; deleting an unset cookie is a no-op.
91            try {
92                document.cookie = name + '=; path=/; domain=' + candidate + '; max-age=0';
93            } catch (e) {}
94        }
95        return accepted;
96    }
97
98    /**
99     * Hand PHP the cookie domain that probeCookieDomain() verified, since
100     * PHP has no Public Suffix List and guesses wrong on hosts like
101     * shop.example.co.uk or mysite.github.io — scoping its cookies to a
102     * public suffix the browser then drops.
103     *
104     * Value is the site's registrable domain (no PII, same for every
105     * visitor); PHP validates it against the request host before use (see
106     * pys_cookie_domain_resolve).
107     *
108     * Written from manageCookies() under the same consent gates as every
109     * other cookie, not unconditionally.
110     */
111    function reportCookieDomain(resolved) {
112        if (!resolved) return;
113        var bare = String(resolved).replace(/^\./, '');
114        if (!bare || bare.indexOf('.') === -1) return;
115        try {
116            if (Cookies.get('pys_cd') === bare) return; // already reported
117            Cookies.set('pys_cd', bare, {
118                expires: 365,
119                path: '/',
120                domain: resolved,
121                sameSite: 'Lax'
122            });
123        } catch (e) {}
124    }
125
126    // Per-page referrer for Facebook CAPI events. Captured once per script
127    // load so SPA-style navigations within the same HTML document do not
128    // overwrite it. Internal/external is NOT filtered — Meta expects this
129    // field to describe the URL of the previous page for THIS event.
130    var pageReferrer = '';
131    try {
132        pageReferrer = stripUrlQuery((document.referrer || '').toString());
133    } catch (e) {}
134
135    // Session-entry referrer (fallback). Captured at session start the first
136    // time the visitor lands from an EXTERNAL site, mirrored to a session
137    // cookie so PHP can read it for orphan events (async/cron) that arrive
138    // without a per-page payload. Never the primary source.
139    var PYS_SESSION_ENTRY_REFERRER_KEY = 'pys_session_entry_referrer';
140    function stripUrlQuery(url) {
141        if (!url) return '';
142        var clean = String(url);
143        var q = clean.indexOf('?');
144        if (q !== -1) clean = clean.substring(0, q);
145        var h = clean.indexOf('#');
146        if (h !== -1) clean = clean.substring(0, h);
147        return clean;
148    }
149    var sessionEntryReferrer = '';
150    function getOrInitSessionEntryReferrer(consentGranted) {
151        var value = '';
152        try {
153            if (typeof window.sessionStorage !== 'undefined') {
154                value = window.sessionStorage.getItem(PYS_SESSION_ENTRY_REFERRER_KEY) || '';
155            }
156        } catch (e) {}
157
158        if (!value) {
159            var cookieValue = Cookies.get(PYS_SESSION_ENTRY_REFERRER_KEY);
160            if (cookieValue && cookieValue !== 'undefined') {
161                value = cookieValue;
162                try {
163                    if (typeof window.sessionStorage !== 'undefined') {
164                        window.sessionStorage.setItem(PYS_SESSION_ENTRY_REFERRER_KEY, value);
165                    }
166                } catch (e) {}
167            }
168        }
169
170        if (!value) {
171            var raw = '';
172            try {
173                raw = stripUrlQuery((document.referrer || '').toString());
174            } catch (e) {}
175            if (raw) {
176                var refHost = '';
177                try {
178                    refHost = new URL(raw).hostname;
179                } catch (e) {}
180                if (refHost && refHost !== window.location.hostname) {
181                    value = raw;
182                    try {
183                        if (typeof window.sessionStorage !== 'undefined') {
184                            window.sessionStorage.setItem(PYS_SESSION_ENTRY_REFERRER_KEY, value);
185                        }
186                    } catch (e) {}
187                }
188            }
189        }
190
191        // Mirror to a session cookie only once consent is given. The cookie
192        // is session-scoped (no expires) and dies with the browser, just
193        // like the sessionStorage entry.
194        if (value && consentGranted && options && options.cookie
195            && !options.cookie.disabled_all_cookie) {
196            var existingCookie = Cookies.get(PYS_SESSION_ENTRY_REFERRER_KEY);
197            if (!existingCookie || existingCookie === 'undefined') {
198                Cookies.set(PYS_SESSION_ENTRY_REFERRER_KEY, value, { path: '/', domain: cookieDomain() });
199            }
200        }
201
202        sessionEntryReferrer = value;
203        return value;
204    }
205
206    // Populate sessionStorage / sessionEntryReferrer immediately. Cookie
207    // mirroring is deferred to the consent-gated path in Facebook.loadPixel().
208    getOrInitSessionEntryReferrer(false);
209
210    // Per-page event referrer (session cookie). Snapshot of document.referrer
211    // for the CURRENT page, refreshed on every page load. Read server-side
212    // by saveFbTagsInOrder() to capture the buyer's checkout-page referrer
213    // for orphan CAPI events (APT, gateway webhooks, manual status changes).
214    // Internal URLs are intentionally NOT filtered — the goal is the page
215    // that led the buyer to checkout, which is usually internal.
216    // If document.referrer is empty (direct navigation), the previous value
217    // is preserved so a session that began with an external referrer keeps
218    // it across same-tab navigations.
219    var PYS_EVENT_REFERRER_KEY = 'pys_event_referrer';
220    function writeEventReferrerCookie() {
221        if (!pageReferrer) {
222            return;
223        }
224        if (!options || !options.cookie || options.cookie.disabled_all_cookie) {
225            return;
226        }
227        try {
228            Cookies.set(PYS_EVENT_REFERRER_KEY, pageReferrer, { path: '/', domain: cookieDomain() });
229        } catch (e) {}
230    }
231    /**
232     * Resolve parameter value based on mode (static or dynamic)
233     *
234     * @param {Object|string} param - Parameter object with { value, selector } or string value
235     * @param {string} key - Parameter key name
236     * @returns {string|null} - Resolved value
237     */
238    function resolveParamValue(param, key = '') {
239        // 1️⃣ null / undefined - skip this parameter
240        if (param === null || param === undefined) {
241            return null;
242        }
243
244        // 2️⃣ Handle primitive types (string, number, boolean)
245        if (typeof param !== 'object') {
246            return param;
247        }
248
249        // 3️⃣ Handle arrays - process each element recursively
250        if (Array.isArray(param)) {
251            return param.map(item => resolveParamValue(item, key));
252        }
253
254        // 4️⃣ Check if this is a configuration object (has value/selector/dynamic/input_type)
255        const isConfigObject =
256            ('value' in param || 'selector' in param || 'dynamic' in param || 'input_type' in param) &&
257            Object.keys(param).every(k =>
258                ['value', 'selector', 'dynamic', 'input_type', 'name'].includes(k)
259            );
260
261        if (isConfigObject) {
262            // STATIC MODE: selector is empty or dynamic is false
263            const isStatic =
264                (!param.dynamic || param.dynamic === false) ||
265                (!param.selector || param.selector.trim() === '');
266
267            if (isStatic) {
268                let value = param.value ?? null;
269
270                // Apply numeric conversion if needed
271                if (param.input_type === "float" || param.input_type === "int") {
272                    value = extractNumericValue(value, param.input_type === "int");
273                }
274
275                return value;
276            }
277
278            // DYNAMIC MODE: selector is present
279            try {
280                const el = document.querySelector(param.selector);
281                if (!el) {
282                    return null;
283                }
284
285                let value =
286                    el.value ||
287                    el.innerText ||
288                    el.textContent ||
289                    el.getAttribute("content") ||
290                    el.getAttribute("data-value") ||
291                    null;
292
293                // Apply numeric conversion if needed
294                if (param.input_type === "float" || param.input_type === "int") {
295                    value = extractNumericValue(value, param.input_type === "int");
296                }
297
298                return value;
299            } catch (e) {
300                return null;
301            }
302        }
303
304        // 5️⃣ Plain object (like ecommerce data) - recursively process nested values
305        return Object.fromEntries(
306            Object.entries(param)
307                .map(([k, v]) => [k, resolveParamValue(v, k)])
308                .filter(([k, v]) => v !== null && v !== undefined)
309        );
310    }
311
312    function extractNumericValue(value, isInt = false) {
313        if (value === null || value === undefined) {
314            return null;
315        }
316        if (isInt) {
317            return parseInt(value);
318        }
319        return parseFloat(value);
320    }
321    var dummyPinterest = function () {
322
323        /**
324         * Public API
325         */
326        return {
327            // Must mirror the real module's surface. Utils.fireEventForAllPixel()
328            // calls tag() on every pixel unconditionally, so a stand-in without it
329            // throws "Pinterest.tag is not a function" whenever the add-on is not
330            // installed and this dummy stays in place.
331            tag: function () {
332                return "pinterest";
333            },
334            isEnabled: function () {},
335            disable: function () {},
336            loadPixel: function () {},
337            fireEvent: function (name, data) {
338                return false;
339            },
340            onAdSenseEvent: function (event) {},
341            onClickEvent: function (params) {},
342            onWatchVideo: function (params) {},
343            onCommentEvent: function () {},
344            onDownloadEvent: function (params) {},
345            onFormEvent: function (params) {},
346            onWooAddToCartOnButtonEvent: function (product_id) {},
347            onWooAddToCartOnSingleEvent: function (product_id, qty, is_variable, is_external, $form) {},
348            onWooRemoveFromCartEvent: function (cart_item_hash) {},
349            onWooAffiliateEvent: function (product_id) {},
350            onWooPayPalEvent: function (event) {},
351            onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {},
352            onEddRemoveFromCartEvent: function (item) {},
353            onPageScroll: function (event) {},
354            onTime: function (event) {},
355
356        }
357
358    }();
359
360    var dummyBing = function () {
361
362        /**
363         * Public API
364         */
365        return {
366            // See dummyPinterest: tag() is required by Utils.fireEventForAllPixel().
367            tag: function () {
368                return "bing";
369            },
370            isEnabled: function () {},
371            disable: function () {},
372            loadPixel: function () {},
373            fireEvent: function (name, data) {
374                return false;
375            },
376            onAdSenseEvent: function () {},
377            onClickEvent: function (params) {},
378            onWatchVideo: function (params) {},
379            onCommentEvent: function () {},
380            onFormEvent: function (params) {},
381            onDownloadEvent: function (params) {},
382            onWooAddToCartOnButtonEvent: function (product_id) {},
383            onWooAddToCartOnSingleEvent: function (product_id, qty, is_variable, is_external, $form) {},
384            onWooRemoveFromCartEvent: function (cart_item_hash) {},
385            onWooAffiliateEvent: function (product_id) {},
386            onWooPayPalEvent: function () {},
387            onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {},
388            onEddRemoveFromCartEvent: function (item) {},
389            onPageScroll: function (event) {},
390            onTime: function (event) {},
391        }
392
393    }();
394
395	var dummyReddit = function () {
396		/**
397		 * Public API
398		 */
399		return {
400			tag: function () {
401				return "reddit";
402			},
403			isEnabled: function () {},
404			disable: function () {},
405			loadPixel: function () {},
406			fireEvent: function ( name, data ) {
407				return false;
408			},
409			onAdSenseEvent: function ( event ) {},
410			onClickEvent: function ( params ) {},
411			onWatchVideo: function ( params ) {},
412			onCommentEvent: function ( event ) {},
413			onFormEvent: function ( params ) {},
414			onDownloadEvent: function ( params ) {},
415			onWooAddToCartOnButtonEvent: function ( product_id ) {},
416			onWooAddToCartOnSingleEvent: function ( product_id, qty, product_type, is_external, $form ) {},
417			onWooRemoveFromCartEvent: function ( cart_item_hash ) {},
418			onWooAffiliateEvent: function ( product_id ) {},
419			onWooPayPalEvent: function ( event ) {},
420			onEddAddToCartOnButtonEvent: function ( download_id, price_index, qty ) {},
421			onEddRemoveFromCartEvent: function ( item ) {},
422			onPageScroll: function ( event ) {},
423			onTime: function ( event ) {},
424		}
425	}();
426
427
428	var Utils = function (options) {
429
430        var Pinterest = dummyPinterest;
431
432        var Bing = dummyBing;
433
434		var Reddit = dummyReddit;
435
436        var gtag_loaded = false;
437
438        var gtm_loaded = false;
439
440        // The GTM container's consent state is sent once per page load. Two code
441        // paths reach it (loadGTMScript() and the gtm_just_data_layer branch of
442        // GTM.loadPixel(), which can both run for the same page), and Consent Mode
443        // expects a single transition, not a stream of them.
444        var gtm_consent_state_pushed = false;
445
446        let isNewSession = checkSession();
447
448        // In-flight pys_get_pbid request. Several code paths call manageCookies()
449        // before the first reply lands; one request is enough for all of them.
450        var pbidRequest = null;
451        // True once a pixel has waited for that reply (or given up on it), so
452        // the hold in holdForIdentity() happens at most once per page.
453        var pbidSettled = false;
454        var PBID_WAIT_MS = 1000;
455
456        var utmTerms = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'];
457
458        var utmId = ['fbadid', 'gadid', 'padid', 'bingid'];
459
460        let dataLayerName = 'dataLayerPYS';
461
462        let GTMdataLayerName = 'dataLayer';
463
464        function validateEmail(email) {
465            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,}))$/;
466            return re.test(email);
467        }
468        function getDomain(url) {
469
470            url = url.replace(/(https?:\/\/)?(www.)?/i, '');
471
472            if (url.indexOf('/') !== -1) {
473                return url.split('/')[0];
474            }
475
476            return url;
477        }
478        function loadPixels() {
479
480            Utils.refreshAddonPixels();
481
482            if (!options.gdpr.all_disabled_by_api) {
483
484                if (!options.gdpr.facebook_disabled_by_api) {
485                    Facebook.loadPixel();
486                }
487
488                if (!options.gdpr.analytics_disabled_by_api) {
489                    Analytics.loadPixel();
490                }
491
492                if (!options.gdpr.analytics_disabled_by_api) {
493                    GTM.loadPixel();
494                }
495
496                if (!options.gdpr.pinterest_disabled_by_api) {
497                    Pinterest.loadPixel();
498                }
499
500                if (!options.gdpr.bing_disabled_by_api) {
501                    Bing.loadPixel();
502                }
503
504                if (!options.gdpr.openai_disabled_by_api) {
505                    OpenAI.loadPixel();
506                }
507
508	            if (!options.gdpr.reddit_disabled_by_api) {
509		            Reddit.loadPixel();
510	            }
511            }
512            if (options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined") {
513                if (typeof CS_Data.cs_google_analytics_consent_mode !== "undefined" && CS_Data.cs_google_analytics_consent_mode == 1) {
514                    Analytics.loadPixel();
515                }
516            }
517
518        }
519
520        function checkSession() {
521            if( Cookies.get('pys_start_session') === undefined ||
522                Cookies.get('pys_session_limit') === undefined) {
523                firstVisit = true;
524                return true
525            }
526            return false
527
528        }
529
530        function getTrafficSource() {
531
532            try {
533
534                let referrer = stripUrlQuery(document.referrer.toString()),
535                    source;
536
537                let direct = referrer.length === 0;
538                let internal = direct ? false : referrer.indexOf(options.siteUrl) === 0;
539                let external = !direct && !internal;
540
541                if (external === false) {
542                    source = 'direct';
543                } else {
544                    source = referrer;
545                }
546
547                if (source !== 'direct') {
548                    // leave only domain (Issue #70)
549                    return getDomain(source);
550                } else {
551                    return source;
552                }
553
554            } catch (e) {
555                console.error(e);
556                return 'direct';
557            }
558
559        }
560
561        /**
562         * Return query variables object with where property name is query variable
563         * and property value is query variable value.
564         */
565        function getQueryVars() {
566
567            try {
568
569                var result = {},
570                    tmp = [];
571
572                window.location.search
573                    .substr(1)
574                    .split("&")
575                    .forEach(function (item) {
576
577                        tmp = item.split('=');
578
579                        if (tmp.length > 1) {
580                            result[tmp[0]] = tmp[1];
581                        }
582
583                    });
584
585                return result;
586
587            } catch (e) {
588                console.error(e);
589                return {};
590            }
591
592        }
593
594
595        /**
596         * @param useCurrentUrlFallback Pass false where an empty value is better
597         *        than a guess, because a more reliable fallback exists downstream.
598         */
599        function getLandingPageValue(useCurrentUrlFallback = true) {
600            let name = "pys_landing_page"
601            if(options.visit_data_model === "last_visit") {
602                name = "last_pys_landing_page"
603            }
604            if(Cookies.get(name) && Cookies.get(name) !== "undefined") {
605                return Cookies.get(name);
606            }
607            // No cookie of our own. The dynamic-options endpoint may still know:
608            // it answers per visitor and is never cached, so it is allowed to carry
609            // the PHP session copy — which is all a visitor whose cookies a consent
610            // manager has blocked ever has. In the HTML this value is always empty,
611            // and an empty value means "nothing to say", so this branch is simply
612            // skipped unless the endpoint filled it in.
613            if ( options.hasOwnProperty( "tracking_analytics" )
614                && options.tracking_analytics
615                && options.tracking_analytics.TrafficLanding ) {
616                return options.tracking_analytics.TrafficLanding;
617            }
618
619            // Nobody knows better than the current URL: it is what the cookie would
620            // have been set to a moment ago.
621            return useCurrentUrlFallback ? window.location.href : "";
622        }
623        function getTrafficSourceValue() {
624            let name = "pysTrafficSource"
625            if(options.visit_data_model === "last_visit") {
626                name = "last_pysTrafficSource"
627            }
628            if(Cookies.get(name) && Cookies.get(name) !== "undefined") {
629                return Cookies.get(name);
630            }
631            // See getLandingPageValue(): filled by the dynamic-options endpoint only.
632            if ( options.hasOwnProperty( "tracking_analytics" )
633                && options.tracking_analytics
634                && options.tracking_analytics.TrafficSource ) {
635                return options.tracking_analytics.TrafficSource;
636            }
637            return "";
638        }
639
640        function getUTMId(useLast = false) {
641            try {
642                let cookiePrefix = 'pys_'
643                let terms = [];
644                if (useLast) {
645                    cookiePrefix = 'last_pys_'
646                }
647                // Fall back to this request's own query string, not to the server
648                // options: cached HTML carries whatever the visitor who generated
649                // the cache entry arrived with.
650                let queryVars = getQueryVars();
651                $.each(utmId, function (index, name) {
652                    if (Cookies.get(cookiePrefix + name)) {
653                        terms[name] = Cookies.get(cookiePrefix + name)
654                    }
655                    else if(options.hasOwnProperty("tracking_analytics")
656                        && options.tracking_analytics
657                        && options.tracking_analytics.TrafficUtmsId
658                        && options.tracking_analytics.TrafficUtmsId[name]) {
659                        terms[name] = filterEmails(options.tracking_analytics.TrafficUtmsId[name])
660                    }
661                    else if(queryVars.hasOwnProperty(name)) {
662                        terms[name] = filterEmails(queryVars[name])
663                    }
664                });
665                return terms;
666            } catch (e) {
667                console.error(e);
668                return [];
669            }
670        }
671        /**
672         * Return UTM terms from request query variables or from cookies.
673         */
674        function getUTMs(useLast = false) {
675
676            try {
677                let cookiePrefix = 'pys_'
678                if(useLast) {
679                    cookiePrefix = 'last_pys_'
680                }
681                let terms = [];
682                // See getUTMId(): the query string, never the server options.
683                let queryVars = getQueryVars();
684                $.each(utmTerms, function (index, name) {
685                    if (Cookies.get(cookiePrefix + name)) {
686                        let value = Cookies.get(cookiePrefix + name);
687                        terms[name] = filterEmails(value); // do not allow email in request params (Issue #70)
688                    }
689                    else if(options.hasOwnProperty("tracking_analytics")
690                        && options.tracking_analytics
691                        && options.tracking_analytics.TrafficUtms
692                        && options.tracking_analytics.TrafficUtms[name]) {
693                        terms[name] = filterEmails(options.tracking_analytics.TrafficUtms[name])
694                    }
695                    else if(queryVars.hasOwnProperty(name)) {
696                        terms[name] = filterEmails(queryVars[name])
697                    }
698                });
699
700                return terms;
701
702            } catch (e) {
703                console.error(e);
704                return [];
705            }
706
707        }
708
709        function getDateTime() {
710            var dateTime = new Array();
711            var date = new Date(),
712                days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
713                months = ['January', 'February', 'March', 'April', 'May', 'June',
714                    'July', 'August', 'September', 'October', 'November', 'December'
715                ],
716                hours = ['00-01', '01-02', '02-03', '03-04', '04-05', '05-06', '06-07', '07-08',
717                    '08-09', '09-10', '10-11', '11-12', '12-13', '13-14', '14-15', '15-16', '16-17',
718                    '17-18', '18-19', '19-20', '20-21', '21-22', '22-23', '23-24'
719                ];
720            dateTime.push(hours[date.getHours()]);
721            dateTime.push(days[date.getDay()]);
722            dateTime.push(months[date.getMonth()]);
723            return dateTime;
724        }
725
726        function filterEmails(value) {
727            return validateEmail(value) ? undefined : value;
728        }
729
730        /**
731         * PUBLIC API
732         */
733        return {
734            PRODUCT_SIMPLE : 0,
735            PRODUCT_VARIABLE : 1,
736            PRODUCT_BUNDLE : 2,
737            PRODUCT_GROUPED : 3,
738            utmTerms : utmTerms,
739            utmId : utmId,
740            fireEventForAllPixel:function(functionName,events){
741                Utils.refreshAddonPixels();
742
743                if (events.hasOwnProperty(Facebook.tag()))
744                    Facebook[functionName](events[Facebook.tag()]);
745                if (events.hasOwnProperty(Analytics.tag()))
746                    Analytics[functionName](events[Analytics.tag()]);
747                if (events.hasOwnProperty(Pinterest.tag()))
748                    Pinterest[functionName](events[Pinterest.tag()]);
749                if (events.hasOwnProperty(Bing.tag()))
750                    Bing[functionName](events[Bing.tag()]);
751	            if (events.hasOwnProperty(Reddit.tag()))
752		            Reddit[functionName](events[Reddit.tag()]);
753
754                if (events.hasOwnProperty(GTM.tag()))
755                    GTM[functionName](events[GTM.tag()]);
756            },
757
758            /**
759             * Re-read the pixels that live in separate plugins.
760             */
761            refreshAddonPixels: function () {
762                Pinterest = Utils.setupPinterestObject();
763                Bing = Utils.setupBingObject();
764                Reddit = Utils.setupRedditObject();
765            },
766
767            /**
768             * Is one of ConsentMagic's switches actually on?
769             */
770            csModeEnabled: function ( value ) {
771                return Number( value ) === 1;
772            },
773
774            setupPinterestObject: function () {
775                Pinterest = window.pys.Pinterest || Pinterest;
776                return Pinterest;
777            },
778
779            setupBingObject: function () {
780                Bing = window.pys.Bing || Bing;
781                return Bing;
782            },
783
784	        setupRedditObject: function () {
785		        Reddit = window.pys.Reddit || Reddit;
786		        return Reddit;
787	        },
788
789            // Clone all object members to another and return it
790            copyProperties: function (from, to) {
791                for (var key in from) {
792                    if("function" == typeof from[key]) {
793                        continue;
794                    }
795                    to[key] = from[key];
796                }
797                return to;
798            },
799
800            /**
801             * Ask PHP for this browser's external_id (single-flight).
802             *
803             * The cookie keeps the id across a network change, so it is
804             * requested whenever external_id is on -- not only when server
805             * events go over AJAX. Returns the in-flight jqXHR, or null when no
806             * request is warranted (feature off, cookie already there, no
807             * Meta pixel).
808             */
809            requestPbid: function () {
810                if (!options.send_external_id || Cookies.get('pbid') || !Facebook.isEnabled()) {
811                    return null;
812                }
813
814                if (!pbidRequest) {
815                    pbidRequest = jQuery.ajax({
816                        url: options.ajaxUrl,
817                        dataType: 'json',
818                        data: {
819                            action: 'pys_get_pbid'
820                        },
821                        success: function (res) {
822                            if (res.data && res.data.pbid != false) {
823                                if(!(options.cookie.disabled_all_cookie || options.cookie.externalID_disabled_by_api)){
824                                    var expires = parseInt(options.external_id_expire || 180);
825                                    Cookies.set('pbid', res.data.pbid, { expires: expires, path: '/',domain: cookieDomain() });
826                                }
827
828                                if(options.hasOwnProperty('facebook')) {
829                                    options.facebook.advancedMatching = {
830                                        ...options.facebook.advancedMatching,  // spread current advancedMatching values
831                                        external_id: res.data.pbid
832                                    };
833                                }
834                            }
835                        }
836                    }).always(function () {
837                        pbidRequest = null;
838                    });
839                }
840
841                return pbidRequest;
842            },
843
844            /**
845             * Defer `fn` until this browser's external_id is known, or until a
846             * short ceiling passes. Returns true when `fn` was deferred (it will
847             * be called back), false when the caller may go ahead right now.
848             *
849             * Pixels read external_id when they initialise. On a first visit the
850             * cookie does not exist yet, and the inline value in the page may
851             * belong to whoever rendered a cached copy of it; the reply to
852             * pys_get_pbid is never cached and names this browser, so the first
853             * PageView waits for it. Sites that fetch identity over REST already
854             * have the right value by now and do not wait again.
855             */
856            holdForIdentity: function (fn) {
857                if (pbidSettled || !options.send_external_id || Cookies.get('pbid')) {
858                    return false;
859                }
860
861                if (options.dynamicDataUrl && options.facebook && options.facebook.advancedMatching
862                    && options.facebook.advancedMatching.external_id) {
863                    return false;
864                }
865
866                var request = Utils.requestPbid();
867                if (!request) {
868                    return false;
869                }
870
871                var done = false;
872                var finish = function () {
873                    if (done) {
874                        return;
875                    }
876                    done = true;
877                    pbidSettled = true;
878                    fn();
879                };
880
881                request.always(finish);
882                setTimeout(finish, PBID_WAIT_MS);
883
884                return true;
885            },
886
887            manageCookies: function () {
888
889                if ( typeof options.cookie === 'undefined' ) {
890                    return;
891                }
892
893                if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') {
894                    if (Cookiebot.consented === false && !Cookiebot.consent['marketing'] && !Cookiebot.consent['statistics']) {
895                        return;
896                    }
897                }
898                let cm_consent_not_expressed = false;
899                if ( options.gdpr.consent_magic_integration_enabled && window.CS_Data !== undefined && window.CS_Data.cs_refresh_after_consent == 1 ) {
900                    if ( Cookies.get( 'cs_viewed_cookie_policy' ) === undefined ) {
901                        cm_consent_not_expressed = true;
902                    }
903                }
904
905                // Hand PHP the browser-verified cookie domain, under the same
906                // consent gates as every cookie written below it.
907                if( !cm_consent_not_expressed && !options.cookie.disabled_all_cookie ) {
908                    reportCookieDomain(cookieDomain());
909                }
910
911                if( !cm_consent_not_expressed && isNewSession && !options.cookie.disabled_all_cookie && !options.cookie.disabled_start_session_cookie) {
912                    let duration = options.last_visit_duration * 60000
913                    var now = new Date();
914                    now.setTime(now.getTime() + duration);
915                    Cookies.set('pys_session_limit', true,{ expires: now, path: '/',domain: cookieDomain() })
916                    Cookies.set('pys_start_session', true,{path: '/',domain: cookieDomain()});
917                }
918
919                if (!Cookies.get('pbid')) {
920                    Utils.requestPbid();
921                } else if (Facebook.isEnabled()){
922                    if(Facebook.advancedMatching() && Facebook.advancedMatching().external_id && !(options.cookie.disabled_all_cookie || options.cookie.externalID_disabled_by_api)){
923                        let expires = parseInt(options.external_id_expire || 180);
924                        Cookies.set('pbid', Facebook.advancedMatching().external_id, { expires: expires, path: '/',domain: cookieDomain() });
925                    }
926                }
927
928                let expires = parseInt(options.cookie_duration); //  days
929                let queryVars = getQueryVars();
930                let landing = window.location.href.split('?')[0];
931                try {
932                    // save data for first visit
933                    if(Cookies.get('pys_first_visit') === undefined && (!options.cookie.disabled_all_cookie)) {
934
935                        if(!options.cookie.disabled_first_visit_cookie)
936                        {
937                            Cookies.set('pys_first_visit', true, { expires: expires, path: '/',domain: cookieDomain() });
938                        }
939                        else {
940                            Cookies.remove('pys_first_visit', { path: '/',domain: cookieDomain() })
941                        }
942
943                        if(!options.cookie.disabled_trafficsource_cookie)
944                        {
945                            Cookies.set('pysTrafficSource', getTrafficSource(), { expires: expires,path: '/',domain: cookieDomain() });
946                        }
947                        else {
948                            Cookies.remove('pysTrafficSource', { path: '/',domain: cookieDomain() })
949                        }
950
951                        if(!options.cookie.disabled_landing_page_cookie)
952                        {
953                            Cookies.set('pys_landing_page',landing,{ expires: expires,path: '/',domain: cookieDomain() });
954                        }
955                        else {
956                            Cookies.remove('pys_landing_page', { path: '/',domain: cookieDomain() })
957                        }
958
959                        if(!options.cookie.disabled_utmTerms_cookie)
960                        {
961                            $.each(utmTerms, function (index, name) {
962                                if (queryVars.hasOwnProperty(name)) {
963                                    Cookies.set('pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() });
964                                } else {
965                                    Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
966                                }
967                            });
968                        }
969                        else {
970                            $.each(utmTerms, function (index, name) {
971                                Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
972                            });
973                        }
974
975                        if(!options.cookie.disabled_utmId_cookie)
976                        {
977                            $.each(utmId,function(index,name) {
978                                if (queryVars.hasOwnProperty(name)) {
979                                    Cookies.set('pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() });
980                                } else {
981                                    Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
982                                }
983                            })
984                        }
985                        else {
986                            $.each(utmId, function (index, name) {
987                                Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
988                            });
989                        }
990                    }
991
992                    // save data for last visit if it new session
993                    if(isNewSession && (!options.cookie.disabled_all_cookie)) {
994                        if(!options.cookie.disabled_trafficsource_cookie)
995                        {
996                            Cookies.set('last_pysTrafficSource', getTrafficSource(), { expires: expires,path: '/',domain: cookieDomain() });
997                        }
998                        else {
999                            Cookies.remove('last_pysTrafficSource', { path: '/',domain: cookieDomain() })
1000                        }
1001
1002                        if(!options.cookie.disabled_landing_page_cookie)
1003                        {
1004                            Cookies.set('last_pys_landing_page',landing,{ expires: expires,path: '/',domain: cookieDomain() });
1005                        }
1006                        else {
1007                            Cookies.remove('last_pys_landing_page', { path: '/',domain: cookieDomain() })
1008                        }
1009
1010                        if(!options.cookie.disabled_utmTerms_cookie)
1011                        {
1012                            $.each(utmTerms, function (index, name) {
1013                                if (queryVars.hasOwnProperty(name)) {
1014                                    Cookies.set('last_pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() });
1015                                } else {
1016                                    Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
1017                                }
1018                            });
1019                        }
1020                        else {
1021                            $.each(utmTerms, function (index, name) {
1022                                Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
1023                            });
1024                        }
1025
1026                        if(!options.cookie.disabled_utmId_cookie)
1027                        {
1028                            $.each(utmId,function(index,name) {
1029                                if (queryVars.hasOwnProperty(name)) {
1030                                    Cookies.set('last_pys_' + name, queryVars[name], { expires: expires,path: '/',domain: cookieDomain() });
1031                                } else {
1032                                    Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
1033                                }
1034                            })
1035                        }
1036                        else {
1037                            $.each(utmId, function (index, name) {
1038                                Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
1039                            });
1040                        }
1041
1042                    }
1043                    if(options.cookie.disabled_start_session_cookie) {
1044                        Cookies.remove('pys_start_session', { path: '/',domain: cookieDomain() })
1045                        Cookies.remove('pys_session_limit', { path: '/',domain: cookieDomain() })
1046                    }
1047                    if(options.cookie.disabled_all_cookie)
1048                    {
1049                        Cookies.remove('pys_first_visit', { path: '/',domain: cookieDomain() })
1050                        Cookies.remove('pysTrafficSource', { path: '/',domain: cookieDomain() })
1051                        Cookies.remove('pys_landing_page', { path: '/',domain: cookieDomain() })
1052                        Cookies.remove('last_pys_landing_page', { path: '/',domain: cookieDomain() })
1053                        Cookies.remove('last_pysTrafficSource', { path: '/',domain: cookieDomain() })
1054                        Cookies.remove('pys_start_session', { path: '/',domain: cookieDomain() })
1055                        Cookies.remove('pys_session_limit', { path: '/',domain: cookieDomain() })
1056                        $.each(Utils.utmTerms, function (index, name) {
1057                            Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
1058                        });
1059                        $.each(Utils.utmId,function(index,name) {
1060                            Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
1061                        })
1062                        $.each(Utils.utmTerms, function (index, name) {
1063                            Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
1064                        });
1065                        $.each(Utils.utmId,function(index,name) {
1066                            Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
1067                        });
1068                    }
1069                } catch (e) {
1070                    console.error(e);
1071                }
1072            },
1073            /**
1074             * Is this Meta pixel opted into Meta-Enabled Conversion API?
1075             */
1076            isMetaEnabledCapiPixel: function (pixelId) {
1077                var pixels = options.facebook && options.facebook.metaEnabledCapiPixels;
1078                return Array.isArray(pixels) && pixels.indexOf(pixelId) !== -1;
1079            },
1080            /**
1081             * Generate unique ID
1082             */
1083            generateUniqueId : function (event) {
1084                if(event.eventID.length == 0 || (event.type == "static" && options.ajaxForServerStaticEvent) || (event.type !== "static" && options.ajaxForServerEvent)) {
1085                    let idKey = event.hasOwnProperty('custom_event_post_id') ? event.custom_event_post_id : event.e_id;
1086                    if (!uniqueId.hasOwnProperty(idKey)) {
1087                        uniqueId[idKey] = pys_generate_token();
1088                    }
1089                    return uniqueId[idKey];
1090                }
1091                else if(event.eventID.length !== 0)
1092                {
1093                    return event.eventID;
1094                }
1095            },
1096
1097            // Flatten object for sendBeacon compatibility
1098            flattenObject: function (obj, prefix = '', res = {}) {
1099                for (const [key, value] of Object.entries(obj)) {
1100                    const prefixedKey = prefix
1101                        ? (Array.isArray(obj) ? `${prefix}[${key}]` : `${prefix}[${key}]`)
1102                        : key;
1103                    if (value !== null && typeof value === 'object' && !(value instanceof File)) {
1104                        this.flattenObject(value, prefixedKey, res);
1105                    } else {
1106                        res[prefixedKey] = value;
1107                    }
1108                }
1109                return res;
1110            },
1111
1112            sendServerAjaxRequest : function(url, data) {
1113                if (data.action === 'pys_api_event' && data.pixel === 'facebook' && window.pysFacebookRest) {
1114                    // Use Facebook REST API
1115                    this.sendRestAPIRequest(data, 'facebook');
1116                    return;
1117                }
1118
1119                if (data.action === 'pys_openai_api_event' && window.pysOpenAIRest) {
1120                    this.sendRestAPIRequest(data, 'openai');
1121                    return;
1122                }
1123
1124                // Check if sendBeacon is enabled and supported
1125                if (options.useSendBeacon && navigator.sendBeacon) {
1126                    try {
1127                        // Flatten the data object for sendBeacon compatibility
1128                        const flattenedData = this.flattenObject(data);
1129                        const formData = new URLSearchParams();
1130
1131                        // Convert flattened data to URLSearchParams
1132                        for (const [key, value] of Object.entries(flattenedData)) {
1133                            if (value !== null && value !== undefined) {
1134                                formData.append(key, value);
1135                            }
1136                        }
1137
1138                        // Try to send using sendBeacon
1139                        const success = navigator.sendBeacon(url, formData);
1140                        if (success) {
1141                            return; // Successfully sent via sendBeacon
1142                        }
1143                    } catch (e) {
1144                        // If sendBeacon fails, fall back to jQuery.ajax
1145                        if (options.debug) {
1146                            console.log('PYS: sendBeacon failed, falling back to jQuery.ajax:', e);
1147                        }
1148                    }
1149                }
1150
1151                // Fallback to jQuery.ajax
1152                jQuery.ajax( {
1153                    type: 'POST',
1154                    url: url,
1155                    data: data,
1156                    headers: {
1157                        'Cache-Control': 'no-cache'
1158                    },
1159                    success: function () {
1160                    },
1161                } );
1162            },
1163
1164            sendRestAPIRequest: function (data, platform) {
1165                let restApiUrl;
1166
1167                // Get platform-specific REST API configuration
1168                switch (platform) {
1169                    case 'facebook':
1170                        restApiUrl = window.pysFacebookRest ? window.pysFacebookRest.restApiUrl : '/wp-json/pys-facebook/v1/event';
1171                        break;
1172                    case 'openai':
1173                        restApiUrl = window.pysOpenAIRest ? window.pysOpenAIRest.restApiUrl : '/wp-json/pys-openai/v1/event';
1174                        break;
1175                    default:
1176                        console.error('PYS: Unknown platform for REST API:', platform);
1177                        this.sendAjaxFallback(data);
1178                        return;
1179                }
1180
1181                // Prepare data for REST API
1182                const restApiData = {
1183                    event: data.event,
1184                    event_slug: data.event_slug || '',
1185                    data: JSON.stringify(data.data || {}),
1186                    ids: JSON.stringify(data.ids || []),
1187                    eventID: data.event_id || data.eventID || '',
1188                    woo_order: data.woo_order || '0',
1189                    edd_order: data.edd_order || '0',
1190                    order_key: data.order_key || ''
1191                };
1192
1193                const forwardedReferrer = data.referrer_url || pageReferrer;
1194                if (forwardedReferrer) {
1195                    restApiData.referrer_url = forwardedReferrer;
1196                }
1197
1198                // Variant A: send the actual page URL where the event happened
1199                // so the server sets event_source_url to this page, not the
1200                // REST endpoint. PHP applies enable_remove_source_url_params.
1201                restApiData.event_source_url = data.url || window.location.href;
1202
1203                // Try to send using sendBeacon first (if enabled)
1204                if (options.useSendBeacon && navigator.sendBeacon) {
1205                    try {
1206                        const formData = new URLSearchParams();
1207                        for (const [key, value] of Object.entries(restApiData)) {
1208                            formData.append(key, value);
1209                        }
1210
1211                        if (navigator.sendBeacon(restApiUrl, formData)) {
1212                            return;
1213                        }
1214                    } catch (e) {
1215                        // sendBeacon failed, continue to fetch
1216                    }
1217                }
1218
1219                // Try to send using fetch with REST API
1220                if (window.fetch) {
1221                    const headers = {
1222                        'Content-Type': 'application/json'
1223                    };
1224
1225                    fetch(restApiUrl, {
1226                        method: 'POST',
1227                        headers: headers,
1228                        body: JSON.stringify(restApiData)
1229                    })
1230                        .then(response => {
1231                            if (!response.ok) {
1232                                throw new Error(platform + ' REST API request failed: ' + response.status);
1233                            }
1234                        })
1235                        .catch(error => {
1236                            // Fallback to AJAX if REST API fails
1237                            if (options.debug) {
1238                                console.log('PYS: ' + platform + ' REST API failed, falling back to AJAX:', error);
1239                            }
1240                            this.sendAjaxFallback(data);
1241                        });
1242                } else {
1243                    // Fallback to AJAX if fetch is not supported
1244                    this.sendAjaxFallback(data);
1245                }
1246            },
1247
1248
1249
1250            // Fallback AJAX method
1251            sendAjaxFallback: function (data) {
1252                jQuery.ajax({
1253                    type: 'POST',
1254                    url: options.ajaxUrl,
1255                    data: data,
1256                    headers: {
1257                        'Cache-Control': 'no-cache'
1258                    },
1259                    success: function () {
1260                    },
1261                });
1262            },
1263            // clone object
1264            clone: function(obj) {
1265                var copy;
1266
1267                // Handle the 3 simple types, and null or undefined
1268                if (null == obj || "object" != typeof obj) return obj;
1269
1270                // Handle Date
1271                if (obj instanceof Date) {
1272                    copy = new Date();
1273                    copy.setTime(obj.getTime());
1274                    return copy;
1275                }
1276
1277                // Handle Array
1278                if (obj instanceof Array) {
1279                    copy = [];
1280                    for (var i = 0, len = obj.length; i < len; i++) {
1281                        if("function" == typeof obj[i]) {
1282                            continue;
1283                        }
1284                        copy[i] = Utils.clone(obj[i]);
1285                    }
1286                    return copy;
1287                }
1288
1289                // Handle Object
1290                if (obj instanceof Object) {
1291                    copy = {};
1292                    for (var attr in obj) {
1293                        if (obj.hasOwnProperty(attr)) {
1294                            if("function" == typeof obj[attr]) {
1295                                continue;
1296                            }
1297                            copy[attr] = Utils.clone(obj[attr]);
1298                        }
1299                    }
1300                    return copy;
1301                }
1302
1303                return obj;
1304            },
1305
1306            // Returns array of elements with given tag name
1307            getTagsAsArray: function (tag) {
1308                return [].slice.call(document.getElementsByTagName(tag));
1309            },
1310
1311            getRequestParams: function () {
1312                return [];
1313            },
1314
1315            /**
1316             * CUSTOM EVENTS
1317             */
1318
1319            setupMouseOverClickEvents: function (eventId, triggers) {
1320
1321                // Non-default binding used to avoid situations when some code in external js
1322                // stopping events propagation, eg. returns false, and our handler will never called
1323                document.addEventListener('mouseover', function(event) {
1324                    var matchedElements = Array.from(document.querySelectorAll(triggers));
1325                    var clickedElement = event.target;
1326                    var closestMatch = clickedElement.closest(triggers);
1327                    if (matchedElements.includes(clickedElement) || closestMatch){
1328                        if (event.target.classList.contains('pys-mouse-over-' + eventId)) {
1329                            return true;
1330                        } else {
1331                            event.target.classList.add('pys-mouse-over-' + eventId);
1332                        }
1333
1334                        Utils.fireTriggerEvent(eventId);
1335                    }
1336                });
1337
1338            },
1339
1340            setupCSSClickEvents: function (eventId, triggers) {
1341                // Non-default binding used to avoid situations when some code in external js
1342                // stopping events propagation, eg. returns false, and our handler will never called
1343                document.addEventListener('click', function(event) {
1344                    let matchedElements = Array.from(document.querySelectorAll(triggers)),
1345                        clickedElement = event.target,
1346                        closestMatch = clickedElement.closest(triggers);
1347
1348                    if (matchedElements.includes(clickedElement) || closestMatch){
1349                        Utils.fireTriggerEvent(eventId);
1350                    }
1351                }, true);
1352            },
1353
1354            setupURLClickEvents: function () {
1355
1356                if( !options.triggerEventTypes.hasOwnProperty('url_click') ) {
1357                    return;
1358                }
1359                // Non-default binding used to avoid situations when some code in external js
1360                // stopping events propagation, eg. returns false, and our handler will never called
1361                $('a[data-pys-event-id]').onFirst('click', function (evt) {
1362
1363                    $(this).attr('data-pys-event-id').split(',').forEach(function (eventId) {
1364
1365                        eventId = parseInt(eventId);
1366
1367                        if (isNaN(eventId) === false) {
1368                            Utils.fireTriggerEvent(eventId);
1369                        }
1370
1371                    });
1372
1373                });
1374
1375
1376            },
1377
1378            setupScrollPosEvents: function (eventId, triggers) {
1379
1380                var scrollPosThresholds = {},
1381                    docHeight = $(document).height() - $(window).height();
1382
1383                // convert % to absolute positions
1384                $.each(triggers, function (index, scrollPos) {
1385
1386                    // convert % to pixels
1387                    scrollPos = docHeight * scrollPos / 100;
1388                    scrollPos = Math.round(scrollPos);
1389
1390                    scrollPosThresholds[scrollPos] = eventId;
1391
1392                });
1393
1394                $(document).on("scroll",function () {
1395
1396                    var scrollPos = $(window).scrollTop();
1397
1398                    $.each(scrollPosThresholds, function (threshold, eventId) {
1399
1400                        // position has not reached yes
1401                        if (scrollPos <= threshold) {
1402                            return true;
1403                        }
1404
1405                        // fire event only once
1406                        if (eventId === null) {
1407                            return true;
1408                        } else {
1409                            scrollPosThresholds[threshold] = null;
1410                        }
1411
1412                        Utils.fireTriggerEvent(eventId);
1413
1414                    });
1415
1416                });
1417
1418
1419            },
1420            setupCommentEvents : function (eventId,triggers) {
1421                $('form.comment-form').on("submit",function () {
1422                    Utils.fireTriggerEvent(eventId);
1423                });
1424            },
1425
1426            /**
1427             * Events
1428             */
1429
1430            fireTriggerEvent: function (eventId) {
1431
1432                if (!options.triggerEvents.hasOwnProperty(eventId)) {
1433                    return;
1434                }
1435
1436                var event = {};
1437                var events = options.triggerEvents[eventId];
1438
1439                if (events.hasOwnProperty('facebook')) {
1440                    event = Utils.getFormFilledData(events.facebook);
1441                    Facebook.fireEvent(event.name, event);
1442                }
1443
1444                if (events.hasOwnProperty('ga')) {
1445                    event = Utils.getFormFilledData(events.ga);
1446                    Analytics.fireEvent(event.name, event);
1447                }
1448
1449                if (events.hasOwnProperty('pinterest')) {
1450                    event = Utils.getFormFilledData(events.pinterest);
1451                    Pinterest.fireEvent(event.name, event);
1452                }
1453
1454                if (events.hasOwnProperty('bing')) {
1455                    event = Utils.getFormFilledData(events.bing);
1456                    Bing.fireEvent(event.name, event);
1457                }
1458
1459	            if (events.hasOwnProperty('reddit')) {
1460                    event = Utils.getFormFilledData(events.reddit);
1461                    Reddit.fireEvent(event.name, event);
1462	            }
1463
1464                if (events.hasOwnProperty('openai')) {
1465                    event = Utils.getFormFilledData(events.openai);
1466                    OpenAI.fireEvent(event.name, event);
1467                }
1468
1469                if (events.hasOwnProperty('gtm')) {
1470                    event = Utils.getFormFilledData(events.gtm);
1471                    GTM.fireEvent(event.name, event);
1472                }
1473            },
1474
1475            fireStaticEvents: function (pixel) {
1476
1477                Utils.refreshAddonPixels();
1478
1479                if (options.staticEvents.hasOwnProperty(pixel)) {
1480
1481                    $.each(options.staticEvents[pixel], function (eventName, events) {
1482                        $.each(events, function (index, eventData) {
1483
1484                            eventData.fired = eventData.fired || false;
1485
1486                            if (!eventData.fired) {
1487                                eventData = Utils.getFormFilledData(eventData);
1488                                var fired = false;
1489
1490                                // fire event
1491                                if ('facebook' === pixel) {
1492                                    fired = Facebook.fireEvent(eventData.name, eventData);
1493                                } else if ('ga' === pixel) {
1494                                    fired = Analytics.fireEvent(eventData.name, eventData);
1495                                } else if ('pinterest' === pixel) {
1496                                    fired = Pinterest.fireEvent(eventData.name, eventData);
1497                                } else if ('bing' === pixel) {
1498                                    fired = Bing.fireEvent(eventData.name, eventData);
1499                                } else if ('gtm' === pixel) {
1500                                    fired = GTM.fireEvent(eventData.name, eventData);
1501                                } else if ('reddit' === pixel) {
1502	                                fired = Reddit.fireEvent(eventData.name, eventData);
1503                                } else if ('openai' === pixel) {
1504                                    fired = OpenAI.fireEvent(eventData.name, eventData);
1505                                }
1506
1507                                // prevent event double event firing
1508                                eventData.fired = fired;
1509
1510                            }
1511
1512                        });
1513                    });
1514
1515                }
1516
1517            },
1518
1519            /**
1520             * Load tag's JS
1521             *
1522             * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/
1523             * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/custom-dims-mets
1524             */
1525            loadGoogleTag: function (id) {
1526
1527                if (!gtag_loaded) {
1528                    let dataLayerName = this.resolveGaDataLayerName();
1529                    this.dataLayerName = dataLayerName;
1530                    (function (window, document, src) {
1531                        var a = document.createElement('script'),
1532                            m = document.getElementsByTagName('script')[0];
1533                        a.async = 1;
1534                        a.src = src;
1535                        m.parentNode.insertBefore(a, m);
1536                    })(window, document, '//www.googletagmanager.com/gtag/js?id=' + id+'&l='+this.dataLayerName);
1537
1538                    window[dataLayerName] = window[dataLayerName] || [];
1539                    // Published only so custom client code and third-party snippets
1540                    // have a gtag() to call. PYS itself must never route through it:
1541                    // the `||` means an earlier definition (Site Kit, a theme, the
1542                    // GTM bootstrap) keeps ownership and would point at its own data
1543                    // layer. Use Utils.gtagPush() instead.
1544                    window.gtag = window.gtag || function gtag() {
1545                        window[dataLayerName].push(arguments);
1546                    };
1547
1548                    if ( options.google_consent_mode ) {
1549                        let data = this.resolveConsentValues();
1550
1551                        // Normally this data layer is ours alone and still empty, so a
1552                        // `default` is what belongs here. But the GA and GTM scopes
1553                        // collapse into one layer when their names resolve to the same
1554                        // value (gtm_dataLayer_name left at 'dataLayerPYS', or the GA
1555                        // layer set to "Disable name transformation"), and then the GTM
1556                        // <head> snippet has already written a strict `denied` default
1557                        // here. Repeating `default` would leave GA denied; `update` is
1558                        // the documented transition once a default exists.
1559                        this.pushConsent( dataLayerName, this.layerHasConsentDefault( dataLayerName ) ? 'update' : 'default', data );
1560                    }
1561
1562                    Utils.gtagPush('js', new Date());
1563                    gtag_loaded = true;
1564
1565                }
1566
1567            },
1568
1569            /**
1570             * Name of the data layer gtag.js is loaded with, see loadGoogleTag(
1570).
1571             * Pure, so it can be resolved before the tag is loaded.
1572             */
1573            resolveGaDataLayerName: function () {
1574                if ( options.hasOwnProperty( 'GATags' ) ) {
1575                    switch ( options.GATags.ga_datalayer_type ) {
1576                        case 'default':
1577                            return 'dataLayerPYS';
1578                        case 'custom':
1579                            return options.GATags.ga_datalayer_name;
1580                        default:
1581                            return 'dataLayer';
1582                    }
1583                }
1584                return this.dataLayerName || 'dataLayer';
1585            },
1586
1587            /**
1588             * Name of the GTM container's data layer. Mirrors GTM::pys_wp_header_top()
1589             * in modules/google_gtm/gtm.php — the two must agree, or the <head> consent
1590             * default and the footer consent update land in different layers. `??`
1591             * alone is not enough: the option can be saved as an empty string, and
1592             * window[''] is a silent dead end.
1593             */
1594            resolveGtmDataLayerName: function () {
1595                var name = options.hasOwnProperty( 'gtm' ) ? options.gtm.gtm_dataLayer_name : '';
1596                return ( typeof name === 'string' && name.length ) ? name : 'dataLayer';
1597            },
1598
1599            /**
1600             * Sends a gtag command (js/config/set/event/consent) to the GA data layer.
1601             *
1602             * Deliberately does NOT call window.gtag. That global is created with
1603             * `window.gtag = window.gtag || ...`, so whoever defined it first owns it:
1604             * if a theme, Site Kit or the GTM bootstrap got there first, our config
1605             * and event commands would silently land in their data layer while the
1606             * gtag.js instance we loaded (&l=<GA layer>) never receives a config —
1607             * Tag Assistant then reports "Some hits will only be sent after a config
1608             * command is issued". Pushing the arguments object is exactly what gtag()
1609             * does internally, so the payload is identical.
1610             */
1611            gtagPush: function () {
1612                var name = this.dataLayerName || this.resolveGaDataLayerName();
1613                window[ name ] = window[ name ] || [];
1614                window[ name ].push( arguments );
1615            },
1616
1617            /**
1618             * Whether a send_to value actually points at a destination.
1619             *
1620             * gtag('event', name, { send_to: '' }) has no destination, so gtag.js
1621             * parks the hit until a matching config command appears. Tag Assistant
1622             * reports that as "Some hits will only be sent after a config command
1623             * is issued via gtag('config')". Callers must skip the event instead.
1624             */
1625            hasSendToTarget: function (target) {
1626                if (Array.isArray(target)) {
1627                    return target.length > 0;
1628                }
1629                return typeof target === 'string' && target !== '';
1630            },
1631
1632            /**
1633             * Back-compat alias of the old GA-scope consent sender. Exposed through
1634             * window.pys.Utils, so custom client code may call it. New code uses
1635             * pushConsent() with an explicit data layer name.
1636             */
1637            loadDefaultConsent: function() {
1638                this.gtagPush.apply( this, arguments );
1639            },
1640
1641            /**
1642             * Sends one Consent Mode command ('default' or 'update') to a named data
1643             * layer. The single place that writes consent commands.
1644             *
1645             * The IIFE is not decoration: gtag() pushes its own `arguments` object,
1646             * and gtag.js / GTM read the command by index off that object. Pushing a
1647             * plain array works today but is not what the tag emits, so we reproduce
1648             * the exact shape — same reason gtm.php builds its <head> command this way.
1649             */
1650            pushConsent: function ( layerName, command, values ) {
1651                if ( !layerName ) {
1652                    return;
1653                }
1654                window[ layerName ] = window[ layerName ] || [];
1655                ( function () {
1656                    window[ layerName ].push( arguments );
1657                } )( 'consent', command, values );
1658            },
1659
1660            /**
1661             * Whether a Consent Mode `default` has already been written to this data
1662             * layer — by our own <head> snippet, by a consent plugin, or by a third
1663             * party (Site Kit, a theme). Decides `default` vs `update`: Consent Mode
1664             * documents exactly one `default` per layer, and a repeated one is not a
1665             * defined transition. Consent Magic reads the layer the same way
1666             * (cs-public.js, CS_Consent_Mode.setDefaultConsent).
1667             */
1668            layerHasConsentDefault: function ( layerName ) {
1669                var layer = window[ layerName ];
1670                if ( !layer || typeof layer.length !== 'number' ) {
1671                    return false;
1672                }
1673                for ( var i = 0; i < layer.length; i++ ) {
1674                    var item = layer[ i ];
1675                    // Entries pushed by other integrations are plain objects; indexing
1676                    // them just yields undefined.
1677                    if ( item && item[ 0 ] === 'consent' && item[ 1 ] === 'default' ) {
1678                        return true;
1679                    }
1680                }
1681                return false;
1682            },
1683
1684            /**
1685             * The four Consent Mode signals as PYS currently resolves them. `enabled`
1686             * is false when consent mode is off for that signal, in which case Google's
1687             * absence of a value means granted anyway — say so explicitly.
1688             */
1689            resolveConsentValues: function () {
1690                return {
1691                    'analytics_storage':  options.gdpr.analytics_storage.enabled  ? options.gdpr.analytics_storage.value  : 'granted',
1692                    'ad_storage':         options.gdpr.ad_storage.enabled         ? options.gdpr.ad_storage.value         : 'granted',
1693                    'ad_user_data':       options.gdpr.ad_user_data.enabled       ? options.gdpr.ad_user_data.value       : 'granted',
1694                    'ad_personalization': options.gdpr.ad_personalization.enabled ? options.gdpr.ad_personalization.value : 'granted',
1695                };
1696            },
1697
1698            loadGTMScript: function (id = '') {
1699                // Strip trailing slash to allow both "domain.com" and "domain.com/" inputs.
1700                const domain = ( options.gtm.gtm_container_domain ?? 'www.googletagmanager.com' ).replace( /\/$/, '' );
1701                const loader = options.gtm.gtm_container_identifier ?? 'gtm';
1702                const gtm_auth = options.gtm.gtm_auth ?? '';
1703                const gtm_preview = options.gtm.gtm_preview ?? '';
1704                const datalayer_name = this.resolveGtmDataLayerName();
1705
1706                window[ datalayer_name ] = window[ datalayer_name ] || [];
1707                window.gtag = window.gtag || function gtag() {
1708                    window[ datalayer_name ].push( arguments );
1709                };
1710
1711                // Send the consent state synchronously before the GTM script tag is
1712                // inserted, so it is in the dataLayer before GTM processes any events.
1713                this.pushGTMConsentState( datalayer_name );
1714
1715                (function(w, d, s, l, i) {
1716                    w[l] = w[l] || [];
1717                    w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
1718                    const f = d.getElementsByTagName(s)[0];
1719                    const j = d.createElement(s);
1720                    const dl = l !== 'dataLayer' ? '&l=' + l : '';
1721                    j.async = true;
1722
1723                    // Detect domain format: bare hostname (legacy) vs full URL / relative path (new).
1724                    // Legacy "www.googletagmanager.com" → prepend https:// and append .js.
1725                    // New format "/metrics" or "https://custom.com" → use as-is.
1726                    //   Identifier with extension (e.g. "gtm.js")  → file:   domain/gtm.js?id=
1727                    //   Identifier without extension (e.g. "g4h25o") → path: domain/g4h25o/?id=
1728                    const isNewFormat = domain.startsWith( 'http://' )
1729                                     || domain.startsWith( 'https://' )
1730                                     || domain.startsWith( '/' );
1731
1732                    if ( !isNewFormat ) {
1733                        j.src = 'https://' + domain + '/' + loader + '.js?id=' + i + dl;
1734                    } else {
1735                        const hasExtension = loader.includes( '.' );
1736                        j.src = domain + '/' + loader + ( hasExtension ? '?' : '/?' ) + 'id=' + i + dl;
1737                    }
1738
1739                    if (gtm_auth && gtm_preview) {
1740                        j.src += '&gtm_auth=' + gtm_auth + '&gtm_preview=' + gtm_preview + '&gtm_cookies_win=x';
1741                    }
1742                    f.parentNode.insertBefore(j, f);
1743                })(window, document, 'script', datalayer_name, id);
1744
1745            },
1746
1747            /**
1748             * Back-compat aliases. Both are reachable as window.pys.Utils.* and may be
1749             * called by custom client code, so they keep working; internally everything
1750             * goes through pushConsent() / pushGTMConsentState() with an explicit layer
1751             * name instead of the implicit this.GTMdataLayerName state.
1752             */
1753            loadDefaultGTMConsent: function() {
1754                window[ this.GTMdataLayerName ] = window[ this.GTMdataLayerName ] || [];
1755                window[ this.GTMdataLayerName ].push( arguments );
1756            },
1757            pushGTMConsentDefaults: function( datalayer_name ) {
1758                this.pushGTMConsentState( datalayer_name );
1759            },
1760
1761            /**
1762             * Sends the GTM container's consent state, once per page load.
1763             *
1764             * Called from loadGTMScript() (PYS loads the container itself) and from
1765             * GTM.loadPixel() when gtm_just_data_layer is on, so the state is sent
1766             * whichever path runs — and only once when both do.
1767             *
1768             * Emits `update`, not a second `default`, whenever a default already sits
1769             * in the layer. modules/google_gtm/gtm.php writes a strict all-denied
1770             * default with wait_for_update:500 in <head>, before the container boots;
1771             * that is correct Consent Mode practice, and it is what makes PYS's own
1772             * prior-consent gate work — this function runs downstream of
1773             * Utils.consentGiven('analytics'), so nothing is granted until consent
1774             * actually exists. What was missing is the other half of the contract:
1775             * the update that resolves the wait_for_update window. A repeated
1776             * `default` is not a defined transition and left GTM-managed tags denied.
1777             */
1778            pushGTMConsentState: function( datalayer_name ) {
1779                if ( !options.google_consent_mode || gtm_consent_state_pushed ) {
1780                    return;
1781                }
1782
1783                // A consent management plugin owns the transition: it issues its own
1784                // update from the user's live choice, while ours carries whatever the
1785                // cookies said when the page was rendered. Consent Magic diffs against
1786                // the last consent values in the layer, so a late push from us either
1787                // suppresses its update or overwrites the choice the user just made.
1788                // The <head> default already carries the CMP's resolved values.
1789                if ( options.consent_mode_cmp_active ) {
1790                    return;
1791                }
1792
1793                gtm_consent_state_pushed = true;
1794                // Kept in sync for the loadDefaultGTMConsent() back-compat alias.
1795                this.GTMdataLayerName = datalayer_name;
1796                this.pushConsent(
1797                    datalayer_name,
1798                    this.layerHasConsentDefault( datalayer_name ) ? 'update' : 'default',
1799                    this.resolveConsentValues()
1800                );
1801            },
1802
1803            /**
1804             * GDPR
1805             */
1806
1807            loadPixels: function () {
1808
1809                if (options.gdpr.ajax_enabled && !options.gdpr.consent_magic_integration_enabled) {
1810
1811                    // retrieves actual PYS GDPR filters values which allow to avoid cache issues
1812                    $.get({
1813                        url: options.ajaxUrl,
1814                        dataType: 'json',
1815                        data: {
1816                            action: 'pys_get_gdpr_filters_values'
1817                        },
1818                        success: function (res) {
1819
1820                            if (res.success) {
1821
1822                                options.gdpr.all_disabled_by_api = res.data.all_disabled_by_api;
1823                                options.gdpr.facebook_disabled_by_api = res.data.facebook_disabled_by_api;
1824                                options.gdpr.analytics_disabled_by_api = res.data.analytics_disabled_by_api;
1825                                options.gdpr.google_ads_disabled_by_api = res.data.google_ads_disabled_by_api;
1826                                options.gdpr.pinterest_disabled_by_api = res.data.pinterest_disabled_by_api;
1827                                options.gdpr.bing_disabled_by_api = res.data.bing_disabled_by_api;
1828	                            options.gdpr.reddit_disabled_by_api = res.data.reddit_disabled_by_api;
1829                                options.gdpr.openai_disabled_by_api = res.data.openai_disabled_by_api;
1830
1831                                options.cookie.externalID_disabled_by_api = res.data.externalID_disabled_by_api;
1832                                options.cookie.disabled_all_cookie = res.data.disabled_all_cookie;
1833                                options.cookie.disabled_advanced_form_data_cookie = res.data.disabled_advanced_form_data_cookie;
1834                                options.cookie.disabled_landing_page_cookie = res.data.disabled_landing_page_cookie;
1835                                options.cookie.disabled_first_visit_cookie = res.data.disabled_first_visit_cookie;
1836                                options.cookie.disabled_trafficsource_cookie = res.data.disabled_trafficsource_cookie;
1837                                options.cookie.disabled_utmTerms_cookie = res.data.disabled_utmTerms_cookie;
1838                                options.cookie.disabled_utmId_cookie = res.data.disabled_utmId_cookie;
1839                            }
1840
1841                            loadPixels();
1842
1843                        }
1844                    });
1845
1846                } else {
1847                    loadPixels();
1848                }
1849
1850            },
1851
1852            consentGiven: function (pixel) {
1853
1854                /**
1855                 * ConsentMagic
1856                 */
1857                if ( options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined" ) {
1858
1859                    let test_prefix = CS_Data.test_prefix;
1860                    if (
1861                        ( Utils.csModeEnabled( CS_Data.cs_google_consent_mode_enabled ) && ( pixel == 'analytics' || pixel == 'google_ads' ) )
1862                        || ( Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) && pixel == 'facebook' )
1863                        || ( Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) && pixel == 'reddit' )
1864                        || ( Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) && pixel == 'openai' )
1865                        || ( Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) && pixel == 'bing' )
1866                    ) {
1867                        if ( Number( CS_Data.cs_cache_enabled ) === 0
1868                            || ( Utils.csModeEnabled( CS_Data.cs_cache_enabled ) && window.CS_Cache && window.CS_Cache.check_status ) ) {
1869                            return true;
1870                        } else {
1871                            return false;
1872                        }
1873                    }
1874
1875                    if( pixel == 'facebook' && ( CS_Data.cs_script_cat.facebook == 0 || CS_Data.cs_script_cat.facebook == CS_Data.cs_necessary_cat_id ) ) {
1876                        return true;
1877                    } else if( pixel == 'bing' && ( CS_Data.cs_script_cat.bing == 0 || CS_Data.cs_script_cat.bing == CS_Data.cs_necessary_cat_id ) ) {
1878                        return true;
1879                    } else if( pixel == 'analytics' && ( CS_Data.cs_script_cat.analytics == 0 || CS_Data.cs_script_cat.analytics == CS_Data.cs_necessary_cat_id ) ) {
1880                        return true;
1881                    } else if( pixel == 'google_ads' && ( CS_Data.cs_script_cat.gads == 0 || CS_Data.cs_script_cat.gads == CS_Data.cs_necessary_cat_id ) ) {
1882                        return true;
1883                    } else if( pixel == 'pinterest' && ( CS_Data.cs_script_cat.pinterest == 0 || CS_Data.cs_script_cat.pinterest == CS_Data.cs_necessary_cat_id ) ) {
1884                        return true;
1885                    } else if( pixel == 'tiktok' && ( CS_Data.cs_script_cat.tiktok == 0 || CS_Data.cs_script_cat.tiktok == CS_Data.cs_necessary_cat_id ) ) {
1886                        return true;
1887		            } else if( pixel == 'reddit' && ( CS_Data.cs_script_cat?.reddit == 0 || CS_Data.cs_script_cat?.reddit == CS_Data.cs_necessary_cat_id ) ) {
1888		                return true;
1889		            } else if( pixel == 'openai' && ( CS_Data.cs_script_cat?.openai == 0 || CS_Data.cs_script_cat?.openai == CS_Data.cs_necessary_cat_id ) ) {
1890		                return true;
1891		            }
1892
1893                    let substring = "cs_enabled_cookie_term",
1894                        theCookies = document.cookie.split( ';' );
1895
1896                    for ( let i = 1; i <= theCookies.length; i++ ) {
1897                        if ( theCookies[ i - 1 ].indexOf( substring ) !== -1 ) {
1898                            let categoryCookie = theCookies[ i - 1 ].replace( 'cs_enabled_cookie_term' + test_prefix + '_', '' );
1899                            categoryCookie = Number( categoryCookie.replace( /\D+/g, "" ) );
1900                            let cs_cookie_val = Cookies.get( 'cs_enabled_cookie_term' + test_prefix + '_' + categoryCookie );
1901
1902                            if ( categoryCookie === CS_Data.cs_script_cat.facebook && pixel == 'facebook' ) {
1903                                return cs_cookie_val == 'yes';
1904                            } else if ( categoryCookie === CS_Data.cs_script_cat.bing && pixel == 'bing' ) {
1905                                return cs_cookie_val == 'yes';
1906                            } else if ( categoryCookie === CS_Data.cs_script_cat.analytics && pixel == 'analytics' ) {
1907                                return cs_cookie_val == 'yes';
1908                            } else if ( categoryCookie === CS_Data.cs_script_cat.gads && pixel == 'google_ads' ) {
1909                                return cs_cookie_val == 'yes';
1910                            } else if ( categoryCookie === CS_Data.cs_script_cat.pinterest && pixel == 'pinterest' ) {
1911                                return cs_cookie_val == 'yes';
1912                            } else if ( categoryCookie === CS_Data.cs_script_cat.tiktok && pixel == 'tiktok' ) {
1913                                return cs_cookie_val == 'yes';
1914                            } else if ( categoryCookie === CS_Data.cs_script_cat?.reddit && pixel == 'reddit' ) {
1915	                            return cs_cookie_val == 'yes';
1916                            } else if ( categoryCookie === CS_Data.cs_script_cat?.openai && pixel == 'openai' ) {
1917                                return cs_cookie_val == 'yes';
1918                            }
1919                        }
1920                    }
1921
1922                    return false;
1923                }
1924
1925                /**
1926                 * Real Cookie Banner
1927                 */
1928                if(options.gdpr.real_cookie_banner_integration_enabled) {
1929                    var consentApi = window.consentApi;
1930                    if (consentApi) {
1931                        switch (pixel) {
1932                            case "analytics":
1933                                return consentApi.consentSync("http", "_ga", "*").cookieOptIn;
1934                            case "facebook":
1935                                return consentApi.consentSync("http", "_fbp", "*").cookieOptIn;
1936                            case "pinterest":
1937                                return consentApi.consentSync("http", "_pinterest_sess", ".pinterest.com").cookieOptIn;
1938                            default:
1939                                return true;
1940                        }
1941                    }
1942                }
1943
1944                /**
1945                 * Cookiebot
1946                 */
1947                if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') {
1948
1949                    var cookiebot_consent_category = options.gdpr['cookiebot_' + pixel + '_consent_category'];
1950
1951                    if (options.gdpr[pixel + '_prior_consent_enabled']) {
1952                        if (Cookiebot.consented === true || Cookiebot.consent[cookiebot_consent_category]) {
1953                            return true;
1954                        }
1955                    } else {
1956                        if (Cookiebot.consent[cookiebot_consent_category]) {
1957                            return true;
1958                        }
1959                    }
1960
1961                    return false;
1962
1963                }
1964
1965                /**
1966                 * Cookie Notice
1967                 */
1968                if (options.gdpr.cookie_notice_integration_enabled && typeof cnArgs !== 'undefined') {
1969
1970                    var cn_cookie = Cookies.get(cnArgs.cookieName);
1971
1972                    if (options.gdpr[pixel + '_prior_consent_enabled']) {
1973                        if (typeof cn_cookie === 'undefined' || cn_cookie === 'true') {
1974                            return true;
1975                        }
1976                    } else {
1977                        if (cn_cookie === 'true') {
1978                            return true;
1979                        }
1980                    }
1981
1982                    return false;
1983
1984                }
1985
1986                /**
1987                 * Cookie Law Info
1988                 */
1989                if (options.gdpr.cookie_law_info_integration_enabled) {
1990                    var cli_cookie = Cookies.get('cookieyes-consent') ?? Cookies.get('wt_consent') ?? Cookies.get('viewed_cookie_policy');
1991                    if (options.gdpr[pixel + '_prior_consent_enabled']) {
1992                        if (typeof cli_cookie === 'undefined') return true;
1993                        if (
1994                            cli_cookie &&
1995                            (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent'))
1996                        ) {
1997                            if (getCookieYes('analytics') === 'yes') {
1998                                return true;
1999                            }
2000                        } else if (cli_cookie && cli_cookie === Cookies.get('viewed_cookie_policy')) {
2001                            if (Cookies.get('viewed_cookie_policy') === 'yes') {
2002                                return true;
2003                            }
2004                        }
2005                    } else {
2006                        if (
2007                            cli_cookie &&
2008                            (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent'))
2009                        ) {
2010                            if (getCookieYes('analytics') === 'yes') {
2011                                return true;
2012                            }
2013                        } else if (cli_cookie && cli_cookie === Cookies.get('viewed_cookie_policy')) {
2014                            if (Cookies.get('viewed_cookie_policy') === 'yes') {
2015                                return true;
2016                            }
2017                        }
2018                    }
2019                    return false;
2020                }
2021
2022                return true;
2023
2024            },
2025
2026            setupGdprCallbacks: function () {
2027                /**
2028                 * ConsentMagic
2029                 */
2030                if (options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined") {
2031                    let test_prefix = CS_Data.test_prefix,
2032                        cs_refresh_after_consent = false,
2033                        substring = "cs_enabled_cookie_term";
2034
2035                    if (CS_Data.cs_refresh_after_consent == 1) {
2036                        cs_refresh_after_consent = CS_Data.cs_refresh_after_consent;
2037                    }
2038
2039                    let consent_actions = function () {
2040                        let theCookies = document.cookie.split( ';' );
2041
2042                        let consent = {
2043                            facebook: true,
2044                            ga: true,
2045                            google_ads: true,
2046                            tiktok: true,
2047                            bing: true,
2048                            pinterest: true,
2049                            gtm: true,
2050	                        reddit: true,
2051                        };
2052
2053                        for (let i = 1 ; i <= theCookies.length; i++) {
2054                            if (theCookies[i-1].indexOf(substring) !== -1) {
2055                                let categoryCookie = theCookies[i-1].replace('cs_enabled_cookie_term'+test_prefix+'_','');
2056                                categoryCookie = Number(categoryCookie.replace(/\D+/g,""));
2057                                let cs_cookie_val = Cookies.get('cs_enabled_cookie_term'+test_prefix+'_'+categoryCookie);
2058                                if(cs_cookie_val == 'yes') {
2059                                    if ( ( categoryCookie === CS_Data.cs_script_cat.facebook ) || Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) ) {
2060                                        Facebook.loadPixel();
2061                                    }
2062
2063                                    if ( categoryCookie === CS_Data.cs_script_cat.bing || Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) ) {
2064                                        Bing.loadPixel();
2065                                    }
2066
2067                                    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)
2067) {
2068
2069                                        Analytics.loadPixel();
2070                                    }
2071
2072                                    if (categoryCookie === CS_Data.cs_script_cat.pinterest) {
2073                                        Pinterest.loadPixel();
2074                                    }
2075
2076	                                if ( ( categoryCookie === CS_Data.cs_script_cat.reddit ) || Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) ) {
2077		                                Reddit.loadPixel();
2078	                                }
2079
2080	                                if ( ( categoryCookie === CS_Data.cs_script_cat.openai ) || Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) ) {
2081		                                OpenAI.loadPixel();
2082	                                }
2083                                } else {
2084                                    if ( ( categoryCookie === CS_Data.cs_script_cat.facebook ) && ! Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) ) {
2085                                        Facebook.disable();
2086                                        consent.facebook = false;
2087                                    }
2088
2089                                    if ( categoryCookie === CS_Data.cs_script_cat.bing && ! Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) ) {
2090                                        Bing.disable();
2091                                        consent.bing = false;
2092                                    }
2093                                    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)) {
2094                                        Analytics.disable();
2095                                        consent.ga = false;
2096                                        consent.gtm = false;
2097                                    }
2098
2099                                    if (categoryCookie === CS_Data.cs_script_cat.pinterest) {
2100                                        Pinterest.disable();
2101                                        consent.pinterest = false;
2102                                    }
2103
2104	                                if ( ( categoryCookie === CS_Data.cs_script_cat.reddit ) && ! Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) ) {
2105		                                Reddit.disable();
2106		                                consent.reddit = false;
2107	                                }
2108
2109	                                if ( ( categoryCookie === CS_Data.cs_script_cat.openai ) && ! Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) ) {
2110		                                OpenAI.disable();
2111		                                consent.openai = false;
2112	                                }
2113
2114                                    if ( categoryCookie === CS_Data.cs_script_cat?.openai ) {
2115                                        OpenAI.disable();
2116                                        consent.openai = false;
2117                                    }
2118                                }
2119                                if (Cookies.get('cs_enabled_advanced_matching') == 'yes') {
2120                                    Facebook.loadPixel();
2121                                }
2122                            }
2123                        }
2124                        Utils.setupGDPRData( consent );
2125                    }
2126
2127                    if (!cs_refresh_after_consent) {
2128                        consent_actions();
2129
2130                        $(document).on('click','.cs_action_btn',function(e) {
2131                            e.preventDefault();
2132
2133                            let consent = {
2134                                facebook: true,
2135                                ga: true,
2136                                bing: true,
2137                                pinterest: true,
2138                                gtm: true,
2139	                            reddit: true,
2140                            };
2141
2142                            let elm = $(this),
2143                                button_action = elm.attr('data-cs_action');
2144
2145                            if(button_action === 'allow_all') {
2146                                Facebook.loadPixel();
2147                                Bing.loadPixel();
2148                                Analytics.loadPixel();
2149                                Pinterest.loadPixel();
2150	                            Reddit.loadPixel();
2151
2152                                consent.facebook = true;
2153                                consent.bing = true;
2154                                consent.ga = true;
2155                                consent.pinterest = true;
2156	                            consent.reddit = true;
2157                                consent.gtm = true;
2158
2159                                Utils.setupGDPRData( consent );
2160                            } else if ( button_action === 'disable_all' ) {
2161                                if ( ! Utils.csModeEnabled( CS_Data.cs_meta_ldu_mode ) ) {
2162                                    Facebook.disable();
2163                                    consent.facebook = false;
2164                                }
2165
2166                                if ( ! Utils.csModeEnabled( CS_Data.cs_bing_consent_mode?.ad_storage?.enabled ) ) {
2167                                    Bing.disable();
2168                                    consent.bing = false;
2169                                }
2170
2171                                if ( CS_Data.cs_google_analytics_consent_mode == 0 || typeof CS_Data.cs_google_analytics_consent_mode == "undefined" ) {
2172                                    Analytics.disable();
2173                                    consent.ga = false;
2174                                    consent.gtm = false;
2175                                }
2176
2177	                            if ( ! Utils.csModeEnabled( CS_Data.cs_reddit_ldu_mode ) ) {
2178		                            Reddit.disable();
2179		                            consent.reddit = false;
2180	                            }
2181
2182                                Pinterest.disable();
2183                                consent.pinterest = false;
2184                                OpenAI.disable();
2185                                consent.openai = false;
2186                                Utils.setupGDPRData( consent );
2187                            } else if ( button_action === 'cs_confirm' ) {
2188                                consent_actions();
2189                            }
2190                        });
2191                    }
2192                }
2193                /**
2194                 * Real Cookie Banner
2195                 */
2196                if(options.gdpr.real_cookie_banner_integration_enabled) {
2197                    let consentApi = window.consentApi;
2198                    if (consentApi) {
2199                        consentApi.consent("http", "_ga", "*")
2200                            .then(Analytics.loadPixel.bind(Analytics), Analytics.disable.bind(Analytics));
2201                        consentApi.consent("http", "_fbp", "*")
2202                            .then(Facebook.loadPixel.bind(Facebook), Facebook.disable.bind(Facebook));
2203                        consentApi.consent("http", "_pinterest_sess", ".pinterest.com")
2204                            .then(Pinterest.loadPixel.bind(Pinterest), Pinterest.disable.bind(Pinterest));
2205                        consentApi.consent("http", "_uetsid", "*")
2206                            .then(Bing.loadPixel.bind(Bing), Bing.disable.bind(Bing));
2207
2208                        let consent = {
2209                            facebook: true,
2210                            ga: true,
2211                            bing: true,
2212                            pinterest: true,
2213                            gtm: true,
2214                        };
2215
2216                        if (!consentApi.consentSync("http", "_ga", "*").cookieOptIn) {
2217                            consent.ga = false;
2218                            consent.gtm = false;
2219                        }
2220                        if (!consentApi.consentSync("http", "_fbp", "*").cookieOptIn) {
2221                            consent.facebook = false;
2222                        }
2223                        if (!consentApi.consentSync("http", "_pinterest_sess", ".pinterest.com").cookieOptIn) {
2224                            consent.pinterest = false;
2225                        }
2226                        if (!consentApi.consentSync("http", "_uetsid", "*").cookieOptIn) {
2227                            consent.bing = false;
2228                        }
2229                        Utils.setupGDPRData( consent );
2230                    }
2231                }
2232                /**
2233                 * Cookiebot
2234                 */
2235                if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') {
2236
2237                    window.addEventListener("CookiebotOnConsentReady", function() {
2238
2239                        let consent = {
2240                            facebook: true,
2241                            ga: true,
2242                            bing: true,
2243                            pinterest: true,
2244                            gtm: true,
2245                        };
2246
2247                        Utils.manageCookies();
2248                        if (Cookiebot.consent.marketing) {
2249                            Facebook.loadPixel();
2250                            Bing.loadPixel();
2251                            Pinterest.loadPixel();
2252
2253                            consent.facebook = true;
2254                            consent.bing = true;
2255                            consent.pinterest = true;
2256                        }
2257                        if (Cookiebot.consent.statistics) {
2258                            Analytics.loadPixel();
2259
2260                            consent.ga = true;
2261                            consent.gtm = true;
2262                        }
2263                        if (!Cookiebot.consent.marketing) {
2264                            Facebook.disable();
2265                            Pinterest.disable();
2266                            Bing.disable();
2267
2268                            consent.facebook = false;
2269                            consent.bing = false;
2270                            consent.pinterest = false;
2271                        }
2272                        if (!Cookiebot.consent.statistics) {
2273                            Analytics.disable();
2274
2275                            consent.ga = false;
2276                            consent.gtm = false;
2277                        }
2278
2279                        Utils.setupGDPRData( consent );
2280                    });
2281
2282                }
2283
2284                /**
2285                 * Cookie Notice
2286                 */
2287                if (options.gdpr.cookie_notice_integration_enabled) {
2288
2289                    $(document).onFirst('click', '.cn-set-cookie', function () {
2290
2291                        let consent = {};
2292
2293                        if ($(this).data('cookie-set') === 'accept') {
2294                            Facebook.loadPixel();
2295                            Analytics.loadPixel();
2296                            Pinterest.loadPixel();
2297                            Bing.loadPixel();
2298
2299                            consent = {
2300                                facebook: true,
2301                                ga: true,
2302                                bing: true,
2303                                pinterest: true,
2304                                gtm: true,
2305                            };
2306                        } else {
2307                            Facebook.disable();
2308                            Analytics.disable();
2309                            Pinterest.disable();
2310                            Bing.disable();
2311
2312                            consent = {
2313                                facebook: false,
2314                                ga: false,
2315                                bing: false,
2316                                pinterest: false,
2317                                gtm: false,
2318                            };
2319                        }
2320
2321                        Utils.setupGDPRData( consent );
2322                    });
2323
2324                    $(document).onFirst('click', '.cn-revoke-cookie', function () {
2325                        Facebook.disable();
2326                        Analytics.disable();
2327                        Pinterest.disable();
2328                        Bing.disable();
2329
2330                        let consent = {
2331                            facebook: false,
2332                            ga: false,
2333                            bing: false,
2334                            pinterest: false,
2335                            gtm: false,
2336                        };
2337                        Utils.setupGDPRData( consent );
2338
2339                    });
2340
2341                }
2342
2343                /**
2344                 * Cookie Law Info
2345                 */
2346                if (options.gdpr.cookie_law_info_integration_enabled) {
2347
2348                    $(document).onFirst('click', '#wt-cli-accept-all-btn,#cookie_action_close_header, .cky-btn-accept', function () {
2349                        setTimeout(function (){
2350                            let cli_cookie = Cookies.get('cookieyes-consent') ?? Cookies.get('wt_consent') ?? Cookies.get('viewed_cookie_policy');
2351                            if (typeof cli_cookie !== 'undefined') {
2352                                if (cli_cookie === Cookies.get('cookieyes-consent') || cli_cookie === Cookies.get('wt_consent')) {
2353                                    if (getCookieYes('analytics') === 'yes') {
2354                                        Utils.manageCookies();
2355                                    }
2356                                } else if (cli_cookie === Cookies.get('viewed_cookie_policy') && cli_cookie == 'yes') {
2357                                    Utils.manageCookies();
2358                                }
2359                            }
2360                        },1000)
2361                        Facebook.loadPixel();
2362                        Analytics.loadPixel();
2363                        Pinterest.loadPixel();
2364                        Bing.loadPixel();
2365
2366                        let consent = {
2367                            facebook: true,
2368                            ga: true,
2369                            bing: true,
2370                            pinterest: true,
2371                            gtm: true,
2372                        };
2373                        Utils.setupGDPRData( consent );
2374                    });
2375
2376                    $(document).onFirst('click', '#cookie_action_close_header_reject, .cky-btn-reject', function () {
2377                        Facebook.disable();
2378                        Analytics.disable();
2379                        Pinterest.disable();
2380                        Bing.disable();
2381
2382                        let consent = {
2383                            facebook: false,
2384                            ga: false,
2385                            bing: false,
2386                            pinterest: false,
2387                            gtm: false,
2388                        };
2389                        Utils.setupGDPRData( consent );
2390                    });
2391
2392                }
2393
2394            },
2395
2396            setupGDPRData: function ( consent ) {
2397                consent = window.btoa( JSON.stringify( consent ) );
2398                Cookies.set( 'pys_consent', consent, { expires: 365, path: '/', domain: cookieDomain() } );
2399            },
2400
2401            /**
2402             * DOWNLOAD DOCS
2403             */
2404
2405            getLinkExtension: function (link) {
2406
2407                // Remove anchor, query string and everything before last slash
2408                link = link.substring(0, (link.indexOf("#") === -1) ? link.length : link.indexOf("#"));
2409                link = link.substring(0, (link.indexOf("?") === -1) ? link.length : link.indexOf("?"));
2410                link = link.substring(link.lastIndexOf("/") + 1, link.length);
2411
2412                // If there's a period left in the URL, then there's a extension
2413                if (link.length > 0 && link.indexOf('.') !== -1) {
2414                    link = link.substring(link.indexOf(".") + 1); // Remove everything but what's after the first period
2415                    return link;
2416                } else {
2417                    return "";
2418                }
2419            },
2420
2421            getLinkFilename: function (link) {
2422
2423                // Remove anchor, query string and everything before last slash
2424                link = link.substring(0, (link.indexOf("#") === -1) ? link.length : link.indexOf("#"));
2425                link = link.substring(0, (link.indexOf("?") === -1) ? link.length : link.indexOf("?"));
2426                link = link.substring(link.lastIndexOf("/") + 1, link.length);
2427
2428                // If there's a period left in the URL, then there's a extension
2429                if (link.length > 0 && link.indexOf('.') !== -1) {
2430                    return link;
2431                } else {
2432                    return "";
2433                }
2434            },
2435            /**
2436             * Enrich
2437             */
2438            isCheckoutPage: function () {
2439                return $('body').hasClass('woocommerce-checkout') || document.querySelector('.woocommerce-checkout') ||
2440                    $('body').hasClass('edd-checkout');
2441            },
2442            addCheckoutFields : function() {
2443                var utm = "";
2444                var utms = getUTMs()
2445
2446                $.each(utmTerms, function (index, name) {
2447                    if(index > 0) {
2448                        utm+="|";
2449                    }
2450                    utm+=name+":"+utms[name];
2451                });
2452                var utmIdList = "";
2453                var utmsIds = getUTMId()
2454                $.each(utmId, function (index, name) {
2455                    if(index > 0) {
2456                        utmIdList+="|";
2457                    }
2458                    utmIdList+=name+":"+utmsIds[name];
2459                });
2460                var utmIdListLast = "";
2461                var utmsIdsLast = getUTMId(true)
2462                $.each(utmId, function (index, name) {
2463                    if(index > 0) {
2464                        utmIdListLast+="|";
2465                    }
2466                    utmIdListLast+=name+":"+utmsIdsLast[name];
2467                });
2468
2469
2470                var utmLast = "";
2471                var utmsLast = getUTMs(true)
2472                $.each(utmTerms, function (index, name) {
2473                    if(index > 0) {
2474                        utmLast+="|";
2475                    }
2476                    utmLast+=name+":"+utmsLast[name];
2477                });
2478
2479                var dateTime = getDateTime();
2480                // No current-URL fallback here: an empty field lets
2481                // EnrichOrder::getRequestValue() use its own server-side default,
2482                // which is the visit data from the PHP session — more reliable than
2483                // the checkout URL. The checkout page is never HTML-cached, so
2484                // reading the session there is safe.
2485                var landing = getLandingPageValue(false);
2486                var lastLanding = getLandingPageValue(false);
2487                var trafic = getTrafficSourceValue();
2488                var lastTrafic = getTrafficSourceValue();
2489
2490                var $form = null;
2491                if($('body').hasClass('woocommerce-checkout')) {
2492                    $form = $("form.woocommerce-checkout");
2493                } else {
2494                    $form = $("#edd_purchase_form");
2495                }
2496                var inputs = {'pys_utm':utm,
2497                    'pys_utm_id':utmIdList,
2498                    'pys_browser_time':dateTime.join("|"),
2499                    'pys_landing':landing,
2500                    'pys_source':trafic,
2501                    'pys_order_type': $(".wcf-optin-form").length > 0 ? "wcf-optin" : "normal",
2502
2503                    'last_pys_landing':lastLanding,
2504                    'last_pys_source':lastTrafic,
2505                    'last_pys_utm':utmLast,
2506                    'last_pys_utm_id':utmIdListLast,
2507                }
2508
2509                Object.keys(inputs).forEach(function(key,index) {
2510                    $form.append("<input type='hidden' name='"+key+"' value='"+inputs[key]+"' /> ");
2511                });
2512
2513
2514            },
2515            getAdvancedFormData: function () {
2516                let dataStr = Cookies.get("pys_advanced_form_data");
2517                if(dataStr === undefined) {
2518                    return {'first_name':"",'last_name':"",'email':"",'phone':""};
2519                } else {
2520                    return JSON.parse(dataStr);
2521                }
2522            },
2523
2524            /**
2525             * Advanced Matching that the server is no longer allowed to render.
2526             *
2527             * Page HTML can be served out of a shared cache, so the server
2528             * leaves the visitor's own email, phone and name out of pysOptions.
2529             * The values are on this device in pys_advanced_form_data, so the
2530             * browser puts them back just before each pixel initialises.
2531             * Normalisation mirrors the PHP side field for field -- if one
2532             * moves, both move.
2533             */
2534            AdvancedMatching: {
2535
2536                normalizeEmail: function (value) {
2537                    if (typeof value !== 'string') {
2538                        return '';
2539                    }
2540                    var email = value.trim().toLowerCase();
2541                    if (email.length === 0 || email.length > 254) {
2542                        return '';
2543                    }
2544                    return /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/.test(email) ? email : '';
2545                },
2546
2547                normalizePhone: function (value) {
2548                    if (typeof value !== 'string') {
2549                        return '';
2550                    }
2551                    var digits = value.replace(/[^0-9]/g, '');
2552                    return digits.length > 0 ? digits : '';
2553                },
2554
2555                normalizeName: function (value) {
2556                    if (typeof value !== 'string' || value.length > 1024) {
2557                        return '';
2558                    }
2559                    return value.trim().toLowerCase();
2560                },
2561
2562                /**
2563                 * The identity this device carries, normalised and possibly
2564                 * all-empty. Never throws when the cookie is absent or corrupt.
2565                 */
2566                cookieIdentity: function () {
2567                    var data;
2568                    try {
2569                        data = Utils.getAdvancedFormData() || {};
2570                    } catch (e) {
2571                        data = {};
2572                    }
2573                    return {
2574                        email: this.normalizeEmail(data.email || ''),
2575                        phone: this.normalizePhone(data.phone || ''),
2576                        first_name: this.normalizeName(data.first_name || ''),
2577                        last_name: this.normalizeName(data.last_name || '')
2578                    };
2579                },
2580
2581                /**
2582                 * @param {object} target      The pixel's advanced matching object, edited in place.
2583                 * @param {object} mapping     {targetKey: {field, hash}}
2584                 * @param {object} identity    Optional; defaults to cookieIdentity().
2585                 * @param {string} persistency Optional; defaults to options.data_persistency.
2586                 */
2587                merge: function (target, mapping, identity, persistency) {
2588
2589                    if (!target || !mapping) {
2590                        return target;
2591                    }
2592
2593                    identity = identity || this.cookieIdentity();
2594
2595                    if (typeof persistency === 'undefined') {
2596                        persistency = typeof options !== 'undefined' ? options.data_persistency : '';
2597                    }
2598
2599                    var keepData = persistency === 'keep_data';
2600
2601                    Object.keys(mapping).forEach(function (targetKey) {
2602
2603                        var rule = mapping[targetKey];
2604                        var value = identity[rule.field];
2605
2606                        if (typeof value !== 'string' || value.length === 0) {
2607                            return;
2608                        }
2609
2610                        var hasServerValue = typeof target[targetKey] === 'string'
2611                            && target[targetKey].length > 0;
2612
2613                        if (hasServerValue && !keepData) {
2614                            return;
2615                        }
2616
2617                        if (rule.hash) {
2618                            if (typeof sha256 !== 'function') {
2619                                return;
2620                            }
2621                            target[targetKey] = sha256(value);
2622                        } else {
2623                            target[targetKey] = value;
2624                        }
2625                    });
2626
2627                    return target;
2628                }
2629            },
2630
2631            getFormFilledData: function ( event ) {
2632                // First, resolve static/dynamic parameters
2633                if (event.params && Object.keys(event.params).length > 0) {
2634                    Object.entries(event.params).forEach(([key, value]) => {
2635                        // Resolve parameter value (static or dynamic)
2636                        const resolvedValue = resolveParamValue(value, key);
2637                        if (resolvedValue !== null) {
2638                            event.params[key] = resolvedValue;
2639                        }
2640                        else {
2641                            delete event.params[key];
2642                        }
2643                    });
2644                }
2645                return event;
2646            }
2647        };
2648
2649    }(options);
2650
2651    var Facebook = function (options) {
2652
2653
2654        var defaultEventTypes = [
2655            'PageView',
2656            'ViewContent',
2657            'Search',
2658            'AddToCart',
2659            'AddToWishlist',
2660            'InitiateCheckout',
2661            'AddPaymentInfo',
2662            'Purchase',
2663            'Lead',
2664
2665            'Subscribe',
2666            'CustomizeProduct',
2667            'FindLocation',
2668            'StartTrial',
2669            'SubmitApplication',
2670            'Schedule',
2671            'Contact',
2672            'Donate'
2673        ];
2674
2675        var initialized = false;
2676        /**
2677         * The `version.subdomainIndex.` prefix Meta's format calls for.
2678         * subdomainIndex is how many labels the cookie's domain has below
2679         * the public suffix (com=0, example.com=1, www.example.com=2), so it
2680         * must track cookieDomain()/probeCookieDomain() rather than a
2681         * hardcoded `fb.1.`. Only new values are affected — an existing
2682         * cookie is returned untouched.
2683         */
2684        var fbValuePrefix = function (){
2685            var resolved = cookieDomain();
2686            var scope = resolved ? String(resolved).replace(/^\./, '') : '';
2687            if (!scope) {
2688                try { scope = String(location.hostname || ''); } catch (e) { scope = ''; }
2689            }
2690            var dots = scope ? scope.split('.').length - 1 : 0;
2691            return 'fb.' + dots + '.' + Date.now() + '.';
2692        };
2693        var genereateFbp = function (){
2694            return !Cookies.get('_fbp') ? fbValuePrefix()+Math.floor(1000000000 + Math.random() * 9000000000) : Cookies.get('_fbp');
2695        };
2696        var genereateFbc = function (){
2697            return getUrlParameter('fbclid') ? fbValuePrefix()+getUrlParameter('fbclid') : ''
2698        };
2699        // fire server side event gdpr plugin installed
2700        var isApiDisabled = options.gdpr.all_disabled_by_api ||
2701            options.gdpr.facebook_disabled_by_api ||
2702            options.gdpr.cookiebot_integration_enabled ||
2703            options.gdpr.consent_magic_integration_enabled ||
2704            options.gdpr.cookie_notice_integration_enabled ||
2705            options.gdpr.cookie_law_info_integration_enabled;
2706
2707        /**
2708         *
2709         * @param allData
2710         * @param params
2711         * @returns {string | null}
2712         */
2713        function sendFbServerEvent(allData,name,params) {
2714            let eventId = null;
2715            // sendEventId is true when the token-based Conversion API is on OR
2716            // when Meta-Enabled CAPI is on: in both cases Meta receives a second,
2717            // API-side copy of this event and dedupes it by eventID.
2718            // Only the token-based API needs an actual request from us, so the
2719            // hook/AJAX branches below stay gated on serverApiEnabled.
2720            if(options.facebook.sendEventId) {
2721
2722                var isAtcFromHook = options.woo
2723                    && options.woo.addToCartCatchMethod === "add_cart_hook";
2724
2725                if(options.facebook.serverApiEnabled && allData.e_id === "woo_remove_from_cart") {// server event will sended from hook
2726                    Facebook.updateEventId(allData.name);
2727                    allData.eventID = Facebook.getEventId(allData.name);
2728                } else if(options.facebook.serverApiEnabled && allData.e_id === "woo_add_to_cart_on_button_click" && isAtcFromHook) {
2729                    // Server event was already sent from the PHP hook
2730                    // (trackWooAddToCartEvent -> FacebookServer()->sendEventsNow).
2731                    // Keep the shared eventID PHP put on the event so the browser
2732                    // pixel dedupes against that server copy — do NOT re-send here,
2733                    // and do NOT regenerate the eventID.
2734                } else {
2735                    // send event from server if they was bloc by gdpr or need send with delay
2736                    allData.eventID = Utils.generateUniqueId(allData);
2737
2738                    if(Cookies.get('_fbp')){
2739                        params._fbp = Cookies.get('_fbp');
2740                    }
2741                    if(Cookies.get('_fbc')){
2742                        params._fbc = Cookies.get('_fbc');
2743                    }
2744
2745                    if( options.facebook.serverApiEnabled && ( options.ajaxForServerEvent || isApiDisabled ) ){
2746                        var json = {
2747                            action: 'pys_api_event',
2748                            pixel: 'facebook',
2749                            event: name,
2750                            event_slug: allData.e_id,
2751                            data:params,
2752                            ids:options.facebook.pixelIds,
2753                            eventID:allData.eventID,
2754                            url:window.location.href,
2755                            ajax_event:options.ajax_event
2756                        };
2757                        if(allData.hasOwnProperty('woo_order')) {
2758                            json['woo_order'] = allData.woo_order;
2759                        }
2760                        if(allData.hasOwnProperty('edd_order')) {
2761                            json['edd_order'] = allData.edd_order;
2762                        }
2763                        if(allData.hasOwnProperty('order_key') && allData.order_key) {
2764                            json['order_key'] = allData.order_key;
2765                        }
2766                        if (pageReferrer) {
2767                            json.referrer_url = pageReferrer;
2768                        }
2769
2770                        if (allData.e_id === "automatic_event_internal_link" || allData.e_id === "automatic_event_outbound_link") {
2771                            setTimeout(() => Utils.sendServerAjaxRequest(options.ajaxUrl, json), 500);
2772                        } else if (allData.type != 'static') {
2773                            Utils.sendServerAjaxRequest(options.ajaxUrl, json);
2774                        }
2775
2776                        if ( ( allData.type == 'static' && options.ajaxForServerStaticEvent ) || ( allData.hasOwnProperty( 'ajaxFire' ) && allData.ajaxFire ) ) {
2777                            Utils.sendServerAjaxRequest( options.ajaxUrl, json );
2778                        }
2779                    }
2780                }
2781                delete params._fbp;
2782                delete params._fbc;
2783                eventId = allData.eventID
2784            }
2785            return eventId;
2786        }
2787
2788        function fireEvent(name, allData) {
2789
2790            if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'facebook')) {
2791                return;
2792            }
2793
2794            var actionType = defaultEventTypes.includes(name) ? 'track' : 'trackCustom';
2795            var data = allData.params;
2796            var params = {};
2797            var arg = {};
2798            Utils.copyProperties(data, params);
2799            let eventId = sendFbServerEvent(allData,name,params)
2800
2801
2802            if("hCR" === name) {
2803                return;
2804            }
2805
2806            if (options.debug) {
2807                console.log('[Facebook] ' + name, params,"eventID",eventId);
2808            }
2809
2810            if(eventId != null) {
2811                arg.eventID = eventId;
2812            }
2813
2814            fbq(actionType, name, params, arg);
2815
2816        }
2817
2818        /**
2819         * Public API
2820         */
2821        return {
2822            tag: function() {
2823                return "facebook";
2824            },
2825            isEnabled: function () {
2826                return options.hasOwnProperty('facebook');
2827            },
2828
2829            disable: function () {
2830                initialized = false;
2831            },
2832            advancedMatching: function () {
2833                if(options.facebook.advancedMatchingEnabled) {
2834                    let advancedMatching = {};
2835                    if(Object.keys(options.facebook.advancedMatching).length > 0) {
2836                        advancedMatching = options.facebook.advancedMatching;
2837                    }
2838
2839                    Utils.AdvancedMatching.merge(advancedMatching, {
2840                        em: { field: 'email',      hash: false },
2841                        ph: { field: 'phone',      hash: false },
2842                        fn: { field: 'first_name', hash: false },
2843                        ln: { field: 'last_name',  hash: false }
2844                    });
2845
2846                    if(!advancedMatching.hasOwnProperty("external_id")){
2847                        if (Cookies.get('pbid') || (options.hasOwnProperty('pbid') && options.pbid)) {
2848                            advancedMatching["external_id"] = Cookies.get('pbid') ? Cookies.get('pbid') : options.pbid;
2849                        }
2850                    }
2851                    else if(advancedMatching.external_id != Cookies.get('pbid'))
2852                    {
2853                        advancedMatching["external_id"] = Cookies.get('pbid') ? Cookies.get('pbid') : advancedMatching.external_id;
2854                    }
2855                    if(Object.keys(advancedMatching).length > 0) {
2856                        return advancedMatching;
2857                    }
2858                }
2859                return false
2860            },
2861            /**
2862             * Load pixel's JS
2863             */
2864            loadPixel: function () {
2865
2866                if (initialized || !this.isEnabled() || !Utils.consentGiven('facebook')) {
2867                    return;
2868                }
2869
2870                // First visit: wait (briefly) for this browser's external_id, so
2871                // the very first PageView is not sent under the inline value.
2872                if (Utils.holdForIdentity(function () { Facebook.loadPixel(); })) {
2873                    return;
2874                }
2875
2876                !function (f, b, e, v, n, t, s) {
2877                    if (f.fbq) return;
2878                    n = f.fbq = function () {
2879                        n.callMethod ?
2880                            n.callMethod.apply(n, arguments) : n.queue.push(arguments)
2881                    };
2882                    if (!f._fbq) f._fbq = n;
2883                    n.push = n;
2884                    n.loaded = !0;
2885                    n.version = '2.0';
2886                    n.agent = 'dvpixelyoursite';
2887                    n.queue = [];
2888                    t = b.createElement(e);
2889                    t.async = !0;
2890                    t.src = v;
2891                    s = b.getElementsByTagName(e)[0];
2892                    s.parentNode.insertBefore(t, s)
2893                }(window,
2894                    document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');
2895
2896                // Report the cookie domain here too, not just from
2897                // manageCookies(): this runs under the `facebook` pixel's own
2898                // consent, which can differ from and outlast the `pys`
2899                // script's consent (e.g. under ConsentMagic's separate
2900                // category assignments), so PHP might otherwise never learn
2901                // the scope for a visitor who only accepted this pixel.
2902                //
2903                // `pys_cd` only ever appears here alongside a cookie we are
2904                // already writing at that scope, so it can never be the first
2905                // cookie a visitor receives. disabled_all_cookie is still
2906                // honoured, matching the call in manageCookies().
2907                if (!options.cookie || !options.cookie.disabled_all_cookie) {
2908                    reportCookieDomain(cookieDomain());
2909                }
2910
2911                let expires = parseInt(options.cookie_duration);
2912                if(!Cookies.get('_fbp')) {
2913                    Cookies.set('_fbp',genereateFbp(),  { expires: expires,path: '/',domain: cookieDomain() });
2914                }
2915
2916                if(getUrlParameter('fbclid')) {
2917                    Cookies.set('_fbc',genereateFbc(),  { expires: expires,path: '/',domain: cookieDomain() });
2918                }
2919
2920                // Now that Facebook consent is confirmed, mirror the session
2921                // entry referrer (if any) into the session cookie so PHP can
2922                // use it as a fallback for orphan server-only events.
2923                getOrInitSessionEntryReferrer(true);
2924
2925                // Refresh the per-page event referrer cookie so checkout
2926                // hooks can snapshot the buyer's prior page.
2927                writeEventReferrerCookie();
2928
2929                // initialize pixel
2930                options.facebook.pixelIds.forEach(function (pixelId) {
2931                    if (options.facebook.removeMetadata) {
2932                        fbq('set', 'autoConfig', false, pixelId);
2933                    }
2934                    let advancedMatching = Facebook.advancedMatching();
2935
2936                    if ( +options.facebook.meta_ldu === 1  ) {
2937                        fbq( 'dataProcessingOptions', [ 'LDU' ], 0, 0 );
2938                    }
2939
2940                    // Meta-Enabled Conversion API: let Meta build its own API
2941                    // events out of this pixel's data. Must run right before init.
2942                    if (Utils.isMetaEnabledCapiPixel(pixelId)) {
2943                        fbq('optinMetaEnabledCapi', pixelId);
2944                    }
2945
2946                    if (options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined") {
2947                        if(!advancedMatching) {
2948                            fbq('init', pixelId);
2949                        } else {
2950                            var test_prefix = CS_Data.test_prefix;
2951                            var cs_advanced_matching = Cookies.get('cs_enabled_advanced_matching'+test_prefix);
2952                            if (jQuery('#cs_enabled_advanced_matching'+test_prefix).length > 0) {
2953                                if (cs_advanced_matching == 'yes') {
2954                                    fbq('init', pixelId, advancedMatching);
2955                                } else {
2956                                    fbq('init', pixelId);
2957                                }
2958                            } else {
2959                                fbq('init', pixelId, advancedMatching);
2960                            }
2961                        }
2962                    } else {
2963                        if(!advancedMatching) {
2964                            fbq('init', pixelId);
2965                        }  else {
2966                            fbq('init', pixelId, advancedMatching);
2967                        }
2968                    }
2969                });
2970
2971                initialized = true;
2972
2973                Utils.fireStaticEvents('facebook');
2974
2975            },
2976
2977            fireEvent: function (name, data) {
2978
2979                if (!initialized || !this.isEnabled()) {
2980                    return false;
2981                }
2982
2983                data.delay = data.delay || 0;
2984                data.params = data.params || {};
2985
2986                if (data.delay === 0) {
2987
2988                    fireEvent(name, data);
2989
2990                } else {
2991
2992                    setTimeout(function (name, params) {
2993                        fireEvent(name, params);
2994                    }, data.delay * 1000, name, data);
2995
2996                }
2997
2998                return true;
2999
3000            },
3001
3002            onCommentEvent: function (event) {
3003                this.fireEvent(event.name, event);
3004            },
3005
3006            onDownloadEvent: function (event) {
3007                this.fireEvent(event.name, event);
3008            },
3009
3010            onFormEvent: function (event) {
3011
3012                this.fireEvent(event.name, event);
3013
3014            },
3015
3016            onWooAddToCartOnButtonEvent: function (product_id) {
3017
3018                if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3019                    return;
3020                var event = options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()];
3021                window.pysWooProductData = window.pysWooProductData || [];
3022                if (window.pysWooProductData.hasOwnProperty(product_id)) {
3023                    if (window.pysWooProductData[product_id].hasOwnProperty('facebook')) {
3024                        event = Utils.copyProperties(event, {})
3025                        Utils.copyProperties(window.pysWooProductData[product_id]['facebook'].params, event.params)
3026                        this.fireEvent(event.name, event);
3027                    }
3028                }
3029            },
3030
3031            onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, $form) {
3032
3033                window.pysWooProductData = window.pysWooProductData || [];
3034                if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3035                    return;
3036                var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
3037
3038                if (product_type === Utils.PRODUCT_VARIABLE && !options.facebook.wooVariableAsSimple) {
3039                    product_id = parseInt($form.find('input[name="variation_id"]').val());
3040                }
3041
3042                if (window.pysWooProductData.hasOwnProperty(product_id)) {
3043                    if (window.pysWooProductData[product_id].hasOwnProperty('facebook')) {
3044
3045
3046                        Utils.copyProperties(window.pysWooProductData[product_id]['facebook'].params, event.params);
3047
3048                        var groupValue = 0;
3049                        if(product_type === Utils.PRODUCT_GROUPED ) {
3050                            $form.find(".woocommerce-grouped-product-list .qty").each(function(index){
3051                                var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]","");
3052                                var quantity = parseInt($(this).val());
3053                                if(isNaN(quantity)) {
3054                                    quantity = 0;
3055                                }
3056                                var childItem = window.pysWooProductData[product_id]['facebook'].grouped[childId];
3057
3058                                if(quantity == 0) {
3059                                    event.params.content_ids.forEach(function(el,index,array) {
3060                                        if(el == childItem.content_id) {
3061                                            array.splice(index, 1);
3062                                        }
3063                                    });
3064                                }
3065
3066                                if(event.params.hasOwnProperty('contents')) {
3067                                    event.params.contents.forEach(function(el,index,array) {
3068                                        if(el.id == childItem.content_id) {
3069                                            if(quantity > 0){
3070                                                el.quantity = quantity;
3071                                            } else {
3072                                                array.splice(index, 1);
3073                                            }
3074                                        }
3075                                    });
3076                                }
3077
3078
3079                                groupValue += childItem.price * quantity;
3080                            });
3081                            if(groupValue == 0) return;
3081 // skip if no items selected
3082                        }
3083
3084                        // maybe customize value option
3085                        if (options.woo.addToCartOnButtonValueEnabled && options.woo.addToCartOnButtonValueOption !== 'global') {
3086
3087                            if(product_type === Utils.PRODUCT_GROUPED) {
3088                                event.params.value = groupValue;
3089                            } else if(product_type === Utils.PRODUCT_BUNDLE) {
3090                                var data = $(".bundle_form .bundle_data").data("bundle_form_data");
3091                                var items_sum = getBundlePriceOnSingleProduct(data);
3092                                event.params.value = (parseInt(data.base_price) + items_sum )* qty;
3093                            } else {
3094                                event.params.value = event.params.value * qty;
3095                            }
3096                        }
3097
3098                        // only when non Facebook for WooCommerce logic used
3099                        if (event.params.hasOwnProperty('contents') && product_type !== Utils.PRODUCT_GROUPED) {
3100                            event.params.contents[0].quantity = qty;
3101                        }
3102
3103                        this.fireEvent(event.name, event);
3104
3105                    }
3106                }
3107            },
3108
3109            onWooRemoveFromCartEvent: function (event) {
3110                this.fireEvent(event.name, event);
3111            },
3112
3113            onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {
3114
3115                if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3116                    return;
3117                var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]);
3118
3119                if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) {
3120
3121                    var index;
3122
3123                    if (price_index) {
3124                        index = download_id + '_' + price_index;
3125                    } else {
3126                        index = download_id;
3127                    }
3128
3129                    if (window.pysEddProductData[download_id].hasOwnProperty(index)) {
3130                        if (window.pysEddProductData[download_id][index].hasOwnProperty('facebook')) {
3131
3132
3133                            Utils.copyProperties(window.pysEddProductData[download_id][index]['facebook']["params"], event.params)
3134
3135                            // maybe customize value option
3136                            if (options.edd.addToCartOnButtonValueEnabled && options.edd.addToCartOnButtonValueOption !== 'global') {
3137                                event.params.value = event.params.value * qty;
3138                            }
3139
3140                            // update contents qty param
3141                            var contents = event.params.contents;
3142                            contents[0].quantity = qty;
3143                            event.params.contents = contents;
3144
3145                            this.fireEvent(event.name,event);
3146
3147                        }
3148                    }
3149
3150                }
3151
3152            },
3153
3154            onEddRemoveFromCartEvent: function (event) {
3155                this.fireEvent(event.name, event);
3156            },
3157            onPageScroll: function (event) {
3158                this.fireEvent(event.name, event);
3159            },
3160            onTime: function (event) {
3161                this.fireEvent(event.name, event);
3162            },
3163            initEventIdCookies: function (key) {
3164                var ids = {};
3165                ids[key] = pys_generate_token(36)
3166                Cookies.set('pys_fb_event_id', JSON.stringify(ids));
3167            },
3168            updateEventId:function(key) {
3169                var cooData = Cookies.get("pys_fb_event_id")
3170                if(cooData === undefined) {
3171                    this.initEventIdCookies(key);
3172                } else {
3173                    var data = JSON.parse(cooData);
3174                    data[key] = pys_generate_token(36);
3175                    Cookies.set('pys_fb_event_id', JSON.stringify(data) );
3176                }
3177            },
3178
3179            getEventId:function (key) {
3180                var data = Cookies.get("pys_fb_event_id");
3181                if(data === undefined) {
3182                    this.initEventIdCookies(key);
3183                    data = Cookies.get("pys_fb_event_id");
3184                }
3185                return JSON.parse(data)[key];
3186            },
3187
3188            sendFacebookRestAPIRequest: function(allData, name, params) {
3189                if (typeof window.pysFacebookRest === 'undefined' || !window.pysFacebookRest.restApiUrl) {
3190                    return;
3191                }
3192
3193                const restApiData = {
3194                    event: name,
3195                    data: JSON.stringify(params),
3196                    ids: allData.pixelIds || options.facebook.pixelIds,
3197                    eventID: allData.eventID,
3198                    woo_order: allData.woo_order || 0,
3199                    edd_order: allData.edd_order || 0,
3200                    order_key: allData.order_key || ''
3201                };
3202
3203                if (pageReferrer) {
3204                    restApiData.referrer_url = pageReferrer;
3205                }
3206
3207                // Variant A: send the actual page URL where the event happened
3208                // so the server sets event_source_url to this page, not the
3209                // REST endpoint. PHP applies enable_remove_source_url_params.
3210                restApiData.event_source_url = window.location.href;
3211
3212                // Try sendBeacon first
3213                if (navigator.sendBeacon) {
3214                    const formData = new FormData();
3215                    Object.keys(restApiData).forEach(key => {
3216                        if (Array.isArray(restApiData[key])) {
3217                            restApiData[key].forEach(value => {
3218                                formData.append(key + '[]', value);
3219                            });
3220                        } else {
3221                            formData.append(key, restApiData[key]);
3222                        }
3223                    });
3224
3225                    if (navigator.sendBeacon(window.pysFacebookRest.restApiUrl, formData)) {
3226                        return;
3227                    }
3228                }
3229
3230                // Fallback to fetch
3231                fetch(window.pysFacebookRest.restApiUrl, {
3232                    method: 'POST',
3233                    headers: {
3234                        'Content-Type': 'application/x-www-form-urlencoded',
3235                    },
3236                    body: new URLSearchParams(restApiData)
3237                }).catch(() => {
3238                    // Fallback to AJAX if fetch fails
3239                    this.sendAjaxFallback(allData, name, params);
3240                });
3241            },
3242
3243            sendAjaxFallback: function(allData, name, params) {
3244                var json = {
3245                    action: 'pys_api_event',
3246                    pixel: 'facebook',
3247                    event: name,
3248                    data: params,
3249                    ids: allData.pixelIds || options.facebook.pixelIds,
3250                    eventID: allData.eventID,
3251                    url: window.location.href,
3252                    ajax_event: options.ajax_event
3253                };
3254                if (allData.hasOwnProperty('woo_order')) {
3255                    json['woo_order'] = allData.woo_order;
3256                }
3257                if (allData.hasOwnProperty('edd_order')) {
3258                    json['edd_order'] = allData.edd_order;
3259                }
3260                if (allData.hasOwnProperty('order_key') && allData.order_key) {
3261                    json['order_key'] = allData.order_key;
3262                }
3263                if (pageReferrer) {
3264                    json.referrer_url = pageReferrer;
3265                }
3266
3267                Utils.sendServerAjaxRequest(options.ajaxUrl, json);
3268            },
3269        };
3270
3271    }(options);
3272
3273    var Analytics = function (options) {
3274
3275        var initialized = false;
3276
3277        /**
3278         * Fires event
3279         *
3280         * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/sending-data
3281         * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/events
3282         * @link: https://developers.google.com/gtagjs/reference/event
3283         * @link: https://developers.google.com/gtagjs/reference/parameter
3284         *
3285         * @link: https://developers.google.com/analytics/devguides/collection/gtagjs/custom-dim
3285s-mets
3286         *
3287         * @param name
3288         * @param data
3289         */
3290        function fireEvent(name, data) {
3291
3292            if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'ga')) {
3293                return;
3294            }
3295
3296            var eventParams = Utils.copyProperties(data, {});
3297            var _fireEvent = function (tracking_id,name,params) {
3298
3299                // Firing without a destination would park the hit inside gtag.js
3300                // instead of sending it, which Tag Assistant reports as a missing
3301                // config command. Skip, but keep the reason visible in debug mode.
3302                if (!Utils.hasSendToTarget(tracking_id)) {
3303                    if (options.debug) {
3304                        console.warn('[Google Analytics] ' + name + ' skipped: empty tracking ID', {
3305                            trackingIds: options.ga.trackingIds
3306                        });
3307                    }
3308                    return;
3309                }
3310
3311                params['send_to'] = tracking_id;
3312
3313                if (options.debug) {
3314                    console.log('[Google Analytics #' + tracking_id + '] ' + name, params);
3315                }
3316
3317                Utils.gtagPush('event', name, params);
3318
3319            };
3320            options.ga.trackingIds.forEach(function (tracking_id) {
3321                var copyParams = Utils.copyProperties(eventParams, {}); // copy params because mapParamsTov4 can modify it
3322                var params = mapParamsTov4(tracking_id,name,copyParams)
3323                _fireEvent(tracking_id,name,params);
3324            });
3325
3326        }
3327        function mapParamsTov4(tag,name,param) {
3328            //GA4 automatically collects a number of parameters for all events
3329            delete param.landing_page;
3330            // end
3331            if(isv4(tag)) {
3332                delete param.traffic_source;
3333                delete param.event_category;
3334                delete param.event_label;
3335                delete param.ecomm_prodid;
3336                delete param.ecomm_pagetype;
3337                delete param.ecomm_totalvalue;
3338                if(name === 'search') {
3339                    param['search'] = param.search_term;
3340                    delete param.search_term;
3341                    delete param.dynx_itemid;
3342                    delete param.dynx_pagetype;
3343                    delete param.dynx_totalvalue;
3344                }
3345            }
3346            return param;
3347        }
3348
3349        function isv4(tag) {
3350            return tag.indexOf('G') === 0;
3351        }
3352
3353        /**
3354         * Public API
3355         */
3356        return {
3357            tag: function() {
3358                return "ga";
3359            },
3360            isEnabled: function () {
3361                return options.hasOwnProperty('ga');
3362            },
3363
3364            disable: function () {
3365                initialized = false;
3366            },
3367
3368            loadPixel: function () {
3369
3370                if (initialized || !this.isEnabled() || !Utils.consentGiven('analytics')) {
3371                    return;
3372                }
3373
3374                Utils.loadGoogleTag(options.ga.trackingIds[0]);
3375                var config = {};
3376                // Cross-Domain tracking
3377                if (options.ga.crossDomainEnabled) {
3378                    config.linker = {
3379                        accept_incoming: options.ga.crossDomainAcceptIncoming,
3380                        domains: options.ga.crossDomainDomains
3381                    };
3382                }
3383                var ids = options.ga.trackingIds;
3384                // configure tracking ids
3385                ids.forEach(function (trackingId,index) {
3386                    var obj = options.ga.isDebugEnabled;
3387                    var searchValue = "index_"+index;
3388                    var config_for_tag = Object.assign({}, options.config);
3389                    config_for_tag.debug_mode = false;
3390                    config_for_tag.send_page_view = !options.ga.custom_page_view_event;
3391                    for (var key in obj) {
3392                        if (obj.hasOwnProperty(key) && obj[key] === searchValue) {
3393                            config_for_tag.debug_mode = true;
3394                            break;
3395                        }
3396                    }
3397                    if(!config_for_tag.debug_mode)
3398                    {
3399                        delete config_for_tag.debug_mode;
3400                    }
3401                    if(isv4(trackingId)) {
3402                        if(options.ga.disableAdvertisingFeatures) {
3403                            config_for_tag.allow_google_signals = false
3404                        }
3405                        if(options.ga.disableAdvertisingPersonalization) {
3406                            config_for_tag.allow_ad_personalization_signals = false
3407                        }
3408                    }
3409                    if(options.ga.hasOwnProperty('additionalConfig')){
3410                        if(options.ga.additionalConfig.hasOwnProperty(trackingId) && options.ga.additionalConfig[trackingId]){
3411                            config_for_tag.first_party_collection = options.ga.additionalConfig[trackingId].first_party_collection;
3412                        }
3413
3414                    }
3415                    if(options.ga.hasOwnProperty('serverContainerUrls')){
3416                        if(options.ga.serverContainerUrls.hasOwnProperty(trackingId) && options.ga.serverContainerUrls[trackingId].enable_server_container != false){
3417                            if(options.ga.serverContainerUrls[trackingId].server_container_url != ''){
3418                                config_for_tag.server_container_url = options.ga.serverContainerUrls[trackingId].server_container_url;
3419                            }
3420                            if(options.ga.serverContainerUrls[trackingId].transport_url != ''){
3421                                config_for_tag.transport_url = options.ga.serverContainerUrls[trackingId].transport_url;
3422                            }
3423                        }
3424                    }
3425                    if (options.gdpr.cookiebot_integration_enabled && typeof Cookiebot !== 'undefined') {
3426
3427                        var cookiebot_consent_category = options.gdpr['cookiebot_analytics_consent_category'];
3428                        if (options.gdpr['analytics_prior_consent_enabled']) {
3429                            if (Cookiebot.consented === true && Cookiebot.consent[cookiebot_consent_category]) {
3430                                Utils.gtagPush('config', trackingId, config_for_tag);
3431                            }
3432                        } else {
3433                            if (Cookiebot.consent[cookiebot_consent_category]) {
3434                                Utils.gtagPush('config', trackingId, config_for_tag);
3435                            }
3436                        }
3437                    }
3438                    else
3439                    {
3440                        Utils.gtagPush('config', trackingId, config_for_tag);
3441                    }
3442                });
3443
3444                initialized = true;
3445
3446                Utils.fireStaticEvents('ga');
3447
3448            },
3449
3450            fireEvent: function (name, data) {
3451
3452                if (!initialized || !this.isEnabled()) {
3453                    return false;
3454                }
3455
3456                data.delay = data.delay || 0;
3457                data.params = data.params || {};
3458                data.params.eventID = Utils.generateUniqueId(data);
3459                if (data.delay === 0) {
3460
3461                    fireEvent(name, data.params);
3462
3463                } else {
3464
3465                    setTimeout(function (name, params) {
3466                        fireEvent(name, params);
3467                    }, data.delay * 1000, name, data.params);
3468
3469                }
3470
3471                return true;
3472
3473            },
3474
3475            onCommentEvent: function (event) {
3476                this.fireEvent(event.name, event);
3477            },
3478
3479            onDownloadEvent: function (event) {
3480                this.fireEvent(event.name, event);
3481            },
3482
3483            onFormEvent: function (event) {
3484                this.fireEvent(event.name, event);
3485            },
3486
3487            onWooAddToCartOnButtonEvent: function (product_id) {
3488
3489                if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3490                    return;
3491                var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
3492
3493                if (window.pysWooProductData.hasOwnProperty(product_id)) {
3494                    if (window.pysWooProductData[product_id].hasOwnProperty('ga')) {
3495                        Utils.copyProperties(window.pysWooProductData[product_id]['ga'].params, event.params)
3496                        this.fireEvent(event.name, event);
3497                    }
3498                }
3499
3500
3501            },
3502
3503            onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, $form) {
3504
3505                window.pysWooProductData = window.pysWooProductData || [];
3506
3507                if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3508                    return;
3509                var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
3510
3511                if (product_type === Utils.PRODUCT_VARIABLE && !options.ga.wooVariableAsSimple) {
3512                    product_id = parseInt($form.find('input[name="variation_id"]').val());
3513                }
3514
3515                if (window.pysWooProductData.hasOwnProperty(product_id)) {
3516                    if (window.pysWooProductData[product_id].hasOwnProperty('ga')) {
3517
3518                        Utils.copyProperties(window.pysWooProductData[product_id]['ga'].params, event.params);
3519                        if(product_type === Utils.PRODUCT_GROUPED ) {
3520                            var groupValue = 0;
3521                            $form.find(".woocommerce-grouped-product-list .qty").each(function(index){
3522                                var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]","");
3523                                var quantity = parseInt($(this).val());
3524                                if(isNaN(quantity)) {
3525                                    quantity = 0;
3526                                }
3527                                var childItem = window.pysWooProductData[product_id]['ga'].grouped[childId];
3528                                // update quantity
3529                                event.params.items.forEach(function(el,index,array) {
3530                                    if(el.id == childItem.content_id) {
3531                                        if(quantity > 0){
3532                                            el.quantity = quantity;
3533                                        } else {
3534                                            array.splice(index, 1);
3535                                        }
3536                                    }
3537                                });
3538                                groupValue += childItem.price * quantity;
3539                            });
3540                            if(options.woo.addToCartOnButtonValueEnabled &&
3541                                options.woo.addToCartOnButtonValueOption !== 'global' &&
3542                                event.params.hasOwnProperty('ecomm_totalvalue')) {
3543                                event.params.ecomm_totalvalue = groupValue;
3544                            }
3545
3546                            if(groupValue == 0) return; // skip if no items selected
3547                        } else {
3548                            // update items qty param
3549                            event.params.items[0].quantity = qty;
3550                        }
3551
3552                        // maybe customize value option
3553                        if (options.woo.addToCartOnButtonValueEnabled &&
3554                            options.woo.addToCartOnButtonValueOption !== 'global' &&
3555                            product_type !== Utils.PRODUCT_GROUPED)
3556                        {
3557                            if(event.params.hasOwnProperty('ecomm_totalvalue')) {
3558                                event.params.ecomm_totalvalue = event.params.items[0].price * qty;
3559                            }
3560
3561                        }
3562
3563
3564                        this.fireEvent(event.name, event);
3565                    }
3566                }
3567
3568            },
3569
3570            onWooRemoveFromCartEvent: function (event) {
3571
3572                this.fireEvent(event.name, event);
3573
3574            },
3575
3576            onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {
3577
3578                if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3579                    return;
3580                var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]);
3581
3582
3583                if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) {
3584
3585                    var index;
3586
3587                    if (price_index) {
3588                        index = download_id + '_' + price_index;
3589                    } else {
3590                        index = download_id;
3591                    }
3592
3593                    if (window.pysEddProductData[download_id].hasOwnProperty(index)) {
3594                        if (window.pysEddProductData[download_id][index].hasOwnProperty('ga')) {
3595
3596                            Utils.copyProperties(window.pysEddProductData[download_id][index]['ga'].params, event.params);
3597
3598                            // update items qty param
3599                            event.params.items[0].quantity = qty;
3600
3601                            this.fireEvent(event.name,event);
3602
3603                        }
3604                    }
3605
3606                }
3607
3608            },
3609
3610            onEddRemoveFromCartEvent: function (event) {
3611                this.fireEvent(event.name, event);
3612            },
3613            onPageScroll: function (event) {
3614                this.fireEvent(event.name, event);
3615            },
3616            onTime: function (event) {
3617                this.fireEvent(event.name, event);
3618            },
3619
3620        };
3621
3622    }(options);
3623
3624    var GTM = function (options) {
3625        var initialized = false;
3626
3627        var datalayer_name = 'dataLayer';
3628        /*
3629         * @param name
3630         * @param data
3631         */
3632        function fireEvent(name, event) {
3633            if(typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name,'gtm')) {
3634                return;
3635            }
3636
3637
3638            var eventParams = event.params;
3639            var data = event.params;
3640            var valuesArray = Object.values(event.trackingIds);
3641            var ids = valuesArray;
3642            Utils.copyProperties(Utils.getRequestParams(), eventParams);
3643            var _fireEvent = function (tracking_id,name,params, event=null) {
3644                var eventData = {};
3645                var ContainerCodeHasTag = !options.gtm.gtm_just_data_layer && tracking_id.length > 0;
3646                if(ContainerCodeHasTag) {
3647                    params['send_to'] = tracking_id;
3648                }
3649                else if(!options.gtm.gtm_just_data_layer){
3650                    return
3651                }
3652
3653                if (params.hasOwnProperty('ecommerce')) {
3654                    eventData.ecommerce = params.ecommerce;
3655                    delete params.ecommerce;
3656                }
3657
3658                var automatedParams = { ...params };
3659                [params['manualName'], 'manualName', 'triggerType'].forEach(key => delete automatedParams[key]);
3660                if(event && (!event.hasOwnProperty('hasAutoParam') || (event.hasOwnProperty('hasAutoParam') && event.hasAutoParam))) {
3661                    eventData.automatedParameters = automatedParams;
3662                }
3663
3664// Move custom parameters to eventData
3665
3666                if (params.hasOwnProperty(params['manualName'])) {
3667                    eventData[params['manualName']] = params[params['manualName']];
3668                    delete params[params['manualName']];
3669                }
3670
3671                ['manualName','triggerType'].forEach(key => {
3672                    if (params.hasOwnProperty(key)) {
3673                        eventData[key] = params[key];
3674                        delete params[key];
3675                    }
3676                });
3677
3678                eventData.manualDataLayer = Utils.resolveGtmDataLayerName();
3679
3680                eventData.event = name;
3681
3682                if (options.debug) {
3683                    if (ContainerCodeHasTag){
3684                        console.log('[Google GTM #' + tracking_id + '] ' + name, eventData);
3685                    }
3686                    else {
3687                        console.log('[Google GTM push to "'+datalayer_name+'"] ' + name, eventData);
3688                    }
3689                }
3690                window[datalayer_name].push(eventData);
3691
3692            };
3693
3694            var copyParams = Utils.copyProperties(eventParams, {}); // copy params because mapParamsToGTM can modify it
3695
3696            var params = mapParamsToGTM(ids,name,copyParams)
3697
3698            params.event_id = Utils.generateUniqueId(event);
3699
3700            _fireEvent(ids, name, params, event);
3701        }
3702
3703        function normalizeEventName(eventName) {
3704
3705            var matches = {
3706                ViewContent: 'view_item',
3707                AddToCart: 'add_to_cart',
3708                AddToWishList: 'add_to_wishlist',
3709                InitiateCheckout: 'begin_checkout',
3710                Purchase: 'purchase',
3711                Lead: 'generate_lead',
3712                CompleteRegistration: 'sign_up',
3713                AddPaymentInfo: 'set_checkout_option'
3714            };
3715
3716            return matches.hasOwnProperty(eventName) ? matches[eventName] : eventName;
3717
3718        }
3719
3720        function mapParamsToGTM(tag,name,param) {
3721            //GA4 automatically collects a number of parameters for all events
3722            var hasGTM = false;
3723
3724            // end
3725            if (Array.isArray(tag)) {
3726                hasGTM = tag.some(function (element) {
3727                    return isGTM(element);
3728                });
3729            } else if(isGTM(tag)) {
3730                // tag is a string and matches GTM
3731                hasGTM = true;
3732            }
3733            if(hasGTM) {
3734                delete param.event_category;
3735                delete param.event_label;
3736
3737                delete param.analytics_storage;
3738                delete param.ad_storage;
3739                delete param.ad_user_data;
3740                delete param.ad_personalization;
3741
3742                if(name === 'search') {
3743                    param['search'] = param.search_term;
3744                    delete param.search_term;
3745                    delete param.dynx_itemid;
3746                    delete param.dynx_pagetype;
3747                    delete param.dynx_totalvalue;
3748                }
3749            }
3750            return param;
3751        }
3752
3753        function isGTM(tag) {
3754            return tag.indexOf('GTM') === 0;
3755        }
3756        /**
3757         * Public API
3758         */
3759        return {
3760            tag: function() {
3761                return "gtm";
3762            },
3763            isEnabled: function () {
3764                return options.hasOwnProperty('gtm');
3765            },
3766            disable: function () {
3767                initialized = false;
3768            },
3769            updateEnhancedConversionData : function () {
3770                if (!initialized || !this.isEnabled()) {
3771                    return;
3772                }
3773                if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable) {
3774                    var advanced = Utils.getAdvancedFormData();
3775                    if (Object.keys(advanced).length > 0) {
3776                        window[datalayer_name].push({'user_data' : advanced});
3777                    }
3778                }
3779            },
3780            loadPixel: function () {
3781
3782                if (initialized || !this.isEnabled() || !Utils.consentGiven('analytics')) {
3783                    return;
3784                }
3785                datalayer_name = Utils.resolveGtmDataLayerName();
3786
3787                for (var i = 0; i < options.gtm.trackingIds.length; i++) {
3788                    var trackingId = options.gtm.trackingIds[i];
3789                    if (!options.gtm.gtm_just_data_layer) {
3790                        console.log('[PYS] Google Tag Manager container code loaded');
3791                        Utils.loadGTMScript(trackingId);
3792                        break;
3793                    }
3794                }
3795                if(options.gtm.gtm_just_data_layer) {
3796                    console.warn && console.warn("[PYS] Google Tag Manager container code placement set to OFF !!!");
3797                    console.warn && console.warn("[PYS] Data layer codes are active but GTM container must be loaded using custom coding !!!");
3798                    // Send the consent state even when GTM script loading is handled
3799                    // externally: gtm_just_data_layer with tracking IDs configured skips
3800                    // loadGTMScript() entirely. When trackingIds is empty, loadGTMScript()
3801                    // below runs too and would reach this again — pushGTMConsentState()
3802                    // is idempotent per page load, so the container still sees one command.
3803                    Utils.pushGTMConsentState( datalayer_name );
3804                    if(options.gtm.trackingIds.length == 0){
3805                        Utils.loadGTMScript();
3806                    }
3807                }
3808
3809                if(options.hasOwnProperty("tracking_analytics") && options.tracking_analytics.hasOwnProperty("userDataEnable") && options.tracking_analytics.userDataEnable){
3810                    var advanced = Utils.getAdvancedFormData();
3811                    if(Object.keys(advanced).length > 0){
3812                        window[datalayer_name].push({'user_data' : advanced});
3813                    }
3814                }
3815
3816
3817                var config = {};
3818
3819                if(options.user_id && options.user_id != 0) {
3820                    config.user_id = options.user_id;
3821                }
3822
3823                // Cross-Domain tracking
3824
3825                var ids = options.gtm.trackingIds;
3826
3827
3828                initialized = true;
3829
3830                Utils.fireStaticEvents('gtm');
3831            },
3832
3833            fireEvent: function (name, data) {
3834
3835                if (!initialized || !this.isEnabled()) {
3836                    return false;
3837                }
3838
3839                data.delay = data.delay || 0;
3840                data.params = data.params || {};
3841
3842                if (data.delay === 0) {
3843
3844                    fireEvent(name, data);
3845
3846                } else {
3847
3848                    setTimeout(function (name, params) {
3849                        fireEvent(name, params);
3850                    }, data.delay * 1000, name, data);
3851
3852                }
3853
3854                return true;
3855
3856            },
3857
3858            onCommentEvent: function (event) {
3859                this.fireEvent(event.name, event);
3860            },
3861
3862            onDownloadEvent: function (event) {
3863                this.fireEvent(event.name, event);
3864            },
3865
3866            onFormEvent: function (event) {
3867                this.fireEvent(event.name, event);
3868            },
3869
3870            onWooAddToCartOnButtonEvent: function (product_id, prod_info = null) {
3871                if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3872                    return;
3873                var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
3874
3875                if (window.pysWooProductData.hasOwnProperty(product_id)) {
3876                    if (window.pysWooProductData[product_id].hasOwnProperty('gtm')) {
3877                        Utils.copyProperties(window.pysWooProductData[product_id]['gtm'].params, event.params)
3878                        this.fireEvent(event.name, event);
3879                    }
3880                }
3881
3882            },
3883
3884            onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, $form) {
3885                window.pysWooProductData = window.pysWooProductData || [];
3886
3887                if(!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3888                    return;
3889                var event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
3890
3891                if (product_type === Utils.PRODUCT_VARIABLE && !options.gtm.wooVariableAsSimple) {
3892                    product_id = parseInt($form.find('input[name="variation_id"]').val());
3893                }
3894
3895                if (window.pysWooProductData.hasOwnProperty(product_id)) {
3896                    if (window.pysWooProductData[product_id].hasOwnProperty('gtm')) {
3897
3898                        Utils.copyProperties(window.pysWooProductData[product_id]['gtm'].params, event.params);
3899
3900                        params = event.params.hasOwnProperty('ecommerce') ? event.params.ecommerce : event.params;
3901
3902                        if(product_type === Utils.PRODUCT_GROUPED ) {
3903                            var groupValue = 0;
3904                            $form.find(".woocommerce-grouped-product-list .qty").each(function(index){
3905                                var childId = $(this).attr('name').replaceAll("quantity[","").replaceAll("]","");
3906                                var quantity = parseInt($(this).val());
3907                                if(isNaN(quantity)) {
3908                                    quantity = 0;
3909                                }
3910                                var childItem = window.pysWooProductData[product_id]['gtm'].grouped[childId];
3911                                params.items.forEach(function(el,index,array) {
3912                                    if(el.id == childItem.content_id) {
3913                                        if(quantity > 0){
3914                                            el.quantity = quantity;
3915                                            el.price = childItem.price;
3916                                        } else {
3917                                            array.splice(index, 1);
3918                                        }
3919                                    }
3920                                });
3921                                groupValue += childItem.price * quantity;
3922                            });
3923
3924                            if(options.woo.addToCartOnButtonValueEnabled &&
3925                                options.woo.addToCartOnButtonValueOption !== 'global' &&
3926                                params.hasOwnProperty('value')) {
3927                                params.value = groupValue;
3928                            }
3929
3930                            if(groupValue == 0) return; // skip if no items selected
3931                        } else {
3932                            // update items qty param
3933                            params.items[0].quantity = qty;
3934                        }
3935
3936                        // maybe customize value option
3937                        if (options.woo.addToCartOnButtonValueEnabled &&
3938                            options.woo.addToCartOnButtonValueOption !== 'global' &&
3939                            product_type !== Utils.PRODUCT_GROUPED)
3940                        {
3941                            if(params.hasOwnProperty('value')) {
3942                                params.value = params.items[0].price * qty;
3943                            }
3944
3945                        }
3946
3947
3948                        this.fireEvent(event.name, event);
3949                    }
3950                }
3951
3952            },
3953
3954            onWooRemoveFromCartEvent: function (event) {
3955
3956                this.fireEvent(event.name, event);
3957
3958            },
3959
3960            onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {
3961                if(!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag()))
3962                    return;
3963                var event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]);
3964
3965
3966                if (window.pysEddProductData && window.pysEddProductData.hasOwnProperty(download_id)) {
3967
3968                    var index;
3969
3970                    if (price_index) {
3971                        index = download_id + '_' + price_index;
3972                    } else {
3973                        index = download_id;
3974                    }
3975
3976                    if (window.pysEddProductData[download_id].hasOwnProperty(index)) {
3977                        if (window.pysEddProductData[download_id][index].hasOwnProperty('gtm')) {
3978
3979                            Utils.copyProperties(window.pysEddProductData[download_id][index]['gtm'].params, event.params);
3980                            item = event.params.hasOwnProperty('ecommerce') ? event.params.ecommerce.items[0] : event.params.items[0];
3981                            // update items qty param
3982                            item.quantity = qty;
3983
3984                            this.fireEvent(event.name,event);
3985
3986                        }
3987                    }
3988
3989                }
3990
3991            },
3992
3993            onEddRemoveFromCartEvent: function (event) {
3994                this.fireEvent(event.name, event);
3995            },
3996            onPageScroll: function (event) {
3997                this.fireEvent(event.name, event);
3998            },
3999            onTime: function (event) {
4000                this.fireEvent(event.name, event);
4001            },
4002        };
4003
4004    }(options);
4005
4006
4007    var OpenAI = function (options) {
4008
4009        let initialized = false,
4010            sentIdentity = null,
4011            identityWaitTimer = null,
4012            identityWaitExpired = false;
4013
4014        /**
4015         * Send the server copy of an event.
4016         *
4017         */
4018        function sendOpenAIServerEvent(name, allData, data, eventId) {
4019
4020            if (!options.hasOwnProperty('openai') || !options.openai.serverApiEnabled) {
4021                return;
4022            }
4023
4024            let isApiDisabled = options.gdpr.all_disabled_by_api ||
4025                options.gdpr.openai_disabled_by_api ||
4026                options.gdpr.cookiebot_integration_enabled ||
4027                options.gdpr.consent_magic_integration_enabled ||
4028                options.gdpr.cookie_notice_integration_enabled ||
4029                options.gdpr.cookie_law_info_integration_enabled;
4030
4031            if (!(options.ajaxForServerEvent || isApiDisabled)) {
4032                return;
4033            }
4034
4035            let isStatic = allData.type === 'static' || allData.type === undefined,
4036                delay = allData.delay || 0;
4037
4038            // PHP never collects a dynamic event, and it collects only delay == 0.
4039            let phpSkipped = !isStatic || delay > 0;
4040
4041            // A static event is PHP's, unless the site is set to send static
4042            // events over AJAX — which is exactly when PHP steps aside.
4043            if (!phpSkipped
4044                && !options.ajaxForServerStaticEvent
4045                && !(allData.hasOwnProperty('ajaxFire') && allData.ajaxFire)) {
4046                return;
4047            }
4048
4049            let params = {data: data};
4050
4051            if (allData.params && allData.params.custom_event_name) {
4052                params.custom_event_name = allData.params.custom_event_name;
4053            }
4054
4055            let json = {
4056                action: 'pys_openai_api_event',
4057                pixel: 'openai',
4058                event: name,
4059                event_slug: allData.e_id || '',
4060                ids: allData.pixelIds || options.openai.pixelIds,
4061                data: params,
4062                url: window.location.href,
4063                eventID: eventId,
4064                ajax_event: options.ajax_event
4065            };
4066
4067            if (allData.hasOwnProperty('woo_order')) {
4068                json['woo_order'] = allData.woo_order;
4069            }
4070
4071            if (allData.hasOwnProperty('edd_order')) {
4072                json['edd_order'] = allData.edd_order;
4073            }
4074
4075            if (allData.hasOwnProperty('order_key') && allData.order_key) {
4076                json['order_key'] = allData.order_key;
4077            }
4078
4079            let wait = name === 'page_viewed' ? 500 : 0;
4080
4081            setTimeout(function () {
4082                Utils.sendServerAjaxRequest(options.ajaxUrl, json);
4083            }, wait);
4084        }
4085
4086        /**
4087         * Is the pixel in OpenAI's restricted mode?
4088         *
4089         */
4090        function optOutMode() {
4091
4092            if ( options.openai && options.openai.opt_out ) {
4093                return true;
4094            }
4095
4096            return !! ( options.gdpr
4097                && options.gdpr.consent_magic_integration_enabled
4098                && typeof CS_Data !== 'undefined'
4099                && Utils.csModeEnabled( CS_Data.cs_openai_opt_out_mode ) );
4100        }
4101
4102        const SDK_DATA_FIELDS = {
4103            contents: ['type', 'amount', 'currency', 'contents'],
4104            custom: ['type', 'plan_id', 'amount', 'currency', 'contents'],
4105            customer_action: ['type', 'amount', 'currency'],
4106            plan_enrollment: ['type', 'plan_id', 'amount', 'currency', 'contents']
4107        };
4108
4109        const SDK_ITEM_FIELDS = ['id', 'name', 'content_type', 'quantity', 'amount', 'currency'];
4110
4111        function sdkInteger(value) {
4112            let number = typeof value === 'string' ? Number(value.trim()) : value;
4113
4114            return typeof number === 'number' && isFinite(number) ? Math.round(number) : null;
4115        }
4116
4117        function sdkCurrency(value) {
4118            return typeof value === 'string' && /^[A-Za-z]{3}$/.test(value) ? value : null;
4119        }
4120
4121        function sdkText(value) {
4122            if (typeof value === 'string') {
4123                return value === '' ? null : value;
4124            }
4125
4126            return typeof value === 'number' && isFinite(value) ? String(value) : null;
4127        }
4128
4129        /**
4130         * One contents[] item, with only the keys the SDK documents.
4131         *
4132         * @param item              the item as configured or collected
4133         * @param eventHasCurrency  true when data.currency is set: an item amount
4134         *                          needs a currency, its own or the event's
4135         */
4136        function sdkSafeItem(item, eventHasCurrency) {
4137
4138            if (!item || typeof item !== 'object' || Array.isArray(item)) {
4139                return null;
4140            }
4141
4142            let safe = {};
4143
4144            SDK_ITEM_FIELDS.forEach(function (field) {
4145                if (item[field] === undefined || item[field] === null || item[field] === '') {
4146                    return;
4147                }
4148
4149                if (field === 'quantity' || field === 'amount') {
4150                    let number = sdkInteger(item[field]);
4151
4152                    if (number !== null) {
4153                        safe[field] = number;
4154                    }
4155
4156                    return;
4157                }
4158
4159                if (field === 'currency') {
4160                    let currency = sdkCurrency(item.currency);
4161
4162                    if (currency !== null) {
4163                        safe.currency = currency;
4164                    }
4165
4166                    return;
4167                }
4168
4169                let text = sdkText(item[field]);
4170
4171                if (text !== null) {
4172                    safe[field] = text;
4173                }
4174            });
4175
4176            if (safe.hasOwnProperty('amount') && !safe.hasOwnProperty('currency') && !eventHasCurrency) {
4177                delete safe.amount;
4178            }
4179
4180            return Object.keys(safe).length ? safe : null;
4181        }
4182
4183        /**
4184         * The event data, with only the fields the SDK documents for its shape.
4185         */
4186        function sdkSafeData(data) {
4187
4188            if (!data || typeof data !== 'object') {
4189                return {};
4190            }
4191
4192            let allowed = SDK_DATA_FIELDS[data.type];
4193
4194            if (!allowed) {
4195                // An undocumented shape is the module's own bug, not the
4196                // payload's: hand it over untouched rather than empty it.
4197                return data;
4198            }
4199
4200            let safe = {};
4201
4202            allowed.forEach(function (field) {
4203                if (data[field] !== undefined && data[field] !== null && data[field] !== '') {
4204                    safe[field] = data[field];
4205                }
4206            });
4207
4208            if (safe.hasOwnProperty('plan_id')) {
4209                let planId = sdkText(safe.plan_id);
4210
4211                if (planId === null) {
4212                    delete safe.plan_id;
4213                } else {
4214                    safe.plan_id = planId;
4215                }
4216            }
4217
4218            if (safe.hasOwnProperty('currency')) {
4219                let currency = sdkCurrency(safe.currency);
4220
4221                if (currency === null) {
4222                    delete safe.currency;
4223                } else {
4224                    safe.currency = currency;
4225                }
4226            }
4227
4228            if (safe.hasOwnProperty('amount')) {
4229                let amount = sdkInteger(safe.amount);
4230
4231                // The SDK refuses an amount with no currency beside it.
4232                if (amount === null || !safe.hasOwnProperty('currency')) {
4233                    delete safe.amount;
4234                } else {
4235                    safe.amount = amount;
4236                }
4237            }
4238
4239            if (safe.hasOwnProperty('contents')) {
4240                let items = Array.isArray(safe.contents)
4241                    ? safe.contents
4242                        .map(function (item) { return sdkSafeItem(item, safe.hasOwnProperty('currency')); })
4243                        .filter(function (item) { return item !== null; })
4244                    : [];
4245
4246                if (items.length) {
4247                    safe.contents = items;
4248                } else {
4249                    delete safe.contents;
4250                }
4251            }
4252
4253            return safe;
4254        }
4255
4256        function fireEvent(name, allData) {
4257
4258            if (typeof window.pys_event_data_filter === "function" && window.pys_disable_event_filter(name, 'openai')) {
4259                return;
4260            }
4261
4262            if (typeof window.oaiq !== 'function') {
4263                return;
4264            }
4265
4266            let ids = (allData && allData.pixelIds && allData.pixelIds.length)
4267                ? allData.pixelIds
4268                : options.openai.pixelIds;
4269
4270            if (!ids || ids.length === 0) {
4271                return;
4272            }
4273
4274            let data = (allData.params && allData.params.data) ? Utils.clone(allData.params.data) : {};
4275
4276            let eventOptions = {},
4277                eventId = Utils.generateUniqueId(allData);
4278
4279            if (eventId) {
4280                eventOptions.event_id = eventId;
4281            }
4282
4283            if (allData.params && allData.params.custom_event_name) {
4284                eventOptions.custom_event_name = allData.params.custom_event_name;
4285            }
4286
4287            sendOpenAIServerEvent(name, allData, data, eventId);
4288
4289            let browserData = sdkSafeData(data);
4290
4291            if (optOutMode()) {
4292                eventOptions.opt_out = true;
4293            }
4294
4295	        if (options.debug) {
4296                console.log('[OpenAI] ' + name, browserData, eventOptions, 'pixel_ids', ids);
4297            }
4298
4299            ids.forEach(function (pixelId) {
4300                oaiq('measureSingle', pixelId, name, browserData, eventOptions);
4301            });
4302
4303            let customEvent = new CustomEvent('openai_event_sent', {
4304                detail: {
4305                    eventName: name,
4306                    data: data,
4307                    options: eventOptions,
4308                    pixel_ids: ids
4309                }
4310            });
4311            window.dispatchEvent(customEvent);
4312        }
4313
4314        /**
4315         * True while the visitor's identity is still in flight.
4316         */
4317        function identityPending() {
4318            return ! identityWaitExpired
4319                && ! options.dynamicDataApplied
4320                && !! options.dynamicDataUrl;
4321        }
4322
4323        const OPENAI_USER_FIELDS = [
4324            'email_sha256',
4325            'phone_number_sha256',
4326            'external_id_sha256',
4327            'first_name_sha256',
4328            'last_name_sha256',
4329            'country',
4330            'city',
4331            'region',
4332            'postal_code'
4333        ];
4334
4335        /**
4336         * OpenAI normalises more aggressively than the other pixels: the phone
4337         * is reduced to its national digit form (leading +, 00 and zeros
4338         * stripped, then length-validated), and names lose every punctuation
4339         * and whitespace character. Mirrors pys_openai_normalize_* in
4340         * modules/openai/function-helpers.php.
4341         */
4342        function openaiCookieIdentity() {
4343
4344            let raw;
4345
4346            try {
4347                raw = Utils.getAdvancedFormData() || {};
4348            } catch (e) {
4349                raw = {};
4350            }
4351
4352            let phone = typeof raw.phone === 'string'
4353                ? raw.phone.trim().replace(/[\s().-]+/g, '')
4354                : '';
4355
4356            if (!/^\+?[0-9]+$/.test(phone) || phone.indexOf('+0') === 0) {
4357                phone = '';
4358            } else {
4359                let international = phone.indexOf('+') === 0 || phone.indexOf('00') === 0;
4360                let national = phone.replace(/^\+?0*/, '');
4361
4362                if (international && national.indexOf('1') === 0 && national.length !== 11) {
4363                    national = '';
4364                }
4365
4366                phone = /^[1-9][0-9]{7,14}$/.test(national) ? national : '';
4367            }
4368
4369            let normalizeName = function (value) {
4370                if (typeof value !== 'string' || value.length > 1024) {
4371                    return '';
4372                }
4373                let stripped = value.replace(/[!-\/:-@\[-`{-~\s]+/g, '').toLowerCase();
4374                return /\D/u.test(stripped) ? stripped : '';
4375            };
4376
4377            return {
4378                email: Utils.AdvancedMatching.normalizeEmail(raw.email || ''),
4379                phone: phone,
4380                first_name: normalizeName(raw.first_name || ''),
4381                last_name: normalizeName(raw.last_name || '')
4382            };
4383        }
4384
4385        /**
4386         * The identity to hand the SDK, built from whatever is known right now.
4387         */
4388        function buildAdvancedMatchingUser() {
4389
4390            let user = {};
4391
4392            if (options.openai.hasOwnProperty('advanced_matching')) {
4393
4394                let matching = options.openai.advanced_matching;
4395
4396                OPENAI_USER_FIELDS.forEach(function (field) {
4397                    let value = matching[field];
4398
4399                    if (typeof value === 'string' && value.length > 0) {
4400                        user[field] = value;
4401                    }
4402                });
4403            }
4404
4405            Utils.AdvancedMatching.merge(user, {
4406                email_sha256:        { field: 'email',      hash: true },
4407                phone_number_sha256: { field: 'phone',      hash: true },
4408                first_name_sha256:   { field: 'first_name', hash: true },
4409                last_name_sha256:    { field: 'last_name',  hash: true }
4410            }, openaiCookieIdentity());
4411
4412            let externalIdAllowed = options.send_external_id
4413                && ! ( options.gdpr && options.gdpr.externalID_disabled_by_api )
4414                && ! ( options.cookie
4415                    && ( options.cookie.disabled_all_cookie
4416                        || options.cookie.externalID_disabled_by_api ) );
4417
4418            if (!user.hasOwnProperty('external_id_sha256')
4419                && externalIdAllowed
4420                && typeof sha256 === 'function'
4421                && Cookies.get('pbid')) {
4422                user.external_id_sha256 = sha256(Cookies.get('pbid'));
4423            }
4424
4425            return user;
4426        }
4427
4428        /**
4429         * Public API
4430         */
4431        return {
4432            tag: function () {
4433                return "openai";
4434            },
4435            isEnabled: function () {
4436                return options.hasOwnProperty('openai');
4437            },
4438            disable: function () {
4439                initialized = false;
4440
4441                if (typeof window.oaiq === 'function') {
4442                    oaiq('consent', false);
4443                }
4444            },
4445
4446            loadPixel: function () {
4447
4448                if (initialized || !this.isEnabled() || !Utils.consentGiven('openai')) {
4449                    return;
4450                }
4451
4452                let ids = options.openai.pixelIds;
4453
4454                if (!ids || ids.length === 0) {
4455                    return;
4456                }
4457
4458                if (identityPending()) {
4459
4460                    if (identityWaitTimer === null) {
4461                        identityWaitTimer = setTimeout(function () {
4462                            identityWaitExpired = true;
4463
4464                            if (window.pys && window.pys.OpenAI) {
4465                                window.pys.OpenAI.loadPixel();
4466                            }
4467                        }, 3000);
4468                    }
4469
4470                    return;
4471                }
4472
4473                if (identityWaitTimer !== null) {
4474                    clearTimeout(identityWaitTimer);
4475                    identityWaitTimer = null;
4476                }
4477
4478                !function (w, d, s, u) {
4479                    if (w.oaiq) return;
4480                    var q = function () {
4481                        q.q.push(arguments);
4482                    };
4483                    q.q = [];
4484                    w.oaiq = q;
4485                    var js = d.createElement(s);
4486                    js.async = true;
4487                    js.src = u;
4488                    var f = d.getElementsByTagName(s)[0];
4489                    f.parentNode.insertBefore(js, f);
4490                }(window, document, 'script', 'https://bzrcdn.openai.com/sdk/oaiq.min.js');
4491
4492                let user = buildAdvancedMatchingUser();
4493
4494                if (Object.keys(user).length > 0) {
4495                    sentIdentity = JSON.stringify(user);
4496                }
4497
4498                ids.forEach(function (pixelId) {
4499                    let initOptions = {
4500                        pixelId: pixelId
4501                    };
4502
4503                    if (options.openai.debug) {
4504                        initOptions.debug = true;
4505                    }
4506
4507                    if (Object.keys(user).length > 0) {
4508                        initOptions.user = user;
4509                    }
4510
4511                    oaiq('init', initOptions);
4512                });
4513
4514                oaiq('consent', true);
4515
4516                initialized = true;
4517
4518                Utils.fireStaticEvents('openai');
4519            },
4520
4521            /**
4522             * Hand the SDK an identity that arrived after the pixel was already initialised.
4523             */
4524            refreshAdvancedMatching: function () {
4525
4526                if (!initialized || !this.isEnabled() || typeof window.oaiq !== 'function') {
4527                    return;
4528                }
4529
4530                let user = buildAdvancedMatchingUser();
4531
4532                if (Object.keys(user).length === 0) {
4533                    return;
4534                }
4535
4536                let identity = JSON.stringify(user);
4537
4538                if (identity === sentIdentity) {
4539                    return;
4540                }
4541
4542                sentIdentity = identity;
4543
4544                let ids = options.openai.pixelIds || [];
4545
4546                ids.forEach(function (pixelId) {
4547                    oaiq('init', {pixelId: pixelId, user: user});
4548                });
4549            },
4550
4551            fireEvent: function (name, data) {
4552
4553                if (!initialized || !this.isEnabled()) {
4554                    return false;
4555                }
4556
4557                fireEvent(name, data);
4558
4559                return true;
4560            },
4561
4562            /**
4563             * Add to cart from a listing button. The event is registered once with
4564             * a placeholder payload; the real one is substituted here from the
4565             * per-product data the server printed.
4566             */
4567            onWooAddToCartOnButtonEvent: function (product_id) {
4568
4569                if (!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) {
4570                    return;
4571                }
4572                if (!window.pysWooProductData || !window.pysWooProductData.hasOwnProperty(product_id)) {
4573                    return;
4574                }
4575                if (!window.pysWooProductData[product_id].hasOwnProperty(this.tag())) {
4576                    return;
4577                }
4578
4579                let productData = window.pysWooProductData[product_id][this.tag()];
4580                let event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
4581
4582                Utils.copyProperties(productData['params'], event.params);
4583
4584                if (productData.hasOwnProperty('pixelIds')) {
4585                    event.pixelIds = productData['pixelIds'];
4586                }
4587
4588                this.fireEvent(event.name, event);
4589            },
4590
4591            /**
4592             * Add to cart from a single product page, where the visitor picks the
4593             * quantity.
4594             */
4595            onWooAddToCartOnSingleEvent: function (product_id, qty, product_type, is_external, $form) {
4596
4597                if (!options.dynamicEvents.woo_add_to_cart_on_button_click.hasOwnProperty(this.tag())) {
4598                    return;
4599                }
4600                if (!window.pysWooProductData || !window.pysWooProductData.hasOwnProperty(product_id)) {
4601                    return;
4602                }
4603                if (!window.pysWooProductData[product_id].hasOwnProperty(this.tag())) {
4604                    return;
4605                }
4606
4607                let event = Utils.clone(options.dynamicEvents.woo_add_to_cart_on_button_click[this.tag()]);
4608
4609                Utils.copyProperties(window.pysWooProductData[product_id][this.tag()]['params'], event.params);
4610
4611                if (qty && event.params && event.params.data && Array.isArray(event.params.data.contents)) {
4612                    event.params.data.contents.forEach(function (item) {
4613                        item.quantity = qty;
4614                    });
4615                }
4616
4617                this.fireEvent(event.name, event);
4618            },
4619
4620            /**
4621             * Add to cart from an EDD button. Downloads with price variations are
4622             * keyed as "<download_id>_<price_index>".
4623             */
4624            onEddAddToCartOnButtonEvent: function (download_id, price_index, qty) {
4625
4626                if (!options.dynamicEvents.edd_add_to_cart_on_button_click.hasOwnProperty(this.tag())) {
4627                    return;
4628                }
4629                if (!window.pysEddProductData || !window.pysEddProductData.hasOwnProperty(download_id)) {
4630                    return;
4631                }
4632
4633                let index = price_index ? download_id + '_' + price_index : download_id;
4634
4635                if (!window.pysEddProductData[download_id].hasOwnProperty(index)) {
4636                    return;
4637                }
4638                if (!window.pysEddProductData[download_id][index].hasOwnProperty(this.tag())) {
4639                    return;
4640                }
4641
4642                let event = Utils.clone(options.dynamicEvents.edd_add_to_cart_on_button_click[this.tag()]);
4643
4644                Utils.copyProperties(window.pysEddProductData[download_id][index][this.tag()].params, event.params);
4645
4646                if (qty && event.params && event.params.data && Array.isArray(event.params.data.contents)) {
4647                    event.params.data.contents.forEach(function (item) {
4648                        item.quantity = qty;
4649                    });
4650                }
4651
4652                this.fireEvent(event.name, event);
4653            },
4654        };
4655
4656    }(options);
4657
4658    var getPixelBySlag = function getPixelBySlag(slug) {
4659        switch (slug) {
4660            case "facebook": return window.pys.Facebook;
4661            case "ga": return window.pys.Analytics;
4662            case "gtm": return window.pys.GTM;
4663            case "bing": return window.pys.Bing;
4664            case "pinterest": return window.pys.Pinterest;
4665	        case "reddit": return window.pys.Reddit;
4666	        case "openai": return window.pys.OpenAI;
4667        }
4668    }
4669
4670    window.pys = window.pys || {};
4671    window.pys.Facebook = Facebook;
4672    window.pys.Analytics = Analytics;
4673    window.pys.GTM = GTM;
4674    window.pys.OpenAI = OpenAI;
4675    window.pys.Utils = Utils;
4676    window.pys.getPixelBySlag = getPixelBySlag;
4677
4678    // Allow external scripts (e.g. generatePixelCode fragments) to refresh
4679    // the uniqueId cache for a given event key, so that each new add_to_cart
4680    // action gets a fresh shared ID across all pixels.
4681    window.pys.setEventUniqueId = function(idKey, newId) {
4682        uniqueId[idKey] = newId;
4683    };
4684
4685    // Export getPixelBySlag globally for backward compatibility
4686    window.getPixelBySlag = getPixelBySlag;
4687
4688
4689    function pysNormalInit() {
4690
4691        if($("#pys_late_event").length > 0) {
4692            var dirAttr = $("#pys_late_event").attr("dir");
4693            if (dirAttr) {
4694                try {
4695                    var events = JSON.parse(dirAttr);
4696                } catch (e) {
4697                    console.warn("Invalid JSON in pys_late_event dir attribute:", e);
4698                }
4699            } else {
4700                console.warn("pys_late_event dir attribute is undefined or empty");
4701            }
4702            if (events) {
4703                for (var key in events) {
4704                    var event = {};
4705                    event[events[key].e_id] = [events[key]];
4706                    if (options.staticEvents.hasOwnProperty(key)) {
4707                        Object.assign(options.staticEvents[key], event);
4708                    } else {
4709                        options.staticEvents[key] = event;
4710                    }
4711                }
4712            }
4713        }
4714
4715        var Pinterest = Utils.setupPinterestObject();
4716        var Bing = Utils.setupBingObject();
4717	    var Reddit = Utils.setupRedditObject();
4718
4719        if(options.hasOwnProperty('cookie'))
4720        {
4721            if(options.cookie.externalID_disabled_by_api || options.cookie.disabled_all_cookie)
4722            {
4723                Cookies.remove('pbid', { path: '/',domain: cookieDomain() })
4724            }
4725            if(options.cookie.disabled_advanced_form_data_cookie || options.cookie.disabled_all_cookie)
4726            {
4727                // Removed twice on purpose: PHP writes this cookie host-only
4728                // until pys_cd is reported (or permanently, on a site whose
4729                // front end never reaches manageCookies()), while this script
4730                // writes/deletes it at cookieDomain(). A host-only cookie and
4731                // a domain-scoped one of the same name are separate jar
4732                // entries, and a delete only matches the scope it names — so
4733                // both scopes are cleared rather than guessed at.
4734                // `domain: ''` is deliberate: js-cookie 2.1.3 treats a falsy
4735                // Domain as "omit the attribute" (host-only delete), so it's
4736                // written explicitly rather than left off.
4737                // In Free this matters more than in Pro: there is no JS
4738                // writer for this cookie at all, so PHP is its only writer
4739                // and the host-only copy is the only copy a pending site
4740                // ever has.
4741                Cookies.remove('pys_advanced_form_data', { path: '/',domain: cookieDomain() })
4742                Cookies.remove('pys_advanced_form_data', { path: '/',domain: '' })
4743            }
4744            if(options.cookie.disabled_landing_page_cookie || options.cookie.disabled_all_cookie)
4745            {
4746                Cookies.remove('pys_landing_page', { path: '/',domain: cookieDomain() })
4747                Cookies.remove('last_pys_landing_page', { path: '/',domain: cookieDomain() })
4748            }
4749            if(options.cookie.disabled_trafficsource_cookie || options.cookie.disabled_all_cookie)
4750            {
4751                Cookies.remove('pysTrafficSource', { path: '/',domain: cookieDomain() })
4752                Cookies.remove('last_pysTrafficSource', { path: '/',domain: cookieDomain() })
4753            }
4754            if(options.cookie.disabled_first_visit_cookie || options.cookie.disabled_all_cookie)
4755            {
4756                Cookies.remove('pys_first_visit', { path: '/',domain: cookieDomain() })
4757
4758            }
4759            if(options.cookie.disabled_utmTerms_cookie || options.cookie.disabled_all_cookie)
4760            {
4761                $.each(Utils.utmTerms, function (index, name) {
4762                    Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
4763                });
4764                $.each(Utils.utmTerms, function (index, name) {
4765                    Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
4766                });
4767            }
4768            if(options.cookie.disabled_utmId_cookie || options.cookie.disabled_all_cookie)
4769            {
4770                $.each(Utils.utmId,function(index,name) {
4771                    Cookies.remove('pys_' + name, { path: '/',domain: cookieDomain() })
4772                })
4773                $.each(Utils.utmId,function(index,name) {
4774                    Cookies.remove('last_pys_' + name, { path: '/',domain: cookieDomain() })
4775                });
4776            }
4777        }
4778
4779
4780        // Exactly one manageCookies() per init, and under a consent
4781        // integration only once consent is on record. The old fall-through
4782        // ran it a second time under CookieYes, racing two pbid requests, and
4783        // ran it before any consent when no CookieYes cookie existed yet.
4784        // external_id itself does not depend on this: the visitor key is
4785        // computed server-side without a cookie.
4786        var cookiesManaged = false;
4787
4788        if (options.gdpr.cookie_law_info_integration_enabled) {
4789            var cli_cookie = Cookies.get('cookieyes-consent') ?? Cookies.get('viewed_cookie_policy');
4790
4791            if (typeof cli_cookie !== 'undefined') {
4792                if (cli_cookie === Cookies.get('cookieyes-consent') && getCookieYes('analytics') == 'yes') {
4793                    Utils.manageCookies();
4794                    cookiesManaged = true;
4795                } else if (cli_cookie === Cookies.get('viewed_cookie_policy') && cli_cookie == 'yes') {
4796                    Utils.manageCookies();
4797                    cookiesManaged = true;
4798                }
4799            }
4800        }
4801
4802        if ( ! cookiesManaged ) {
4803            if ( options.gdpr.consent_magic_integration_enabled && typeof CS_Data !== "undefined" ) {
4804                if ( CS_Data.cs_script_cat.pys == CS_Data.cs_necessary_cat_id || CS_Data.cs_script_cat.pys == 0 ) {
4805                    Utils.manageCookies();
4806                } else if ( Cookies.get( 'cs_enabled_cookie_term' + CS_Data.test_prefix + '_' + CS_Data.cs_script_cat.pys ) == 'yes' ) {
4807                    Utils.manageCookies();
4808                }
4809            } else if ( ! options.gdpr.cookie_law_info_integration_enabled ) {
4810                Utils.manageCookies();
4811            }
4812        }
4813
4814        Utils.setupGdprCallbacks();
4815        // page scroll event
4816        if ( options.dynamicEvents.hasOwnProperty("automatic_event_scroll")
4817        ) {
4818
4819            var singlePageScroll = function () {
4820
4821
4822                var docHeight = $(document).height() - $(window).height();
4823                var isFired = false;
4824
4825                if (options.dynamicEvents.hasOwnProperty("automatic_event_scroll")) {
4826                    var pixels = Object.keys(options.dynamicEvents.automatic_event_scroll);
4827                    for(var i = 0;i<pixels.length;i++) {
4828                        var event = Utils.clone(options.dynamicEvents.automatic_event_scroll[pixels[i]]);
4829                        var scroll = Math.round(docHeight * event.scroll_percent / 100)// convert % to absolute positions
4830
4831                        if(scroll < $(window).scrollTop()) {
4832	                        if ( pixels[i] !== 'reddit') {
4833		                        Utils.copyProperties( Utils.getRequestParam
4833s(), event.params );
4834	                        }
4835	                        getPixelBySlag( pixels[ i ] ).onPageScroll( event );
4836                            isFired = true
4837                        }
4838                    }
4839                }
4840                if(isFired) {
4841                    $(document).off("scroll",singlePageScroll);
4842                }
4843            }
4844            $(document).on("scroll",singlePageScroll);
4845        }
4846
4847
4848        if (options.dynamicEvents.hasOwnProperty("automatic_event_time_on_page")) {
4849            var pixels = Object.keys(options.dynamicEvents.automatic_event_time_on_page);
4850            var time = options.dynamicEvents.automatic_event_time_on_page[pixels[0]].time_on_page; // the same for all pixel
4851            setTimeout(function(){
4852                for(var i = 0;i<pixels.length;i++) {
4853                    var event = Utils.clone(options.dynamicEvents.automatic_event_time_on_page[pixels[i]]);
4854
4855	                if ( pixels[i] !== 'reddit') {
4856		                Utils.copyProperties(Utils.getRequestParams(), event.params);
4857	                }
4858                    getPixelBySlag(pixels[i]).onTime(event);
4859                }
4860            },time*1000);
4861        }
4862
4863        // setup Click Event
4864        if (options.dynamicEvents.hasOwnProperty("automatic_event_download")) {
4865
4866            $(document).onFirst('click', 'a, button, input[type="button"], input[type="submit"]', function (e) {
4867
4868                var $elem = $(this);
4869
4870                // Download
4871                if(options.dynamicEvents.hasOwnProperty("automatic_event_download")
4872                ) {
4873                    var isFired = false;
4874                    if ($elem.is('a')) {
4875                        var href = $elem.attr('href');
4876                        if (typeof href !== "string") {
4877                            return;
4878                        }
4879                        href = href.trim();
4880                        var extension = Utils.getLinkExtension(href);
4881                        var track_download = false;
4882
4883                        if (extension.length > 0) {
4884
4885                            if(options.dynamicEvents.hasOwnProperty("automatic_event_download") ) {
4886                                var pixels = Object.keys(options.dynamicEvents.automatic_event_download);
4887                                for (var i = 0; i < pixels.length; i++) {
4888                                    var event = Utils.clone(options.dynamicEvents.automatic_event_download[pixels[i]]);
4889                                    var extensions = event.extensions;
4890                                    if (extensions.includes(extension)) {
4891
4892                                        if( pixels[i] == "tiktok" || pixels[i] == "reddit" ) {
4893                                            getPixelBySlag(pixels[i]).fireEvent(tikEvent.name, event);
4894                                        } else {
4895                                            if (options.enable_remove_download_url_param) {
4896                                                href = stripUrlQuery(href);
4897                                            }
4898                                            event.params.download_url = href;
4899                                            event.params.download_type = extension;
4900                                            event.params.download_name = Utils.getLinkFilename(href);
4901                                            getPixelBySlag(pixels[i]).onDownloadEvent(event);
4902                                        }
4903
4904                                        isFired = true;
4905                                    }
4906                                }
4907                            }
4908                        }
4909                    }
4910                    if(isFired) { // prevent duplicate events on the same element
4911                        return;
4912                    }
4913                }
4914            });
4915        }
4916
4917
4918        // setup Dynamic events
4919        $.each(options.triggerEventTypes, function (triggerType, events) {
4920
4921            $.each(events, function (eventId, triggers) {
4922
4923                switch (triggerType) {
4924                    case 'url_click':
4925                        //@see: Utils.setupURLClickEvents()
4926                        break;
4927
4928                    case 'css_click':
4929                        Utils.setupCSSClickEvents(eventId, triggers);
4930                        break;
4931
4932                    case 'css_mouseover':
4933                        Utils.setupMouseOverClickEvents(eventId, triggers);
4934                        break;
4935
4936                    case 'scroll_pos':
4937                        Utils.setupScrollPosEvents(eventId, triggers);
4938                        break;
4939                    case 'comment':
4940                        Utils.setupCommentEvents(eventId, triggers);
4941                        break;
4942                }
4943
4944            });
4945
4946        });
4947        // setup WooCommerce events
4948        if (options.woo.enabled) {
4949
4950            // WooCommerce AddToCart
4951            if (options.dynamicEvents.hasOwnProperty("woo_add_to_cart_on_button_click")
4952                && options.woo.hasOwnProperty("addToCartCatchMethod")
4953                && options.woo.addToCartCatchMethod === "add_cart_js"
4954            ) {
4955
4956                // Loop, any kind of "simple" product, except external
4957                $('.add_to_cart_button:not(.product_type_variable,.product_type_bundle,.single_add_to_cart_button)').on("click",function (e) {
4958
4959                    var product_id = $(this).data('product_id');
4960                    if (typeof product_id !== 'undefined') {
4961                        if (options.dynamicEvents.hasOwnProperty("woo_add_to_cart_on_button_click")) {
4962                            var tmpEventID = pys_generate_token();
4963                            $.each(options.dynamicEvents.woo_add_to_cart_on_button_click, function (i, tag) {
4964                                tag.eventID = tmpEventID;
4965                                // Also refresh the uniqueId cache so server-API path
4966                                // (ajaxForServerStaticEvent) gets the same fresh ID.
4967                                var idKey = tag.hasOwnProperty('custom_event_post_id') ? tag.custom_event_post_id : tag.e_id;
4968                                uniqueId[idKey] = tmpEventID;
4969                            });
4970                        }
4971                        if (typeof product_id !== 'undefined') {
4972                            Facebook.onWooAddToCartOnButtonEvent(product_id);
4973                            Analytics.onWooAddToCartOnButtonEvent(product_id);
4974                            GTM.onWooAddToCartOnButtonEvent(product_id);
4975                            Pinterest.onWooAddToCartOnButtonEvent(product_id);
4976                            Bing.onWooAddToCartOnButtonEvent(product_id);
4977	                        Reddit.onWooAddToCartOnButtonEvent(product_id);
4978                            OpenAI.onWooAddToCartOnButtonEvent(product_id);
4979                        }
4980                    }
4981                });
4982
4983                // Single Product
4984                // tap try to https://stackoverflow.com/questions/30990967/on-tap-click-event-firing-twice-how-to-avoid-it
4985                //  $(document) not work
4986                $('body').onFirst('click','button.single_add_to_cart_button,.single_add_to_cart_button',function (e) {
4987
4988                    var $button = $(this);
4989
4990                    if ($button.hasClass('disabled')) {
4991                        return;
4992                    }
4993
4994                    var $form = $button.closest('form');
4995
4996                    var product_type = Utils.PRODUCT_SIMPLE;
4997
4998                    if ($form.length === 0) {
4999                        return ;
5000                    } else if ($form.hasClass('variations_form')) {
5001                        product_type = Utils.PRODUCT_VARIABLE;
5002                    } else if($form.hasClass('bundle_form')) {
5003                        product_type = Utils.PRODUCT_BUNDLE;
5004                    } else if($form.hasClass('grouped_form')) {
5005                        product_type = Utils.PRODUCT_GROUPED;
5006                    }
5007
5008
5009
5010
5011                    var product_id;
5012                    var qty;
5013
5014                    if (product_type === Utils.PRODUCT_GROUPED) {
5015                        qty = 1;
5016                        product_id = parseInt($form.find('*[name="add-to-cart"]').val());
5017                    } else if (product_type === Utils.PRODUCT_VARIABLE) {
5018                        product_id = parseInt($form.find('*[name="add-to-cart"]').val());
5019                        var qtyTag = $form.find('input[name="quantity"]');
5020                        if(qtyTag.length <= 0) {
5021                            qtyTag = $form.find('select[name="quantity"]');
5022                        }
5023                        qty = parseInt(qtyTag.val());
5024                    } else {
5025                        product_id = parseInt($form.find('*[name="add-to-cart"]').val());
5026                        var qtyTag = $form.find('input[name="quantity"]');
5027                        if(qtyTag.length <= 0) {
5028                            qtyTag = $form.find('select[name="quantity"]');
5029                        }
5030                        qty = parseInt(qtyTag.val());
5031                    }
5032
5033                    if(options.dynamicEvents.hasOwnProperty("woo_add_to_cart_on_button_click")){
5034                        var tmpEventID = pys_generate_token();
5035                        $.each(options.dynamicEvents.woo_add_to_cart_on_button_click, function (i, tag) {
5036                            tag.eventID = tmpEventID;
5037                            // Also refresh the uniqueId cache so server-API path
5038                            // (ajaxForServerStaticEvent) gets the same fresh ID.
5039                            var idKey = tag.hasOwnProperty('custom_event_post_id') ? tag.custom_event_post_id : tag.e_id;
5040                            uniqueId[idKey] = tmpEventID;
5041                        });
5042                    }
5043
5044                    Facebook.onWooAddToCartOnSingleEvent(product_id, qty, product_type, $form);
5045                    Analytics.onWooAddToCartOnSingleEvent(product_id, qty, product_type, $form);
5046
5047                    GTM.onWooAddToCartOnSingleEvent(product_id, qty, product_type, $form);
5048
5049                    Pinterest.onWooAddToCartOnSingleEvent(product_id, qty, product_type, false, $form);
5050                    Bing.onWooAddToCartOnSingleEvent(product_id, qty, product_type, false, $form);
5051	                Reddit.onWooAddToCartOnSingleEvent(product_id, qty, product_type, false, $form);
5052                    OpenAI.onWooAddToCartOnSingleEvent(product_id, qty, product_type, false, $form);
5053
5054                });
5055
5056            }
5057
5058            // WooCommerce RemoveFromCart
5059            if (options.dynamicEvents.hasOwnProperty("woo_remove_from_cart")) {
5060
5061                $('body').on('click', options.woo.removeFromCartSelector, function (e) {
5062
5063                    var $a = $(e.currentTarget),
5064                        href = $a.attr('href');
5065
5066                    // extract cart item hash from remove button URL
5067                    var regex = new RegExp("[\\?&]remove_item=([^&#]*)"),
5068                        results = regex.exec(href);
5069
5070                    if (results !== null) {
5071
5072                        var item_hash = results[1];
5073
5074                        if (options.dynamicEvents["woo_remove_from_cart"].hasOwnProperty(item_hash)) {
5075                            var events = options.dynamicEvents["woo_remove_from_cart"][item_hash];
5076                            Utils.fireEventForAllPixel("onWooRemoveFromCartEvent",events)
5077                        }
5078
5079                    }
5080
5081                });
5082            }
5083        }
5084
5085        // setup EDD events
5086        if (options.edd.enabled) {
5087
5088            // EDD AddToCart
5089            if (options.dynamicEvents.hasOwnProperty("edd_add_to_cart_on_button_click")) {
5090
5091                $('form.edd_download_purchase_form .edd-add-to-cart').on("click",function (e) {
5092
5093                    var $button = $(this);
5094                    var $form = $button.closest('form');
5095                    var variable_price = $button.data('variablePrice'); // yes/no
5096                    var price_mode = $button.data('priceMode'); // single/multi
5097                    var ids = [];
5098                    var quantities = [];
5099                    var qty;
5100                    var id;
5101
5102                    if (variable_price === 'yes' && price_mode === 'multi') {
5103
5104                        id = $form.find('input[name="download_id"]').val();
5105
5106                        // get selected variants
5107                        $.each($form.find('input[name="edd_options[price_id][]"]:checked'), function (i, el) {
5108                            ids.push(id + '_' + $(el).val());
5109                        });
5110
5111                        // get qty for selected variants
5112                        $.each(ids, function (i, variant_id) {
5113
5114                            var variant_index = variant_id.split('_', 2);
5115                            qty = $form.find('input[name="edd_download_quantity_' + variant_index[1] + '"]').val();
5116
5117                            if (typeof qty !== 'undefined') {
5118                                quantities.push(qty);
5119                            } else {
5120                                quantities.push(1);
5121                            }
5122
5123                        });
5124
5125                    } else if (variable_price === 'yes' && price_mode === 'single') {
5126
5127                        id = $form.find('input[name="download_id"]').val();
5128                        ids.push(id + '_' + $form.find('input[name="edd_options[price_id][]"]:checked').val());
5129
5130                        qty = $form.find('input[name="edd_download_quantity"]').val();
5131
5132                        if (typeof qty !== 'undefined') {
5133                            quantities.push(qty);
5134                        } else {
5135                            quantities.push(1);
5136                        }
5137
5138                    } else {
5139
5140                        ids.push($button.data('downloadId'));
5141
5142                        qty = $form.find('input[name="edd_download_quantity"]').val();
5143
5144                        if (typeof qty !== 'undefined') {
5145                            quantities.push(qty);
5146                        } else {
5147                            quantities.push(1);
5148                        }
5149                    }
5150
5151                    // fire event for each download/variant
5152                    $.each(ids, function (i, download_id) {
5153
5154                        var q = parseInt(quantities[i]);
5155                        var variant_index = download_id.toString().split('_', 2);
5156                        var price_index;
5157
5158                        if (variant_index.length === 2) {
5159                            download_id = variant_index[0];
5160                            price_index = variant_index[1];
5161                        }
5162
5163                        Facebook.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5164                        Analytics.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5165                        GTM.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5166                        Pinterest.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5167                        Bing.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5168	                    Reddit.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5169                        OpenAI.onEddAddToCartOnButtonEvent(download_id, price_index, q);
5170                    });
5171
5172                });
5173
5174            }
5175
5176
5177            // EDD RemoveFromCart
5178            if (options.dynamicEvents.hasOwnProperty("edd_remove_from_cart") ) {
5179
5180                $('form#edd_checkout_cart_form .edd_cart_remove_item_btn').on("click",function (e) {
5181
5182                    var href = $(this).attr('href');
5183                    if(href) {
5184                        var key = href.substring(href.indexOf('=') + 1).charAt(0);
5185                        if (options.dynamicEvents.edd_remove_from_cart.hasOwnProperty(key)) {
5186                            var events = options.dynamicEvents.edd_remove_from_cart[key];
5187                            Utils.fireEventForAllPixel("onEddRemoveFromCartEvent",events)
5188                        }
5189                    }
5190                });
5191
5192            }
5193
5194        }
5195
5196        // setup Comment Event
5197        if (options.dynamicEvents.hasOwnProperty("automatic_event_c
5197omment")
5198        ) {
5199
5200            $('form.comment-form').on("submit",function () {
5201                if (options.dynamicEvents.hasOwnProperty("automatic_event_comment")) {
5202                    var pixels = Object.keys(options.dynamicEvents.automatic_event_comment);
5203                    for (var i = 0; i < pixels.length; i++) {
5204	                    var event = Utils.clone(options.dynamicEvents.automatic_event_comment[pixels[i]]);
5205	                    if ( pixels[i] !== 'reddit') {
5206		                    Utils.copyProperties(Utils.getRequestParams(), event.params);
5207	                    }
5208                        getPixelBySlag(pixels[i]).onCommentEvent(event);
5209                    }
5210                }
5211            });
5212
5213        }
5214
5215
5216        // setup Form Event
5217        if ( options.dynamicEvents.hasOwnProperty("automatic_event_form")) {
5218
5219            $(document).onFirst('submit', 'form', function (e) {
5220
5221                var $form = $(this);
5222
5223                // exclude WP forms
5224                const formClass = $form.attr('class') || '';
5225                const formRole = ($form.attr('role') || '').toLowerCase();
5226                const formId = $form.attr('id');
5227
5228                if (
5229                    $form.hasClass('comment-form') ||
5230                    $form.hasClass('search-form') ||
5231                    $form.hasClass('woocommerce-ordering') ||
5232                    formClass.toLowerCase().includes('search') || formClass.toLowerCase().includes('add-to-cart') ||
5233                    formRole === 'search' ||
5234                    formId === 'adminbarsearch'
5235                ) {
5236                    return;
5237                }
5238
5239                // exclude Woo forms
5240                if ($form.hasClass('woocommerce-product-search') || $form.hasClass('cart') || $form.hasClass('woocommerce-cart-form') ||
5241                    $form.hasClass('woocommerce-shipping-calculator') || $form.hasClass('checkout') || $form.hasClass('checkout_coupon')) {
5242                    return;
5243                }
5244
5245                // exclude EDD forms
5246                if ($form.hasClass('edd_form') || $form.hasClass('edd_download_purchase_form')) {
5247                    return;
5248                }
5249// exclude CF7 forms
5250                if ($form.hasClass('wpcf7-form')) {
5251                    return;
5252                }
5253                // exclude Forminator forms
5254                if ($form.hasClass('forminator-custom-form') || $form.hasClass('forminator_ajax')) {
5255                    return;
5256                }
5257                // exclude WPforms forms
5258                if ($form.hasClass('wpforms-form') || $form.hasClass('wpforms-ajax-form')) {
5259                    return;
5260                }
5261                // exclude Formidable forms
5262                /*if ($form.hasClass('frm-show-form')) {
5263                    return;
5264                }*/
5265                // exclude Ninja Forms forms
5266                if ($form.parent().hasClass('nf-form-layout')) {
5267                    return;
5268                }
5269                // exclude Fluent forms
5270                if ($form.hasClass('frm-fluent-form')) {
5271                    return;
5272                }
5273                if(!options.enable_success_send_form) {
5274                    var params = {
5275                        form_id: $form.attr('id'),
5276                        form_class: $form.attr('class'),
5277                        text: $form.find('[type="submit"]').is('input') ?
5278                            $form.find('[type="submit"]').val() : $form.find('[type="submit"]').text()
5279                    };
5280
5281                    if (options.dynamicEvents.hasOwnProperty("automatic_event_form")) {
5282                        var pixels = Object.keys(options.dynamicEvents.automatic_event_form);
5283                        for (var i = 0; i < pixels.length; i++) {
5284                            var event = Utils.clone(options.dynamicEvents.automatic_event_form[pixels[i]]);
5285
5286	                        if (pixels[i] === "tiktok" || pixels[i] === "reddit") {
5287                                getPixelBySlag(pixels[i]).fireEvent(event.name, event);
5288                            } else {
5289                                Utils.copyProperties(params, event.params,)
5290                                Utils.copyProperties(Utils.getRequestParam
5290s(), event.params);
5291                                getPixelBySlag(pixels[i]).onFormEvent(event);
5292                            }
5293                        }
5294                    }
5295                }
5296            });
5297
5298            document.addEventListener( 'wpcf7mailsent', function( event ) {
5299                var form_id = event.detail.contactFormId;
5300                sendFormAction($(event.target), form_id);
5301            }, false );
5302            //Forminator
5303            $(document).on( 'forminator:form:submit:success', function( event ){
5304                var form_id = $(event.target).find('input[name="form_id"]').val();
5305                sendFormAction($(event.target), form_id);
5306            });
5307
5308            //WPForm
5309            $('form.wpforms-form').on('wpformsAjaxSubmitSuccess', (event) => {
5310                var form_id = $(event.target).attr('data-formid');
5311                sendFormAction($(event.target), form_id);
5312            })
5313            $(document).on( 'frmFormComplete', function( event, form, response ) {
5314                const form_id = $(form).find('input[name="form_id"]').val();
5315                sendFormAction($(event.target), form_id);
5316            });
5317            // Ninja Forms
5318            $(document).onFirst('nfFormSubmitResponse', function (event, data) {
5319                const form_id = data.response.data.form_id;
5320                sendFormAction($(event.target), form_id);
5321            });
5322
5323            var fluentForms = $('form.frm-fluent-form');
5324            fluentForms.each(function() {
5325                var $form = $(this);
5326                $form.on('fluentform_submission_success', function(event) {
5327                    var $formItem = $(this);
5328                    var form_id = $formItem.attr('data-form_id');
5329                    sendFormAction($(event.target), form_id);
5330                });
5331            });
5332
5333        }
5334
5335
5336        // load pixel APIs
5337        Utils.loadPixels();
5338        // setup Enrich content
5339        if(Utils.isCheckoutPage()) {
5340            Utils.addCheckoutFields();
5341        }
5342    }
5343
5344    $(document).ready(function () {
5345        if ( options.dynamicDataUrl ) {
5346            fetch( options.dynamicDataUrl, {
5347                method: 'GET',
5348                credentials: 'same-origin',
5349                cache: 'no-store',
5350                headers: { 'Accept': 'application/json' }
5351            } )
5352                .then( function(r) { return r.ok ? r.json() : Promise.reject(r.status); } )
5353                .then( function(dynamic) {
5354                    options.ajax_event         = dynamic.ajax_event;
5355                    options.cache_bypass       = dynamic.cache_bypass;
5356                    options.tracking_analytics = dynamic.tracking_analytics;
5357                    if ( dynamic.gdpr_dynamic && options.gdpr ) {
5358                        Object.assign( options.gdpr, dynamic.gdpr_dynamic );
5359                        if ( dynamic.gdpr_dynamic.analytics_storage_value !== null )
5360                            options.gdpr.analytics_storage.value = dynamic.gdpr_dynamic.analytics_storage_value;
5361                        if ( dynamic.gdpr_dynamic.ad_storage_value !== null )
5362                            options.gdpr.ad_storage.value = dynamic.gdpr_dynamic.ad_storage_value;
5363                        if ( dynamic.gdpr_dynamic.ad_user_data_value !== null )
5364                            options.gdpr.ad_user_data.value = dynamic.gdpr_dynamic.ad_user_data_value;
5365                        if ( dynamic.gdpr_dynamic.ad_personalization_value !== null )
5366                            options.gdpr.ad_personalization.value = dynamic.gdpr_dynamic.ad_personalization_value;
5367                    }
5368                    options.cookie = dynamic.cookie;
5369
5370                    // Per-pixel user data (advanced matching) that was emptied out of
5371                    // the cached HTML. An inline value that still holds something wins:
5372                    // on pages we never strip (order received / EDD success) the HTML
5373                    // carries the order's own data, which beats the account-level data
5374                    // this endpoint can see.
5375                    if ( dynamic.pixels ) {
5376                        for ( var slug in dynamic.pixels ) {
5377                            if ( ! dynamic.pixels.hasOwnProperty( slug ) || ! options[ slug ] ) continue;
5378                            for ( var key in dynamic.pixels[ slug ] ) {
5379                                if ( ! dynamic.pixels[ slug ].hasOwnProperty( key ) ) continue;
5380                                var inline = options[ slug ][ key ];
5381                                if ( ! inline || Object.keys( inline ).length === 0 ) {
5382                                    options[ slug ][ key ] = dynamic.pixels[ slug ][ key ];
5383                                }
5384                            }
5385                        }
5386                    }
5387
5388                    options.dynamicDataApplied = true;
5389
5390                    if ( window.pys && window.pys.OpenAI
5391                        && typeof window.pys.OpenAI.refreshAdvancedMatching === 'function' ) {
5392                        window.pys.OpenAI.refreshAdvancedMatching();
5393                    }
5394
5395                    pysNormalInit();
5396                } )
5397                .catch( function(err) {
5398                    if ( options.debug ) console.warn( '[PYS] dynamic-options fetch failed:', err );
5399                    options.dynamicDataApplied = true;
5400                    pysNormalInit();
5401                } );
5402        } else {
5403            pysNormalInit();
5404        }
5405    });
5406
5407    var sendFormAction = function (form_target, formId){
5408        var params = {
5409            form_id: formId,
5410            text: form_target.find('[type="submit"]').is('input') ? form_target.find('[type="submit"]').val() :
5411                form_target.find('.forminator-button-submit').text() != '' ? form_target.find('.forminator-button-submit').text() :
5412                    form_target.find('[type="submit"]').text()
5413        };
5414
5415        if (options.dynamicEvents.hasOwnProperty("automatic_event_form")) {
5416            var pixels = Object.keys(options.dynamicEvents.automatic_event_form);
5417            for (var i = 0; i < pixels.length; i++) {
5418                var event = options.dynamicEvents.automatic_event_form[pixels[i]];
5419                if (pixels[i] === "tiktok" || pixels[i] === "reddit") {
5420                    getPixelBySlag(pixels[i]).fireEvent(event.name, event);
5421                } else {
5422                    Utils.copyProperties(params, event.params)
5423                    Utils.copyProperties(Utils.getRequestParams(), event.params);
5424                    getPixelBySlag(pixels[i]).onFormEvent(event);
5425                }
5426            }
5427        }
5428    }
5429
5430}(jQuery, pysOptions);
5431
5432function pys_generate_token() {
5433    return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
5434        (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
5435    );
5436}
5437
5438function getBundlePriceOnSingleProduct(data) {
5439    var items_sum = 0;
5440    jQuery(".bundle_form .bundled_product").each(function(index){
5441        var id = jQuery(this).find(".cart").data("bundled_item_id");
5442        var item_price = data.prices[id];
5443        var item_quantity = jQuery(this).find(".bundled_qty").val();
5444        if(!jQuery(this).hasClass("bundled_item_optional") ||
5445            jQuery(this).find(".bundled_product_optional_checkbox input").prop('checked')) {
5446            items_sum += item_price*item_quantity;
5447        }
5448    });
5449    return items_sum;
5450}
5451function getUrlParameter(sParam) {
5452    var sPageURL = window.location.search.substring(1),
5453        sURLVariables = sPageURL.split('&'),
5454        sParameterName,
5455        i;
5456
5457    for (i = 0; i < sURLVariables.length; i++) {
5458        sParameterName = sURLVariables[i].split('=');
5459
5460        if (sParameterName[0] === sParam) {
5461            // Twin of ServerEventHelper::getUrlParameter(). A parameter with
5462            // no '=' has no value; skip it rather than inventing one, so
5463            // ?fbclid&fbclid=REAL still finds the real one.
5464            if (sParameterName[1] === undefined) {
5465                continue;
5466            }
5467            try {
5468                return decodeURIComponent(sParameterName[1]);
5469            } catch (e) {
5470                // decodeURIComponent throws on a malformed escape ("%",
5471                // "%zz", "a%2"). Return the raw value instead of throwing,
5472                // matching PHP's urldecode(), which is lenient about it.
5473                return sParameterName[1];
5474            }
5475        }
5476    }
5477    return false;
5478};
5479function getCookieYes(key) {
5480    const cookiesObj = document.cookie
5481        .split(";")
5482        .reduce((ac, cv) => {
5483            const [k, v] = cv.split("=");
5484            if (k && v) ac[k.trim()] = v;
5485            return ac;
5486        }, {});
5487    const consentCookie = cookiesObj["cookieyes-consent"] || cookiesObj["wt_consent"];
5488    if (!consentCookie) return undefined;
5489    const { [key]: value } = consentCookie
5490        .split(",")
5491        .reduce((obj, pair) => {
5492            const [k, v] = pair.split(":");
5493            if (k && v) obj[k] = v;
5494            return obj;
5495        }, {});
5496    return value;
5497}
5498function getRootDomain(useSubdomain = false) {
5499    const hostname = window.location.hostname; // Get the current hostname
5500    // Check if tldjs is defined before using it
5501    if (typeof tldjs === "undefined") {
5502        console.warn("tldjs is not defined");
5503        return hostname; // Return hostname as a fallback
5504    }
5505
5506    const rootDomain = tldjs.getDomain(hostname); // Use tldjs to extract the root domain
5507
5508    // Return the root domain with or without a leading dot based on useSubdomain
5509    return rootDomain ? (useSubdomain ? '.' + rootDomain : rootDomain) : hostname;
5510}

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.