PageSourceSearch

https://plus2clothing.com/wp-content/plugins/pixelyoursite/dist/scripts/public.js?ver=11.4.0

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

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.