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