PageSourceSearch

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

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

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.