PageSourceSearch

https://www.gopeople.com.au/wp-content/plugins/pixelyoursite/dist/scripts/public.js?x78855

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

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.