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