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