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