1/** 2 * WordPress localize object mapped to a constant. 3 */ 4const _wccStore = window._wccConfig; 5const _wccStyle = window._wccStyles; 6 7// Define currentLaw as a global variable 8let currentLaw = window._wccConfig._lawSelected[0]; 9 10// Iniitialize showBanner as true 11let showBanner = true; 12_wccStore._backupNodes = []; 13_wccStore._resetConsentID = false; 14_wccStore._bannerState = false; 15_wccStore._preferenceOriginTag = false; 16_wccStore._poweredByHidden = false; 17_wccStore._consentDetailsHidden = false; 18 19_wccStore._cookiePath = (function() { 20 try { 21 const currentPathname = window.location.pathname; 22 23 if (_wccStore._publicURL) { 24 try { 25 const publicURL = new URL(_wccStore._publicURL); 26 const publicPathname = publicURL.pathname.replace(/\/+$/, ''); 27 28 if (publicPathname && publicPathname !== '/' && currentPathname.startsWith(publicPathname)) { 29 return publicPathname + '/'; 30 } 31 } catch (e) { 32 // Ignore parsing errors 33 } 34 } 35 36 return '/'; 37 } catch (e) { 38 console.warn('Failed to detect cookie path:', e); 39 return '/'; 40 } 41})(); 42 43window.webtoffee = window.webtoffee || {}; 44const ref = window.webtoffee; 45ref._wccConsentStore = new Map(); 46 47ref._wccGetCookieMap = function () { 48 const cookieMap = {}; 49 try { 50 document.cookie.split(";
50").forEach((cookie) => { 51 const [key, value] = cookie.split("="); 52 if (!key) return; 53 const trimmedKey = key.trim(); 54 55 if (trimmedKey === 'wt_consent' && cookieMap[trimmedKey]) { 56 return; 57 } 58 59 cookieMap[trimmedKey] = value; 60 }); 61 } catch (error) { } 62 return cookieMap; 63}; 64 65const currentCookieMap = ref._wccGetCookieMap(); 66 67ref._wccGetFromStore = function (key) { 68 return ref._wccConsentStore.get(key) || ""; 69}; 70 71ref._wccSetInStore = function (key, value) { 72 ref._wccConsentStore.set(key, value); 73 const consentPath = _wccStore._enableConsentSharing ? '/' : _wccStore._cookiePath; 74 ref._wccConsentStore.set('cookie_path', consentPath); 75 76 const cookieStringArray = Array.from(ref._wccConsentStore, ([k, v]) => `${k}:${v}`); 77 const scriptExpiry = _wccStore?._bannerConfig?.[currentLaw]?.settings?.consentExpiry || 365; 78 79 ref._wccSetCookie("wt_consent", cookieStringArray.join(","), scriptExpiry, undefined, undefined, consentPath); 80}; 81 82const webtoffeeConsentMap = (currentCookieMap["wt_consent"] || "") 83 .split(",") 84 .reduce((prev, curr) => { 85 if (!curr) return prev; 86 const [key, value] = curr.split(":"); 87 prev[key] = value; 88 return prev; 89 }, {}); 90 91const cookiePath = webtoffeeConsentMap['cookie_path']; 92const isCorrectPath = _wccStore._enableConsentSharing && currentCookieMap["wt_consent"] 93 ? true 94 : cookiePath 95 ? cookiePath === _wccStore._cookiePath 96 : _wccStore._cookiePath === '/' && currentCookieMap["wt_consent"]; 97 98const consentKeys = ["consent", "action"].concat(_wccStore._categories.map(({ slug }) => slug)); 99const consentValue = isCorrectPath ? (item) => webtoffeeConsentMap[item] || "" : () => ""; 100 101consentKeys.forEach(item => ref._wccConsentStore.set(item, consentValue(item))); 102if (isCorrectPath) { 103 ref._wccConsentStore.set('cookie_path', _wccStore._enableConsentSharing ? '/' : _wccStore._cookiePath); 104} 105/** 106 * Get the value of cookie by it's name. 107 * 108 * @param {string} name Name of the cookie 109 * @returns {string} 110 */ 111ref._wccGetCookie = function (name) { 112 const cookies = document.cookie.split(';'); 113 for (let i = 0; i < cookies.length; i++) { 114 const cookie = cookies[i].trim(); 115 if (cookie.startsWith(name + '=')) { 116 return decodeURIComponent(cookie.substring(name.length + 1)); 117 } 118 } 119 return null; 120} 121 122/** 123 * Set a cookie on document.cookie object. 124 * 125 * @param {*} name Name of the cookie. 126 * @param {*} value Value to be set. 127 * @param {*} days Expiry in days. 128 * @param {*} domain Cookie domain. 129 * @param {*} ssl SSL enabled. 130 * @param {*} path Cookie path (defaults to detected path for multisite support). 131 */ 132ref._wccSetCookie = function (name, value, days = 0, domain = _wccStore._rootDomain, ssl = _wccStore._ssl, path = _wccStore._cookiePath) { 133 const date = new Date(); 134 const domainStr = domain ? `domain=${domain}` : ''; 135 const sslStr = ssl ? `secure` : ''; 136 const toSetTime = days === 0 ? 0 : date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); 137 138 document.cookie = `${name}=${value}; expires=${new Date(toSetTime).toUTCString()}; path=${path};${domainStr}; SameSite=Strict;${sslStr};`; 139} 140 141function _wccSetConsentID() { 142 const cookieyesID = ref._wccGetFromStore("consentid"); 143 if (cookieyesID) return; 144 const consentID = ref._wccRandomString(32); 145 ref._wccSetInStore("consentid", consentID); 146 _wccStore._resetConsentID = true; 147} 148function _wccSetConsentTime() { 149 const consentTime = new Date().getTime(); // Get current time if consent time is not available 150 ref._wccSetInStore("consent_time", consentTime); 151} 152 153function _wccRenewConsent() { 154 ref._wccSetInStore("action", ""); 155} 156 157_revisitWccConsent = function () { 158 const type = _wccGetType(); 159 if (type === 'classic') { 160 _wccShowBanner(); 161 } 162 _wccSetPreferenceAction(); 163 _wccToggleRevisit(); 164}; 165/** 166 * Search an element by it's data-tag attribute 167 * 168 * @param {string} tag data-tag of an element. 169 * @returns {object} 170 */ 171function _wccGetElementByTag(tag) { 172 const item = document.querySelector('[data-tag=' + tag + ']'); 173 return item ? item : false;
174} 175 176function _wccEscapeRegex(literal) { 177 return literal.replace(/[.*+?^${}()[\]\\]/g, "\\$&"); 178} 179 180/** 181 * Bind click event to banner elements. 182 * 183 * @param {string} tag data-tag of the element 184 * @param {function} fn callback function 185 */ 186function _wccAttachListener(selector, fn) { 187 const item = _wccFindElement(selector); 188 item && item.addEventListener("click", fn); 189} 190function _wccClassRemove() { 191 return _wccClassAction("remove", ...arguments); 192} 193 194function _wccClassToggle() { 195 return _wccClassAction("toggle", ...arguments); 196} 197 198function _wccClassAction(action, selector, className, forParent = true) { 199 const item = _wccFindElement(selector, forParent); 200 return item && item.classList[action](className); 201} 202 203function _wccFindElement(selector, forParent) { 204 let createdSelector = selector; 205 switch (true) { 206 case selector.startsWith("="): 207 createdSelector = `[data-tag="${selector.substring(1)}"]`; 208 default: 209 break; 210 } 211 const element = document.querySelector(createdSelector); 212 if (!element || (forParent && !element.parentElement)) return null; 213 return forParent ? element.parentElement : element; 214} 215/** 216 * Remove an element from the DOM. 217 * 218 * @param {string} tag data-tag of the element. 219 */ 220function _wccRemoveElement(tag) { 221 const item = _wccGetElementByTag(tag); 222 item && item.remove(); 223} 224 225/** 226 * Remove styles by it's id. 227 */ 228function _wccRemoveStyles() { 229 const item = document.getElementById('wcc-style-inline'); 230 item && item.remove(); 231} 232 233/** 234 * Generate a random string for logging purposes. 235 * 236 * @param {integer} length Length of the string to be generated. 237 * @returns 238 */ 239ref._wccRandomString = function (length, allChars = true) { 240 const chars = `${allChars ? `0123456789` : "" 241 }ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghiklmnopqrstuvwxyz`; 242 const response = []; 243 for (let i = 0; i < length; i++) 244 response.push(chars[Math.floor(Math.random() * chars.length)]); 245 if (!allChars) return response.join(""); 246 return btoa(response.join("")).replace(/\=+$/, ""); 247} 248 249/** 250 * Remove banner if necessary. 251 */ 252function _wccRemoveBanner() { 253 _wccHideBanner(); 254 if (_wccStore._bannerConfig[currentLaw].config.revisitConsent.status === true) { 255 _wccShowRevisit(); 256 } 257 if(_wccGetType() === 'popup') { 258 //Remove the overlay when consent updated 259 _wccHideOverLay(); 260 //Remove wcc-popup-overflow class from <body> 261 if (document.body && document.body.classList.contains('wcc-popup-overflow')) { 262 document.body.classList.remove('wcc-popup-overflow'); 263 } 264 } 265} 266 267/** 268 * Initialize the plugin front-end operations. 269 * 270 * @returns {boolean} 271 */ 272async function _wccInitOperations() { 273 await _wccSetBanner(); 274 _wccAttachNoticeStyles(); 275 _wccAttachShortCodeStyles(); 276 _wccRenderBanner(); 277 _wccSetShowMoreLess(); 278 // Determine consent renewed time 279 let renew_date = _wccStore._renewConsent; 280 // Update the consent time based on the last modified time of "wt_consent" cookie 281 const consent_time = _wccGetCookieLastModified("wt_consent"); 282 // Check whether consent renewed date greater than current time 283 if (!ref._wccGetFromStore("action") || _wccPreviewEnabled() || renew_date && renew_date > consent_time) { 284 _wccShowBanner(); 285 _wccSetInitialState(); 286 if (renew_date && renew_date > consent_time) { 287 _wccAcceptReject("reject"); 288 _wccRenewConsent(); 289 } 290 } else { 291 _wccRemoveBanner(); 292 } 293} 294function _wccPreviewEnabled() { 295 let params = (new URL(document.location)).searchParams; 296 return params.get("wcc_preview") && params.get("wcc_preview") === 'true'; 297} 298/** 299 * Sets up the cookie consent banner based on visitor's location and applicable privacy laws (GDPR/CCPA) 300 */ 301async function _wccSetBanner() { 302 const bannerConfig = _wccStore._bannerConfig; 303 const availableLaws = Object.keys(bannerConfig); 304 305 // Initialize variables 306 let country = ''; 307 let is_eu = false; 308 let region_code = ''; 309 // Get allowed regions for CCPA and GDPR.
310 let ccpaAllowedRegions = _wccStore._ccpaAllowedRegions; 311 let gdprAllowedRegions = _wccStore._gdprAllowedRegions; 312 313 314 // Check if any law requires geolocation 315 const needsGeolocation = availableLaws.some(law => 316 bannerConfig[law].settings.selectedRegion !== 'ALL' 317 ); 318 319 // Only fetch visitor data if geolocation is needed 320 if (needsGeolocation) { 321 const visitor = await _wccGetCountryFromVisitorData(); 322 if (visitor) { 323 country = visitor.country; 324 is_eu = visitor.in_eu; 325 region_code = visitor.region_code; 326 } 327 } 328 329 if (availableLaws.length === 1) { 330 currentLaw = availableLaws[0]; 331 let bannerEnabled = _wccBannerEnabled(); 332 if (!bannerEnabled) { 333 showBanner = false; 334 } 335 } else { 336 // Multiple laws - determine which one applies 337 if (country === 'EU' || is_eu) { 338 currentLaw = 'GDPR'; 339 } else if (country === 'US') { 340 currentLaw = 'CCPA'; 341 } else { 342 // Default to first available law if no specific conditions met 343 currentLaw = availableLaws[0]; 344 } 345 } 346 347 let selectedRegion = _wccSelectedRegion(); 348 let bannerEnabled = _wccBannerEnabled(); 349 350 // Handle GDPR specific logic 351 if (currentLaw === 'GDPR') { 352 if (!bannerEnabled) { 353 showBanner = false; 354 } else if (selectedRegion !== 'ALL') { 355 if (Array.isArray(gdprAllowedRegions) && gdprAllowedRegions.length > 0) { 356 // Check if visitor's country is in allowed regions 357 showBanner = gdprAllowedRegions.includes(country); 358 } else { 359 // Fallback to original logic if gdprAllowedRegions is empty 360 showBanner = is_eu; 361 } 362 } 363 } 364 // Handle CCPA specific logic 365 else if (currentLaw === 'CCPA') { 366 if (!bannerEnabled) { 367 showBanner = false; 368 } else if (selectedRegion !== 'ALL') { 369 if (Array.isArray(ccpaAllowedRegions) && ccpaAllowedRegions.length > 0) { 370 // Only proceed if visitor is from US 371 if (country === 'US') { 372 // Check if visitor's region code is in allowed regions 373 showBanner = ccpaAllowedRegions.includes(region_code); 374 } else { 375 showBanner = false; 376 } 377 } else { 378 // Fallback to original logic if ccpaAllowedRegions is empty 379 showBanner = _wccIsVisitorInCalifornia(country, region_code); 380 } 381 } 382 } 383} 384/** 385 * Sets the initial state of the plugin. 386 */ 387function _wccSetInitialState() { 388 const activeLaw = _wccGetLaw(); 389 const ccpaCheckBoxValue = _wccFindCheckBoxValue(); 390 // CCPA: opt-out checkbox checked = sale consent revoked ("no"); unchecked = "yes". 391 if (activeLaw === "ccpa") { 392 ref._wccSetInStore("consent", ccpaCheckBoxValue ? "no" : "yes"); 393 } else { 394 ref._wccSetInStore("consent", "no"); 395 } 396 const responseCategories = { accepted: [], rejected: [] }; 397 398 for (const category of _wccStore._categories) { 399 let valueToSet = "yes"; 400 401 if (showBanner) { 402 if ( 403 (activeLaw === "gdpr" && 404 !category.isNecessary && 405 !category.defaultConsent.gdpr) || 406 (activeLaw === "ccpa" && 407 ccpaCheckBoxValue && 408 !category.defaultConsent.ccpa) 409 ) { 410 valueToSet = "no"; 411 } 412 } 413 414 if (valueToSet === "no") { 415 responseCategories.rejected.push(category.slug); 416 } else { 417 responseCategories.accepted.push(category.slug); 418 } 419 420 ref._wccSetInStore(`${category.slug}`, valueToSet); 421 } 422 423 _wccUnblock(); 424 _wccSendConsentEvent(responseCategories); 425} 426 427/** 428 * Add a class based on the banner type and position. Eg: 'wcc-banner-top' 429 * 430 * @returns {boolean} 431 */ 432function _wccAddPositionClass() { 433 const notice = _wccGetElementByTag('notice'); 434 if (!notice) return false;
435 const container = notice.closest('.wcc-consent-container'); 436 if (!container) return false; 437 const type = _wccStore._bannerConfig[currentLaw].settings.type; 438 let position = _wccStore._bannerConfig[currentLaw].settings.position; 439 if (type === 'popup') { 440 position = 'center'; 441 //Show overlay when popup visible 442 _wccShowOverLay(); 443 //Add wcc-popup-overflow class to <body> to prevent scroll when layout type is popup 444 document.body.classList.add('wcc-popup-overflow'); 445 } 446 const noticeClass = 'wcc' + '-' + type + '-' + position; 447 container.classList.add(noticeClass); 448 const revisitConsent = _wccGetElementByTag('revisit-consent'); 449 if (!revisitConsent) return false; 450 const revisitPosition = `wcc-revisit-${_wccStore._bannerConfig[currentLaw].config.revisitConsent.position}`; 451 revisitConsent.classList.add(revisitPosition); 452} 453 454/** 455 * Initialize the plugin operations. 456 */ 457async function _wccInit() { 458 try { 459 _wccInitOperations(); 460 _wccRemoveAllDeadCookies(); 461 _wccWatchBannerElement(); 462 _wccApplyCustomPlaceholders(); 463 } catch (err) { 464 console.error(err); 465 } 466} 467 468/** 469 * Domready event, alternative to jQuery(document).ready() function 470 * 471 * @param {function} callback 472 * @returns 473 */ 474function _wccDomReady(callback) { 475 if (typeof document === 'undefined') { 476 return; 477 } 478 if (document.readyState === 'complete' || /** DOMContentLoaded + Images/Styles/etc loaded, so we call directly. */ 479 document.readyState === 'interactive' /** DOMContentLoaded fires at this point, so we call directly. */ 480 ) { 481 return void callback(); 482 } /** DOMContentLoaded has not fired yet, delay callback until then. */ 483 document.addEventListener('DOMContentLoaded', callback); 484} 485 486/** 487 * Callback function to Domready event. 488 */ 489_wccDomReady(async function () { 490 try { 491 await _wccInit(); 492 } catch (err) { 493 console.error(err); 494 } 495}); 496 497/** 498 * Register event handler for all the action elements. 499 */ 500function _wccRegisterListeners() { 501 for (const { slug } of _wccStore._categories) { 502 _wccAttachListener('detail-category-title', () => 503 document 504 .getElementById(`wccCategory${slug}`) 505 .classList.toggle("wcc-tab-active") 506 ); 507 } 508 _wccAttachListener("=settings-button", () => _wccSetPreferenceAction('settings-button')); 509 _wccAttachListener("=detail-close", () => _wccHidePreferenceCenter()); 510 _wccAttachListener("=optout-cancel-button", () => _wccHidePreferenceCenter()); 511 _wccAttachListener("=close-button", () => _wccActionClose()); 512 _wccAttachListener("=donotsell-button", () => _wccSetPreferenceAction('donotsell-button')); 513 _wccAttachListener("=reject-button", _wccAcceptReject("reject")); 514 _wccAttachListener("=accept-button", _wccAcceptReject("all")); 515 _wccAttachListener("=detail-accept-button", _wccAcceptReject("all")); 516 _wccAttachListener("=detail-save-button", _wccAcceptReject()); 517 _wccAttachListener("=detail-category-preview-save-button", _wccAcceptReject()); 518 _wccAttachListener("=optout-confirm-button", _wccAcceptReject()); 519 _wccAttachListener("=detail-reject-button", _wccAcceptReject("reject")); 520 _wccAttachListener("=revisit-consent", () => _revisitWccConsent()); 521 _wccAttachListener("=optout-close", () => _wccHidePreferenceCenter()); 522 _wccAttachListener("=category-tab", () => _wccShowTab('wccIABSectionCookie')); 523 _wccAttachListener("=purpose-tab", () => _wccShowTab('wccIABSectionPurpose')); 524 _wccAttachListener("=vendor-tab", () => _wccShowTab('wccIABSectionVendor')); 525 _wccAttachListener('#wcc-iab-notice-toggle', () => _wccShowPreferenceCenter()); 526 _wccInitiAccordionTabs(); 527 _wccInitPreferenceButton(); 528 _wccAttachCategoryListeners(); 529 530} 531function _wccAttachCategoryListeners() { 532 if (!_wccStore._bannerConfig[currentLaw].config.auditTable.status) return; 533 534 const categoryNames = _wccStore._categories.map(({ slug }) => slug); 535 const sync = (root, expanded) => { 536 if (!root) return; 537 const btn = root.querySelector('.wcc-accordion-btn'); 538 const body = root.querySelector('.wcc-accordion-body'); 539 root.removeAttribute('aria-expanded'); 540 if (btn) btn.setAttribute('aria-expanded', expanded ? 'true' : 'false');
541 if (body) body.setAttribute('aria-hidden', expanded ? 'false' : 'true'); 542 }; 543 544 categoryNames.map((category) => { 545 const selector = `#wccDetailCategory${category}`; 546 _wccAttachListener(selector, ({ target: { id } }) => { 547 if (id === `wccSwitch${category}`) return; 548 const accordion = document.querySelector(selector); 549 if (!accordion) return; 550 const isActive = accordion.classList.toggle('wcc-accordion-active'); 551 sync(accordion, isActive); 552 categoryNames 553 .filter((categoryName) => categoryName !== category) 554 .map((filteredName) => { 555 const other = document.querySelector(`#wccDetailCategory${filteredName}`); 556 if (!other) return; 557 other.classList.remove('wcc-accordion-active'); 558 sync(other, false); 559 }); 560 }); 561 }); 562} 563/** 564 * Add support for accordion tabs on the privacy overview screen. 565 */ 566function _wccInitiAccordionTabs() { 567 _wccIabInnerAccordionClickListener('wccIABSectionPurpose'); 568 _wccIabInnerAccordionClickListener('wccIABSectionVendor'); 569 570 document.querySelectorAll(".wcc-child-accordion .wcc-child-accordion-item").forEach((item) => ( 571 item.addEventListener('click', function (event) { 572 if (event.target.type === 'checkbox') return; 573 574 const accordion = this.parentNode; 575 const btn = accordion.querySelector('.wcc-child-accordion-btn'); 576 const body = accordion.querySelector('.wcc-child-accordion-body'); 577 const isActive = accordion.classList.toggle('wcc-child-accordion-active'); 578 accordion.removeAttribute('aria-expanded'); 579 if (btn) btn.setAttribute('aria-expanded', isActive ? 'true' : 'false'); 580 if (body) body.setAttribute('aria-hidden', isActive ? 'false' : 'true'); 581 }) 582 )); 583 584 // Enable / disable all purposes/features consent based on user action 585 const checkboxes = document.querySelectorAll('.wcc-consents-checkbox'); 586 checkboxes.forEach(function (checkbox) { 587 checkbox.addEventListener('change', wcc_enable_disable_consent); 588 }); 589} 590 591/** 592 * This function selects all elements with the class 'wcc-preference-btn', assigns a unique ID to each button, 593 * and adds a click event listener that triggers the `_revisitWccConsent` function. 594 * 595 */ 596function _wccInitPreferenceButton() { 597 const preferenceButtons = document.querySelectorAll('.wcc-preference-btn'); // Select all buttons with the class 598 preferenceButtons.forEach((button) => { 599 // Generate a unique ID for each preference button 600 const uniqueID = ref._wccRandomString(8, false); 601 // Set the unique ID 602 button.id = uniqueID; 603 // Add an event listener for each button 604 button.addEventListener('click', function () { 605 _revisitWccConsent(); 606 }); 607 }); 608} 609 610function wcc_enable_disable_consent() { 611 let el = this.getAttribute('data-tag'); 612 const nestedInputs = document.querySelectorAll('.wcc-' + el + '-checkbox, .wcc-' + el + '-legitimate-interests-checkbox'); 613 const isChecked = this.checked; 614 nestedInputs.forEach(input => { 615 input.checked = isChecked; 616 }); 617} 618function _wccIabInnerAccordionClickListener(containerId) { 619 const accordionItems = document.querySelectorAll(`#${containerId} .wcc-accordion .wcc-accordion-iab-item`); 620 accordionItems.forEach((item) => { 621 item.addEventListener('click', function (event) { 622 if (event.target.type === 'checkbox') return; 623 const accordion = this.parentNode; 624 const btn = this.querySelector('.wcc-accordion-btn'); 625 const body = accordion.querySelector('.wcc-accordion-body'); 626 accordion.classList.toggle('wcc-accordion-active'); 627 const isActive = accordion.classList.toggle('wcc-child-accordion-active'); 628 accordion.removeAttribute('aria-expanded'); 629 if (btn) btn.setAttribute('aria-expanded', isActive ? 'true' : 'false'); 630 if (body) body.setAttribute('aria-hidden', isActive ? 'false' : 'true'); 631 }); 632 }); 633} 634 635function _wccToggleBanner(force = false) { 636 const notice = _wccGetElementByTag('notice'); 637 const container = notice && notice.closest('.wcc-consent-container') || false; 638 if (container) { 639 force === true ? container.classList.add('wcc-hide') : container.classList.toggle('wcc-hide'); 640 } 641} 642 643function _wccToggleRevisit(force = false) { 644 const revisit = _wccGetRevisit(); 645 if (revisit) { 646 force === true ? _wccRevisitHide() : revisit.classList.toggle('wcc-revisit-hide'); 647 } 648} 649function _wccGetLaw() { 650 return _wccStore._bannerConfig[currentLaw].settings.applicableLaw; 651} 652function _wccSelectedRegion() { 653 return _wccStore._bannerConfig[currentLaw].settings.selectedRegion; 654} 655function _wccBannerEnabled() { 656 return _wccStore._bannerConfig[currentLaw].settings.bannerEnabled; 657} 658function _wccPoweredByEnabled() { 659 return _wccStore._bannerConfig[currentLaw].settings.poweredBy; 660} 661function _wccGetType() { 662 return _wccStore._bannerConfig[currentLaw].settings.type; 663} 664function _wccGetBanner() { 665 const notice = _wccGetElementByTag('notice'); 666 const container = notice && notice.closest('.wcc-consent-container') || false; 667 return container && container || false; 668} 669function _wccHideBanner() { 670 const notice = _wccGetBanner(); 671 notice && notice.classList.add('wcc-hide'); 672} 673function _wccShowBanner() { 674 const notice = _wccGetBanner(); 675 notice && notice.classList.remove('wcc-hide'); 676} 677function _wccHideOverLay() {
678 const overlay = document.querySelector('.wcc-overlay'); 679 overlay && overlay.classList.add('wcc-hide'); 680} 681function _wccShowOverLay() { 682 const overlay = document.querySelector('.wcc-overlay'); 683 overlay && overlay.classList.remove('wcc-hide'); 684} 685function _wccToggleOverLay() { 686 const overlay = document.querySelector('.wcc-overlay'); 687 overlay && overlay.classList.toggle('wcc-hide'); 688} 689function _wccGetPreferenceCenter() { 690 if (_wccGetType() === 'classic') { 691 return _wccGetBanner(); 692 } 693 let element = _wccGetLaw() === 'ccpa' ? _wccGetElementByTag("optout-popup") : _wccGetElementByTag("detail"); 694 return element && element.closest('.wcc-modal') || false; 695} 696function _wccHidePreferenceCenter() { 697 const element = _wccGetPreferenceCenter(); 698 element && element.classList.remove(_wccGetPreferenceClass()); 699 if (_wccGetType() !== 'classic') { 700 _wccHideOverLay(); 701 if (!ref._wccGetFromStore("action")) _wccShowBanner(); 702 } 703 if (ref._wccGetFromStore("action")) _wccShowRevisit(); 704 const origin = _wccStore._preferenceOriginTag; 705 origin && _wccSetFocus(origin) 706} 707function _wccShowPreferenceCenter() { 708 const element = _wccGetPreferenceCenter(); 709 element && element.classList.add(_wccGetPreferenceClass()); 710 if (_wccGetType() !== 'classic') { 711 _wccShowOverLay(); 712 _wccHideBanner(); 713 } 714} 715function _wccTogglePreferenceCenter() { 716 const element = _wccGetPreferenceCenter(); 717 element && element.classList.toggle(_wccGetPreferenceClass()); 718 if (_wccGetType() !== 'classic') _wccToggleOverLay(); 719} 720function _wccGetPreferenceClass() { 721 return _wccGetType() === 'classic' ? 'wcc-consent-bar-expand' : 'wcc-modal-open'; 722} 723function _wccGetRevisit() { 724 const revisit = _wccGetElementByTag('revisit-consent'); 725 return revisit && revisit || false; 726} 727function _wccHideRevisit() { 728 const revisit = _wccGetRevisit(); 729 revisit && revisit.classList.add('wcc-revisit-hide') 730} 731function _wccShowRevisit() { 732 const revisit = _wccGetRevisit(); 733 revisit && revisit.classList.remove('wcc-revisit-hide') 734} 735function _wccSetPreferenceAction(tagName = false) { 736 _wccStore._preferenceOriginTag = tagName; 737 if (_wccGetType() === 'classic') { 738 _wccTogglePreferenceCenter(); 739 } else { 740 _wccShowPreferenceCenter(); 741 } 742} 743// Define section names array 744const sectionNames = ['wccIABSectionCookie', 'wccIABSectionPurpose', 'wccIABSectionVendor']; 745function _wccShowTab(tagName = false) { 746 sectionNames.forEach(validTagName => { 747 _wccNavBar(); 748 const tab = _wccGetTab(validTagName); 749 if (validTagName === tagName) { 750 tab && tab.classList.remove('wcc-hide'); 751 } else { 752 tab && tab.classList.add('wcc-hide'); 753 } 754 }); 755} 756function _wccNavBar() { 757 // Get all buttons 758 const buttons = document.querySelectorAll('.wcc-iab-nav-item'); 759 760 // Add click event listeners to each button 761 buttons.forEach(button => { 762 button.addEventListener('click', function () { 763 // Remove 'wcc-iab-nav-item-active' from all buttons 764 buttons.forEach(btn => btn.classList.remove('wcc-iab-nav-item-active')); 765 766 // Add 'wcc-iab-nav-item-active' only to the clicked button 767 this.classList.add('wcc-iab-nav-item-active'); 768 }); 769 }); 770} 771function _wccGetTab(tagName = false) { 772 const tab = _wccGetElementByTag(tagName); 773 return tab && tab || false; 774} 775/** 776 * Replace footer shadow with current preference center background. 777 * 778 * @param {object} $doc Dom node. 779 * @returns 780 */ 781function _wccSetFooterShadow($doc) { 782 const footer = $doc.querySelector('[data-tag="detail"] .wcc-footer-shadow'); 783 const preference = $doc.querySelector('[data-tag="detail"]'); 784 if (!footer) return; 785 const background = preference && preference.style.backgroundColor || '#ffffff'; 786 footer.style.background = `linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, ${background 787 } 100%)`; 788} 789 790/** 791 * Remove all the rejected cookies. 792 * 793 * @param {object} cookies Cookies list. 794 */
795function _wccRemoveDeadCookies({ cookies }) { 796 const currentCookieMap = ref._wccGetCookieMap(); 797 for (const { cookieID, domain } of cookies) { 798 for (const existingCookieID in currentCookieMap) { 799 // Check if the cookieID matches exactly or if it matches a wildcard pattern 800 const isMatch = 801 existingCookieID === cookieID || 802 (cookieID.includes('*') && new RegExp(`^${cookieID.replace('*', '.*')}$`).test(existingCookieID)); 803 804 if (isMatch) { 805 [domain, ""].map((cookieDomain) => 806 ref._wccSetCookie(existingCookieID, "", 0, cookieDomain) 807 ); 808 } 809 } 810 } 811} 812function _wccSetPreferenceCheckBoxStates(revisit = false) { 813 for (const category of _wccStore._categories) { 814 const cookieValue = ref._wccGetFromStore(category.slug); 815 const checked = 816 cookieValue === "yes" || 817 (!cookieValue && 818 category.defaultConsent[_wccGetLaw()]) || category.isNecessary; 819 820 const disabled = category.isNecessary; 821 const shortCodeData = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 822 (code) => code.key === 'wcc_category_toggle_label' 823 ); 824 const toggleTextFormatted = shortCodeData.content.replace( 825 `[wcc_preference_{{category_slug}}_title]`, 826 category.name 827 ); 828 _wccSetCheckboxes( 829 category, 830 checked, 831 disabled, 832 toggleTextFormatted, 833 revisit 834 ); 835 _wccSetPreferenceState(category); 836 } 837} 838 839function _wccSetCheckboxes( 840 category, 841 checked, 842 disabled, 843 formattedLabel, 844 revisit = false 845) { 846 const toggle = _wccStore._bannerConfig[currentLaw].config.preferenceCenter.toggle; 847 const activeColor = toggle.states.active.styles['background-color']; 848 const inactiveColor = toggle.states.inactive.styles['background-color']; 849 850 [`wccCategoryDirect`, `wccSwitch`].map((key) => { 851 const boxElem = document.getElementById(`${key}${category.slug}`); 852 if (!boxElem) return; 853 _wccSetCategoryToggle( 854 boxElem, 855 category, 856 revisit); 857 boxElem.checked = checked; 858 boxElem.disabled = disabled; 859 boxElem.style.backgroundColor = checked ? activeColor : inactiveColor; 860 _wccSetCheckBoxAriaLabel(boxElem, checked, formattedLabel); 861 if (revisit) return; 862 boxElem.addEventListener("change", ({ currentTarget: elem }) => { 863 const isChecked = elem.checked; 864 elem.style.backgroundColor = isChecked ? activeColor : inactiveColor; 865 _wccSetCheckBoxAriaLabel(boxElem, isChecked, formattedLabel); 866 }); 867 }); 868} 869function _wccSetCategoryToggle(element, category = {}, revisit = false) { 870 if (revisit) return; 871 if (element.parentElement.getAttribute('data-tag') === 'detail-category-toggle') { 872 _wccSetCategoryPreferenceToggle(element, category); 873 } else if (element.parentElement.getAttribute('data-tag') === 'detail-category-preview-toggle') { 874 _wccSetCategoryPreview(element, category); 875 } 876 877} 878function _wccSetCategoryPreferenceToggle(element, category) { 879 let toggleContainer = element.closest('.wcc-accordion-item'); 880 if (!toggleContainer) return; 881 const toggleSwitch = toggleContainer.querySelector('.wcc-switch'); 882 const necessaryText = toggleContainer.querySelector('.wcc-always-active'); 883 if (category.isNecessary) { 884 toggleSwitch && toggleSwitch.remove(); 885 } else { 886 necessaryText && necessaryText.remove(); 887 if (_wccGetType() === 'classic' && _wccStore._bannerConfig[currentLaw].config.categoryPreview.status || (category.cookies && category.cookies.length === 0) && !category.foundNoCookieScript) { 888 toggleSwitch && toggleSwitch.remove(); 889 } 890 } 891} 892function _wccSetPreferenceState(category) { 893 if (_wccStore._bannerConfig[currentLaw].config.auditTable.status === false) { 894 const tableElement = document.querySelector( 895 `#wccDetailCategory${category.slug} [data-tag="audit-table"]` 896 ); 897 tableElement && tableElement.remove(); 898 const chevronElement = document.querySelector( 899 `#wccDetailCategory${category.slug} .wcc-accordion-chevron` 900 ); 901 chevronElement && chevronElement.classList.add("wcc-accordion-chevron-hide"); 902 } 903} 904function _wccSetCategoryPreview(element, category) {
905 if ((category.cookies && category.cookies.length === 0) && !category.isNecessary) 906 element.parentElement.parentElement.remove(); 907} 908 909function _wccSetCheckBoxAriaLabel(boxElem, isChecked, formattedLabel, isCCPA = false) { 910 911 if (!boxElem) return; 912 const keyName = isChecked ? "disable" : "enable"; 913 const textCode = `wcc_${keyName}_${isCCPA ? "optout" : "category"}_label`; 914 const shortCodeData = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 915 (code) => code.key === textCode 916 ); 917 if (!shortCodeData) return; 918 const labelText = formattedLabel 919 .replace(/{{status}}/g, keyName) 920 .replace(`[${textCode}]`, shortCodeData.content); 921 boxElem.setAttribute("aria-label", labelText); 922} 923/** 924 * Render banner after processing. 925 */ 926async function _wccRenderBanner() { 927 const myEvent = new CustomEvent("_wccBannerVisible", { 928 detail: { 929 bannerVisibility: showBanner, // Include showBanner in the detail property 930 currentLaw: currentLaw, 931 }, 932 bubbles: true, 933 cancelable: true, 934 composed: false, 935 }); 936 // Ensure the event listener is added before dispatching the event 937 setTimeout(() => { 938 document.body.dispatchEvent(myEvent); 939 }, 0); 940 941 if (currentLaw && showBanner) { 942 const template = document.getElementById(`wccBannerTemplate_${currentLaw}`); 943 if (template) { 944 const templateHtml = template.innerHTML; 945 const doc = new DOMParser().parseFromString(templateHtml, 'text/html'); 946 _wccSetFooterShadow(doc); 947 document.body.insertAdjacentHTML( 948 "afterbegin", 949 doc.body.innerHTML 950 ); 951 } 952 } 953 _wccSetPreferenceCheckBoxStates(); 954 _wccRegisterListeners(); 955 _wccSetCCPAOptions(); 956 _wccSetNoticeGpcOption(); 957 _wccSetPlaceHolder(); 958 _wccAttachReadMore(); 959 _wccRemoveStyles(); 960 _wccAddPositionClass(); 961 _wccAddRtlClass(); 962 _wccSetPoweredBy(); 963 _wccSetConsentDetails(); 964 _wccSetFooterWrapperStyles(); 965} 966 967async function _wccGetCountryFromVisitorData() { 968 const apiUrl = _wccStore._restApiUrl; 969 const response = await fetch(apiUrl); 970 const data = await response.json(); 971 return data; 972} 973 974/** 975 * Check whether the visitor country is US and If _restrictToCA is true check the region code also. 976 * @since 3.0.2 977 * @returns boolean 978 */ 979function _wccIsVisitorInCalifornia(country, region_code) { 980 const _restricttocalifornia = _wccStore._restrictToCA; 981 // Check if restriction to California is enabled 982 if (_restricttocalifornia) { 983 // Restrict to California only 984 return country === 'US' && region_code === 'CA'; 985 } else { 986 // Only check if country is US 987 return country === 'US'; 988 } 989} 990 991/** 992 * Accept or reject the consent based on the option. 993 * 994 * @param {string} option Type of consent. 995 * @returns {void} 996 */ 997function _wccAcceptReject(option = "custom") { 998 return () => { 999 _wccAcceptCookies(option); 1000 _wccSetConsentID(); 1001 _wccSetConsentTime(); 1002 _wccRemoveBanner(); 1003 _wccHidePreferenceCenter(); 1004 _wccLogConsent(); 1005 _wccAfterConsent(); 1006 }; 1007} 1008 1009function _wccActionClose() { 1010 const actionType = _wccStore._bannerConfig[currentLaw].settings.closeButtonAction; 1011 _wccAcceptReject(actionType)(); 1012} 1013/** 1014 * Consent accept callback. 1015 * 1016 * @param {string} choice Type of consent. 1017 */ 1018function _wccAcceptCookies(choice = "all") { 1019 const activeLaw = _wccGetLaw(); 1020 const ccpaCheckBoxValue = _wccFindCheckBoxValue(); 1021 1022 ref._wccSetInStore("action", "yes"); 1023 if (activeLaw === 'gdpr') { 1024 ref._wccSetInStore("consent", choice === "reject" ? "no" : "yes"); 1025 } else { 1026 ref._wccSetInStore("consent", ccpaCheckBoxValue ? "no" : "yes"); 1027 } 1028 const responseCategories = { accepted: [], rejected: [] }; 1029 for (const category of _wccStore._categories) { 1030 let valueToSet = "no"; 1031 if (activeLaw === 'gdpr') { 1032 valueToSet = 1033 !category.isNecessary && 1034 (choice === "reject" || 1035 (choice === "custom" && !_wccFindCheckBoxValue(category.slug))) 1036 ? "no" 1037 : "yes"; 1038 } else { 1039 valueToSet = (ccpaCheckBoxValue && !category.defaultConsent.ccpa) || (!category.isNecessary && currentLaw === 'CCPA' && choice === "reject") ? "no" : "yes"; 1040 } 1041 ref._wccSetInStore(`${category.slug}`, valueToSet); 1042 if (valueToSet === "no") { 1043 responseCategories.rejected.push(category.slug);
1044 _wccRemoveDeadCookies(category); 1045 } else responseCategories.accepted.push(category.slug); 1046 } 1047 let customEventSupport = _wccStore._customEvents; 1048 // Add GTM event trigger after consent mode flags are set 1049 if (window.dataLayer && customEventSupport) { 1050 window.dataLayer.push({ 1051 "event": "cookie_consent_updated" 1052 }); 1053 } 1054 _wccUnblock(); 1055 _wccReblockExistingEmbeds(); 1056 _wccApplyCustomPlaceholders(); 1057 _wccSendConsentEvent(responseCategories,choice); 1058} 1059function _wccSetShowMoreLess() { 1060 const activeLaw = _wccGetLaw(); 1061 const showCode = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 1062 (code) => code.key === "wcc_show_desc" 1063 ); 1064 const hideCode = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 1065 (code) => code.key === "wcc_hide_desc" 1066 ); 1067 1068 if (!showCode || !hideCode) return; 1069 const hideButtonContent = hideCode.content; 1070 const showButtonContent = showCode.content; 1071 1072 const contentLimit = window.innerWidth < 376 ? 150 : 300; 1073 const element = document.querySelector( 1074 `[data-tag="${activeLaw === "gdpr" ? "detail" : "optout"}-description"]` 1075 ); 1076 if (!element) return; 1077 const content = element.textContent; 1078 if (content.length < contentLimit) return; 1079 const contentHTML = element.innerHTML; 1080 const htmlDoc = new DOMParser().parseFromString(contentHTML, "text/html"); 1081 const innerElements = htmlDoc.querySelectorAll("body > p"); 1082 if (innerElements.length <= 1) return; 1083 let strippedContent = ``; 1084 for (let index = 0; index < innerElements.length; index++) { 1085 if (index === innerElements.length - 1) return; 1086 const element = innerElements[index]; 1087 if (`${strippedContent}${element.outerHTML}`.length > contentLimit) 1088 element.insertAdjacentHTML("beforeend", `... ${showButtonContent}`); 1089 strippedContent = `${strippedContent}${element.outerHTML}`; 1090 if (strippedContent.length > contentLimit) break; 1091 } 1092 function showMoreHandler() { 1093 element.innerHTML = `${contentHTML}${hideButtonContent}`; 1094 _wccAttachListener("=hide-desc-button", showLessHandler); 1095 } 1096 function showLessHandler() { 1097 element.innerHTML = strippedContent; 1098 _wccAttachListener("=show-desc-button", showMoreHandler); 1099 } 1100 showLessHandler(); 1101} 1102/** 1103 * Toggle show more or less on click event. 1104 * 1105 * @param {object} object Object containing toggle buttons and texts. 1106 * @param {*} element Target element. 1107 */ 1108function _wccToggleMoreLess(object, element) { 1109 let { 1110 currentTarget, 1111 target 1112 } = element; 1113 if (target && target.tagName.toUpperCase() !== 'BUTTON') return; 1114 const ariaExpanded = currentTarget.getAttribute('aria-expanded'); 1115 const trimmed = ariaExpanded === 'false'; 1116 let btn = object.btnTrim; 1117 let text = object.originalText; 1118 if (!trimmed) { 1119 btn = object.btnExpand; 1120 text = object.truncatedText; 1121 } 1122 currentTarget.innerHTML = `${text}${btn}`; 1123 currentTarget.ariaExpanded = trimmed; 1124} 1125 1126/** 1127 * Add styles to the shortcode HTML rendered outside of the banner. 1128 * 1129 * @returns {void} 1130 */ 1131function _wccAttachShortCodeStyles() { 1132 const shortCodes = _wccStore._tags; 1133 Array.prototype.forEach.call(shortCodes, function (shortcode) { 1134 document.querySelectorAll('[data-tag=' + shortcode.tag + ']').forEach(function (item) { 1135 let styles = ''; 1136 for (const key in shortcode.styles) { 1137 styles += `${key}: ${shortcode.styles[key]};`; 1138 } 1139 item.style.cssText = styles; 1140 }); 1141 }); 1142} 1143 1144/** Script blocker Version 2 */ 1145 1146 1147const _wccCreateElementBackup = document.createElement; 1148 1149document.createElement = (...args) => { 1150 const createdElement = _wccCreateElementBackup.call(document, ...args); 1151 1152 if (createdElement.nodeName.toLowerCase() !== "script") return createdElement; 1153 1154 const originalSetAttribute = createdElement.setAttribute.bind(createdElement); 1155 1156 // Get the native src descriptor from HTMLScriptElement prototype 1157 const nativeSrcDescriptor = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'src'); 1158 1159 Object.defineProperties(createdElement, { 1160 src: { 1161 configurable: true, 1162 enumerable: true, 1163 get: function () { 1164 // Use native getter for accurate absolute URL 1165 return nativeSrcDescriptor.get.call(createdElement); 1166 }, 1167 set: function (value) { 1168 if (typeof _wccStore !== 'undefined' && _wccShouldChangeType(createdElement, value)) 1169 originalSetAttribute("type", "javascript/blocked"); 1170 originalSetAttribute("src", value); 1171 return true; 1172 }, 1173 }, 1174 type: { 1175 configurable: true, 1176 enumerable: true, 1177 get: function () { 1178 return createdElement.getAttribute("type"); 1179 }, 1180 set: function (value) { 1181 value = (typeof _wccStore !== 'undefined' && _wccShouldChangeType(createdElement)) 1182 ? "javascript/blocked" 1183 : value; 1184 originalSetAttribute("type", value); 1185 return true; 1186 }, 1187 }, 1188 }); 1189 1190 createdElement.setAttribute = (name, value) => { 1191 if (name === "type" || name === "src") 1192 return (createdElement[name] = value); 1193 originalSetAttribute(name, value); 1194 if (name === "data-wcc" && !_wccShouldChangeType(createdElement)) 1195 originalSetAttribute("type", "text/javascript"); 1196 }; 1197 1198 return createdElement; 1199}; 1200 1201function _wccMutationObserver(mutations) {
1202 const urlParams = new URLSearchParams(window.location.search); 1203 if (urlParams.has('wcc_bypass')) return; 1204 1205 for (const { addedNodes } of mutations) { 1206 for (const node of addedNodes) { 1207 try { 1208 // Handle TikTok blockquote elements. 1209 if (node.nodeName === 'BLOCKQUOTE' && node.classList.contains('tiktok-embed')) { 1210 const videoId = node.getAttribute('data-video-id'); 1211 if (!videoId) continue; 1212 1213 const uniqueID = ref._wccRandomString(8, false); 1214 const pseudoIframe = { 1215 nodeName: 'iframe', 1216 src: `https://www.tiktok.com/embed/${videoId}`, 1217 offsetWidth: parseInt(node.style.maxWidth) || 605, 1218 offsetHeight: parseInt(node.style.minHeight) || 700, 1219 insertAdjacentHTML: function (position, html) { 1220 node.insertAdjacentHTML(position, html); 1221 } 1222 }; 1223 1224 const cleanedHostname = _wccCleanHostName('tiktok.com'); 1225 _wccAddProviderToList(node, cleanedHostname); 1226 1227 // Check if element has data-wcc attribute that should prevent blocking. 1228 if (node.hasAttribute('data-wcc') && !_wccIsCategoryToBeBlocked(node.getAttribute('data-wcc'))) { 1229 continue; 1230 } 1231 // Check if provider should be blocked. 1232 if (!_wccShouldBlockProvider(cleanedHostname)) continue; 1233 1234 const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname); 1235 _wccAddPlaceholder(pseudoIframe, uniqueID, category); 1236 1237 const tiktokScript = document.querySelector('script[src*="tiktok.com/embed.js"]'); 1238 if (tiktokScript) tiktokScript.remove(); 1239 1240 node.remove(); 1241 _wccStore._backupNodes.push({ 1242 position: 'body', 1243 node: node.cloneNode(true), 1244 uniqueID 1245 }); 1246 continue; 1247 } 1248 1249 //Handle Elementor video widget (YouTube, Vimeo, Dailymotion, VideoPress, Self Hosted). 1250 if (node.nodeType === 1 && node.classList.contains('elementor-video')) { 1251 const widgetContainer = node.closest('.elementor-widget-video'); 1252 if (!widgetContainer) continue; 1253 1254 const settings = widgetContainer.getAttribute('data-settings'); 1255 if (!settings) continue; 1256 1257 try { 1258 const parsed = JSON.parse(settings.replace(/"/g, '"')); 1259 const videoType = parsed.video_type || 'youtube'; 1260 1261 let videoUrl = ''; 1262 let hostname = ''; 1263 let videoId = ''; 1264 let embedUrl = ''; 1265 let externalUrlEnabled = false; 1266 1267 // Handle different video types. 1268 switch (videoType) { 1269 case 'youtube': 1270 videoUrl = parsed.youtube_url; 1271 if (!videoUrl || typeof videoUrl !== 'string') continue; 1272 const ytMatch = videoUrl.match(/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([\w-]{11})/); 1273 if (!ytMatch) continue; 1274 videoId = ytMatch[1]; 1275 hostname = 'youtube.com'; 1276 embedUrl = `https://www.youtube.com/embed/${videoId}`; 1277 break; 1278 1279 case 'vimeo': 1280 videoUrl = parsed.vimeo_url; 1281 if (!videoUrl || typeof videoUrl !== 'string') continue; 1282 const vimeoMatch = videoUrl.match(/(?:vimeo\.com\/)(\d+)/); 1283 if (!vimeoMatch) continue; 1284 videoId = vimeoMatch[1]; 1285 hostname = 'vimeo.com'; 1286 embedUrl = `https://player.vimeo.com/video/${videoId}`; 1287 break; 1288 1289 case 'dailymotion': 1290 videoUrl = parsed.dailymotion_url; 1291 if (!videoUrl || typeof videoUrl !== 'string') continue; 1292 const dmMatch = videoUrl.match(/(?:dailymotion\.com\/video\/)([a-zA-Z0-9]+)/); 1293 if (!dmMatch) continue; 1294 videoId = dmMatch[1]; 1295 hostname = 'dailymotion.com'; 1296 embedUrl = `https://www.dailymotion.com/embed/video/${videoId}`; 1297 break; 1298 1299 case 'videopress': 1300 videoUrl = parsed.videopress_url; 1301 if (!videoUrl || typeof videoUrl !== 'string') continue; 1302 const vpMatch = videoUrl.match(/(?:videopress\.com\/v\/)([a-zA-Z0-9]+)/); 1303 if (!vpMatch) continue; 1304 videoId = vpMatch[1]; 1305 hostname = 'videopress.com'; 1306 embedUrl = `https://videopress.com/embed/${videoId}`; 1307 break; 1308 1309 case 'hosted': 1310 // Self-hosted videos - check if they should be blocked. 1311 const externalUrl = parsed.external_url; 1312 externalUrlEnabled = parsed.external_url_enabled === 'yes'; 1313 1314 if (externalUrlEnabled && externalUrl && typeof externalUrl === 'string') { 1315 // External URL is enabled and provided - treat as third-party content. 1316 try { 1317 const url = new URL(externalUrl); 1318 hostname = url.hostname; 1319 embedUrl = externalUrl; 1320 } catch (urlError) { 1321 // If external URL is invali
1321d, skip this video. 1322 continue; 1323 } 1324 } else { 1325 // External URL is disabled or not provided - this is a WordPress media file. 1326 // For self-hosted videos, we should block them by default unless they have data-wcc="necessary". 1327 const videoSrc = node.src || node.getAttribute('src'); 1328 if (videoSrc && typeof videoSrc === 'string') { 1329 try { 1330 const url = new URL(videoSrc); 1331 1332 1333 // Block self-hosted videos unless they're marked as necessary. 1334 // Check if the video element has data-wcc attribute. 1335 if (node.hasAttribute('data-wcc') && node.getAttribute('data-wcc') === 'necessary') { 1336 // Video is marked as necessary - skip blocking. 1337 continue; 1338 } 1339 // Block the video and use the full path for more specific blocking. 1340 hostname = `${url.hostname}${url.pathname}`; 1341 embedUrl = videoSrc; 1342 1343 1344 } catch (urlError) { 1345 // If video src is invalid, skip this video. 1346 continue; 1347 } 1348 } else { 1349 // No video src found - skip blocking. 1350 continue; 1351 } 1352 } 1353 break; 1354 1355 default: 1356 // Unknown video source, skip. 1357 continue; 1358 } 1359 1360 // Skip if no valid video configuration found 1361 if (!embedUrl || !hostname) continue; 1362 1363 const uniqueID = ref._wccRandomString(8, false); 1364 1365 // Real iframe for restoring after consent. 1366 const realIframe = document.createElement('iframe'); 1367 realIframe.src = embedUrl; 1368 realIframe.width = widgetContainer.offsetWidth || 560; 1369 realIframe.height = widgetContainer.offsetHeight || 315; 1370 realIframe.frameBorder = 0; 1371 1372 // Set appropriate allow attributes based on video source. 1373 if (hostname === 'youtube.com') { 1374 realIframe.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"); 1375 } else if (hostname === 'vimeo.com') { 1376 realIframe.setAttribute("allow", "autoplay; fullscreen; picture-in-picture"); 1377 } else { 1378 realIframe.setAttribute("allow", "autoplay; fullscreen"); 1379 } 1380 1381 // Pseudo iframe for placeholder. 1382 const pseudoIframe = { 1383 nodeName: 'iframe', 1384 src: realIframe.src, 1385 offsetWidth: realIframe.width, 1386 offsetHeight: realIframe.height, 1387 insertAdjacentHTML: function (position, html) { 1388 node.insertAdjacentHTML(position, html); 1389 } 1390 }; 1391
1392 const cleanedHostname = _wccCleanHostName(hostname); 1393 _wccAddProviderToList(node, cleanedHostname); 1394 1395 // Check if element has data-wcc attribute that should prevent blocking. 1396 if (node.hasAttribute('data-wcc') && !_wccIsCategoryToBeBlocked(node.getAttribute('data-wcc'))) { 1397 continue; 1398 } 1399 // Check if provider should be blocked. 1400 if (!_wccShouldBlockProvider(cleanedHostname)) { 1401 // Provider is allowed, so make sure the real iframe exists. 1402 if (node.nodeName.toLowerCase() === 'div' && node.classList.contains('elementor-video')) { 1403 if (!node.querySelector('iframe')) { 1404 node.replaceWith(realIframe); 1405 } 1406 } 1407 continue; 1408 } 1409 1410 const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname); 1411 _wccAddPlaceholder(pseudoIframe, uniqueID, category); 1412 1413 // For self-hosted videos, store the original video element, not the iframe. 1414 const backupNode = (videoType === 'hosted' && !externalUrlEnabled) ? 1415 node.cloneNode(true) : // Store original video element for self-hosted. 1416 realIframe.cloneNode(true); // Store iframe for external videos. 1417 1418 node.remove(); 1419 _wccStore._backupNodes.push({ 1420 position: 'body', 1421 node: backupNode, 1422 uniqueID 1423 }); 1424 } catch (e) { 1425 console.error('Error processing Elementor video:', e); 1426 } 1427 1428 continue; 1429 } 1430 1431 // Original script/iframe blocking. 1432 if (!node.src || !node.nodeName || !["script", "iframe"].includes(node.nodeName.toLowerCase())) 1433 continue; 1434 1435 try { 1436 let urlToParse = node.src; 1437 let hostname, pathname; 1438 1439 1440 if (urlToParse.match(/^\/[a-zA-Z0-9]+\/?$/)) { 1441 hostname = window.location.hostname; 1442 pathname = urlToParse; 1443 } else { 1444 try { 1445 const url = new URL( 1446 urlToParse.startsWith("//") 1447 ? `${window.location.protocol}${urlToParse}` 1448 : urlToParse.startsWith("/") 1449 ? `${window.location.origin}${urlToParse}` 1450 : urlToParse.match(/^https?:\/\//) 1451 ? urlToParse 1452 : `${window.location.origin}/${urlToParse}` 1453 ); 1454 hostname = url.hostname; 1455 pathname = url.pathname; 1456 } catch (urlError) { 1457 hostname = window.location.hostname; 1458 pathname = urlToParse.startsWith('/') ? urlToParse : `/${urlToParse}`; 1459 } 1460 } 1461 1462 const cleanedHostname = _wccCleanHostName(`${hostname}${pathname}`); 1463 _wccAddProviderToList(node, cleanedHostname); 1464 1465 // Check if element has data-wcc attribute that should prevent blocking. 1466 if (node.hasAttribute('data-wcc') && !_wccIsCategoryToBeBlocked(node.getAttribute('data-wcc'))) { 1467 continue; 1468 } 1469 1470 // Check if provider should be blocked. 1471 if (!_wccShouldBlockProvider(cleanedHostname)) continue; 1472 1473 const uniqueID = ref._wccRandomString(8, false); 1474 if (node.nodeName.toLowerCase() === "iframe") { 1475 const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname); 1476 _wccAddPlaceholder(node, uniqueID, category); 1477 } else { 1478 node.type = "javascript/blocked"; 1479 const scriptEventListener = function (event) { 1480 event.preventDefault(); 1481 node.removeEventListener("beforescriptexecute", scriptEventListener); 1482 }; 1483 node.addEventListener("beforescriptexecute", scriptEventListener); 1484 } 1485 1486 const position = document.head.compareDocumentPosition(node) & 1487 Node.DOCUMENT_POSITION_CONTAINED_BY ? "head" : "body"; 1488 1489 node.remove(); 1490 _wccStore._backupNodes.push({ 1491 position: position, 1492 node: node.cloneNode(), 1493 uniqueID 1494 }); 1495 } catch (error) { 1496 console.log('Script processing error:', { 1497 src: node.src, 1498 nodeType: node.nodeName, 1499 error: error.message 1500 }); 1501 continue; 1502 } 1503 } catch (error) { 1504 console.error('Error in mutation observer:', error); 1505 } 1506 if (node.nodeType === 1) { 1507 _wccApplyCustomPlaceholders(node); 1508 } 1509 } 1510 } 1511} 1512 1513/** 1514 * Re-apply iframe blocking for embeds already in the DOM after consent is withdrawn.
1515 * MutationObserver only sees newly added nodes; restored YouTube/iframes stay playable until this runs. 1516 */ 1517function _wccReblockExistingEmbeds() { 1518 const urlParams = new URLSearchParams(window.location.search); 1519 if (urlParams.has('wcc_bypass')) return; 1520 1521 const iframes = document.querySelectorAll('iframe[src]'); 1522 for (const node of iframes) { 1523 try { 1524 if (!node.src) continue; 1525 if (node.closest('.wcc-consent-container')) continue; 1526 if ( 1527 node.closest( 1528 '.video-placeholder-normal, .video-placeholder-youtube, .video-placeholder-tiktok, [data-tag="video-placeholder"]' 1529 ) 1530 ) { 1531 continue; 1532 } 1533 1534 let urlToParse = node.src; 1535 let hostname; 1536 let pathname; 1537 1538 if (urlToParse.match(/^\/[a-zA-Z0-9]+\/?$/)) { 1539 hostname = window.location.hostname; 1540 pathname = urlToParse; 1541 } else { 1542 try { 1543 const url = new URL( 1544 urlToParse.startsWith("//") 1545 ? `${window.location.protocol}${urlToParse}` 1546 : urlToParse.startsWith("/") 1547 ? `${window.location.origin}${urlToParse}` 1548 : urlToParse.match(/^https?:\/\//) 1549 ? urlToParse 1550 : `${window.location.origin}/${urlToParse}` 1551 ); 1552 hostname = url.hostname; 1553 pathname = url.pathname; 1554 } catch (urlError) { 1555 hostname = window.location.hostname; 1556 pathname = urlToParse.startsWith("/") ? urlToParse : `/${urlToParse}`; 1557 } 1558 } 1559 1560 const cleanedHostname = _wccCleanHostName(`${hostname}${pathname}`); 1561 1562 if ( 1563 node.hasAttribute("data-wcc") && 1564 !_wccIsCategoryToBeBlocked(node.getAttribute("data-wcc")) 1565 ) { 1566 continue; 1567 } 1568 1569 if (!_wccShouldBlockProvider(cleanedHostname)) continue; 1570 1571 const uniqueID = ref._wccRandomString(8, false); 1572 const category = _wccGetCategoryFromBlockedScript(node, cleanedHostname); 1573 const w = node.offsetWidth || node.getBoundingClientRect().width || 560; 1574 const h = node.offsetHeight || node.getBoundingClientRect().height || 315; 1575 const pseudoIframe = { 1576 nodeName: "iframe", 1577 src: node.src, 1578 offsetWidth: w, 1579 offsetHeight: h, 1580 insertAdjacentHTML: function (position, html) { 1581 node.insertAdjacentHTML(position, html); 1582 }, 1583 }; 1584 1585 _wccAddPlaceholder(pseudoIframe, uniqueID, category); 1586 const backupNode = node.cloneNode(true); 1587 node.remove(); 1588 _wccStore._backupNodes.push({ 1589 position: "body", 1590 node: backupNode, 1591 uniqueID, 1592 }); 1593 } catch (e) { 1594 console.error("Error in _wccReblockExistingEmbeds:", e); 1595 } 1596 } 1597 _wccSetPlaceHolder(); 1598 _wccApplyCustomPlaceholders(); 1599} 1600 1601function _wccUnblock() { 1602 if (navigator.doNotTrack === 1) return; 1603 1604 const consent = ref._wccGetFromStore("consent"); 1605 if ( 1606 (_wccGetLaw() === "gdpr" && 1607 (!consent || consent !== "yes") && showBanner) 1608 ) 1609 return; 1610 1611 _wccStore._backupNodes = _wccStore._backupNodes.filter( 1612 (entry) => { 1613 const { position, node, uniqueID } = entry; 1614 try { 1615 if (entry.wccPh) { 1616 const phCategories = entry.categories || []; 1617 if (_wccGetBlockedPlaceholderCategories(phCategories).length) return true; 1618 document.getElementById(uniqueID)?.remove(); 1619 if (node) { 1620 node.style.display = ''; 1621 delete node.dataset.wccPh; 1622 } 1623 return false;
1624 } 1625 if (_wccShouldBlockProvider(node.src)) return true; 1626 1627 const frame = document.getElementById(uniqueID); 1628 1629 // Handle TikTok embeds 1630 const isTikTok = node.nodeName === 'BLOCKQUOTE' && 1631 node.classList.contains('tiktok-embed'); 1632 1633 if (isTikTok && frame) { 1634 frame.parentNode.insertBefore(node, frame); 1635 const script = document.createElement('script'); 1636 script.src = 'https://www.tiktok.com/embed.js'; 1637 script.async = true; 1638 document.body.appendChild(script); 1639 frame.parentNode.removeChild(frame); 1640 return false; 1641 } 1642 1643 // Handle script-like nodes (including type="javascript/blocked") 1644 const isScriptLike = ( 1645 node.src && 1646 node.type && 1647 node.type.startsWith("javascript") 1648 ); 1649 1650 if (isScriptLike) { 1651 const scriptNode = document.createElement("script"); 1652 scriptNode.src = node.src; 1653 scriptNode.type = "text/javascript"; 1654 scriptNode.async = true; 1655 1656 const target = (position === "head") ? document.head : document.body; 1657 target.appendChild(scriptNode); 1658 1659 if (frame) frame.parentNode.removeChild(frame); 1660 return false; 1661 } 1662 1663 // Handle iframe-based embeds if frame exists 1664 if (frame) { 1665 // Check if this is a restored Elementor video iframe (has all the attributes we set) 1666 if (node.frameBorder !== undefined && node.hasAttribute && node.hasAttribute('allow')) { 1667 // This is an Elementor video iframe - restore it with all attributes 1668 frame.parentNode.insertBefore(node, frame); 1669 frame.parentNode.removeChild(frame); 1670 } else if (node.nodeName === 'VIDEO') { 1671 // This is a self-hosted video element - restore it directly 1672 frame.parentNode.insertBefore(node, frame); 1673 frame.parentNode.removeChild(frame); 1674 } else { 1675 // Generic iframe restoration 1676 const iframe = document.createElement("iframe"); 1677 iframe.src = node.src; 1678 iframe.width = frame.offsetWidth; 1679 iframe.height = frame.offsetHeight; 1680 frame.parentNode.insertBefore(iframe, frame); 1681 frame.parentNode.removeChild(frame); 1682 } 1683 } 1684 1685 return false; 1686 } catch (error) { 1687 console.error(error); 1688 return false; 1689 } 1690 } 1691 ); 1692} 1693 1694function _wccClearListeners() {
1695 _nodeListObserver.disconnect(); 1696 document.createElement = _wccCreateElementBackup; 1697} 1698function _wccAddProviderToList(node, cleanedHostname) { 1699 const categoryName = 1700 node.hasAttribute("data-wcc") && node.getAttribute("data-wcc"); 1701 if (!categoryName) return; 1702 for (const category of _wccStore._categories) 1703 if (category.isNecessary && category.slug === categoryName && true === category.defaultConsent[currentLaw]) return; 1704 const provider = _wccStore._providersToBlock.find( 1705 ({ re }) => re === cleanedHostname 1706 ); 1707 if (!provider) 1708 _wccStore._providersToBlock.push({ 1709 re: cleanedHostname, 1710 categories: [categoryName], 1711 fullPath: false, 1712 }); 1713 else if (!provider.isOverriden) { 1714 provider.categories = [categoryName]; 1715 provider.isOverriden = true; 1716 } else if (!provider.categories.includes(categoryName)) 1717 provider.categories.push(categoryName); 1718} 1719 1720const _nodeListObserver = new MutationObserver(_wccMutationObserver); 1721_nodeListObserver.observe(document.documentElement, { 1722 childList: true, 1723 subtree: true, 1724}); 1725function _wccCleanHostName(name) { 1726 return name.replace(/^www./, ""); 1727} 1728 1729function _wccIsCategoryToBeBlocked(category) { 1730 const cookieValue = ref._wccGetFromStore(category); 1731 const normalizedLaw = currentLaw.toLowerCase(); // Ensure case-insensitive key access 1732 return ( 1733 cookieValue === "no" || 1734 (!cookieValue && 1735 _wccStore._categories.some( 1736 (cat) => cat.slug === category && !cat.isNecessary && !cat.defaultConsent[normalizedLaw] 1737 )) 1738 ); 1739} 1740 1741function _wccShouldBlockProvider(formattedRE) { 1742 const provider = _wccStore._providersToBlock.find(({ re }) => 1743 new RegExp(_wccEscapeRegex(re)).test(formattedRE) 1744 ); 1745 return ( 1746 provider && 1747 provider.categories.some((category) => _wccIsCategoryToBeBlocked(category)) 1748 ); 1749} 1750 1751function _wccGetCategoryFromBlockedScript(element, cleanedHostname) { 1752 // Priority 1: Check data-wcc attribute 1753 if (element.hasAttribute && element.hasAttribute('data-wcc')) { 1754 const dataWcc = element.getAttribute('data-wcc'); 1755 if (dataWcc && dataWcc !== 'necessary') { 1756 return [dataWcc]; // Return as array for consistency 1757 } 1758 } 1759 1760 // Priority 2: Check provider pattern in _wccStore._providersToBlock 1761 if (cleanedHostname && _wccStore._providersToBlock) { 1762 const provider = _wccStore._providersToBlock.find(({ re }) => 1763 new RegExp(_wccEscapeRegex(re)).test(cleanedHostname) 1764 ); 1765 if (provider && provider.categories && provider.categories.length > 0) { 1766 // Return all categories that should be blocked, or all categories if none are blocked 1767 const blockedCategories = provider.categories.filter((category) => _wccIsCategoryToBeBlocked(category)); 1768 return blockedCategories.length > 0 ? blockedCategories : provider.categories; 1769 } 1770 } 1771 1772 // return null if no category is found 1773 return null; 1774} 1775function _wccShouldChangeType(element, src) { 1776 return ( 1777 (element.hasAttribute("data-wcc") && 1778 _wccIsCategoryToBeBlocked( 1779 element.getAttribute("data-wcc") 1780 )) || 1781 _wccShouldBlockProvider(src ? src : element.src) 1782 ); 1783} 1784 1785/** 1786 * Add readmore button to consent notice. 1787 * 1788 * @returns void 1789 */ 1790function _wccAttachReadMore() { 1791 const readMoreButton = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 1792 (code) => code.key === "wcc_readmore" 1793 ); 1794 if (!readMoreButton.status) return; 1795 const content = readMoreButton.content; 1796 const styles = _wccStore._bannerConfig[currentLaw].config.readMore.styles; 1797 const readMoreElement = document.querySelector( 1798 '[data-tag="description"]' 1799 ); 1800 if (!readMoreElement) return; 1801 if (readMoreElement.childNodes.length > 1) { 1802 const innerElement = document.querySelector( 1803 '[data-tag="description"] p:last-child' 1804 ); 1805 innerElement && innerElement.insertAdjacentHTML( 1806 "beforeend", 1807 ` ${content}` 1808 ); 1809 } else { 1810 readMoreElement.insertAdjacentHTML( 1811 "beforeend", 1812 ` ${content}` 1813 ); 1814 } 1815 const placeHolders = document.querySelectorAll( 1816 `[data-tag="readmore-button"]` 1817 ); 1818 if (placeHolders.length < 1) return;
1819 Array.from(placeHolders).forEach((placeHolder) => { 1820 for (const style in styles) { 1821 if (!styles[style]) continue; 1822 placeHolder.style[style] = styles[style]; 1823 } 1824 }); 1825} 1826 1827function _wccAfterConsent() { 1828 if (_wccGetLaw() === 'gdpr') _wccSetPreferenceCheckBoxStates(true); 1829 1830 // Update consent details after consent action 1831 _wccSetConsentDetails(); 1832 _wccSetFooterWrapperStyles(); 1833 1834 if (_wccStore._bannerConfig[currentLaw].behaviours.reloadBannerOnAccept === true) { 1835 window.location.reload(); 1836 } 1837} 1838 1839function _wccSendConsentEvent(responseCategories, status = "initial") { 1840 const consentUpdate = new CustomEvent('wcc_consent_update', { 1841 detail: { 1842 status: status, 1843 categories: responseCategories, 1844 currentLaw: currentLaw 1845 }, 1846 }); 1847 document.dispatchEvent(consentUpdate); 1848 1849 // Push consent data to dataLayer if it exists 1850 if (window.dataLayer) { 1851 window.dataLayer.push({ 1852 "event": "wcc_cookie_consent_update", 1853 "cookie_consent_status": status, 1854 "cookie_consent_categories": responseCategories 1855 }); 1856 } 1857} 1858 1859function _wccAttachNoticeStyles() { 1860 if (document.getElementById("wcc-style") || !_wccStyle) return; 1861 document.head.insertAdjacentHTML( 1862 "beforeend", 1863 ` <style id="wcc-style">${_wccStyle.css[currentLaw]}</style>` 1864 ); 1865} 1866 1867function _wccFindCheckBoxValue(id = "") { 1868 const elemetsToCheck = id 1869 ? [`wccSwitch`, `wccCategoryDirect`] 1870 : [`wccCCPAOptOut`]; 1871 return elemetsToCheck.some((key) => { 1872 const checkBox = document.getElementById(`${key}${id}`); 1873 return checkBox && checkBox.checked; 1874 }); 1875} 1876 1877function _wccGetBlockedPlaceholderCategories(categories) { 1878 return categories.filter((slug) => _wccIsCategoryToBeBlocked(slug)); 1879} 1880 1881function _wccFormatPlaceholderCategoryNames(categories) { 1882 return categories 1883 .map((catSlug) => { 1884 const foundCategory = _wccStore._categories?.find((cat) => cat.slug === catSlug); 1885 return foundCategory?.name || catSlug || ''; 1886 }) 1887 .filter(Boolean) 1888 .join(', ') 1889 .toLowerCase(); 1890} 1891 1892function _wccUpdatePlaceholderCategoryText(uniqueID, categories) { 1893 const container = document.getElementById(uniqueID); 1894 if (!container) return; 1895 const innerTextElement = container.querySelector('[data-tag="placeholder-title"]') 1896 || container.querySelector( 1897 '.video-placeholder-text-normal, .video-placeholder-text-youtube, .video-placeholder-text-tiktok' 1898 ); 1899 if (!innerTextElement) return; 1900 const template = innerTextElement.dataset.wccPlaceholderTemplate 1901 || innerTextElement.textContent 1902 || innerTextElement.innerText; 1903 if (!template.includes('{category}')) return; 1904 innerTextElement.dataset.wccPlaceholderTemplate = template; 1905 innerTextElement.textContent = template.replace( 1906 '{category}', 1907 _wccFormatPlaceholderCategoryNames(categories) 1908 ); 1909 innerTextElement.style.display = 'block'; 1910} 1911 1912/** Selector targets: { selector, categories } — same shape as _providersToBlock ({ re, categories }). */ 1913function _wccApplyCustomPlaceholders(root = document) { 1914 const targets = _wccStore._customPlaceholderTargets; 1915 if (!targets?.length || !_wccStore._bannerConfig[currentLaw]?.config?.videoPlaceholder?.status) return; 1916 const scope = root?.nodeType === 1 ? root : document; 1917 targets.forEach(({ selector, categories = [] }) => { 1918 const blockedCategories = _wccGetBlockedPlaceholderCategories(categories); 1919 if (!selector || !blockedCategories.length) return; 1920 let nodes; 1921 try { 1922 nodes = scope === document 1923 ? document.querySelectorAll(selector) 1924 : [...(scope.matches(selector) ? [scope] : []), ...scope.querySelectorAll(selector)]; 1925 } catch (e) { 1926 return; 1927 } 1928 nodes.forEach((node) => { 1929 if (!node.offsetWidth && !node.offsetHeight) return; 1930 if (node.closest('.wcc-consent-container,[data-tag="video-placeholder"]')) return; 1931 1932 if (node.dataset.wccPh) { 1933 node.setAttribute('data-wcc', blockedCategories.join(',')); 1934 _wccUpdatePlaceholderCategoryText(node.dataset.wccPh, blockedCategories); 1935 const backup = _wccStore._backupNodes.find((entry) => entry.wccPh && entry.node === node); 1936 if (backup) backup.categories = blockedCategories; 1937 return; 1938 } 1939 1940 const uniqueID = ref._wccRandomString(8, false); 1941 const pseudo = { 1942 nodeName: 'iframe', 1943 src: '', 1944 offsetWidth: node.offsetWidth || 300, 1945 offsetHeight: node.offsetHeight || 200, 1946 insertAdjacentHTML(position, html) { 1947 node.insertAdjacentHTML(position, html); 1948 }, 1949 }; 1950 node.dataset.wccPh = uniqueID; 1951 node.setAttribute('data-wcc', blockedCategories.join(',')); 1952 _wccAddPlaceholder(pseudo, uniqueID, blockedCategories); 1953 node.style.display = 'none'; 1954 _wccStore._backupNodes.push({ 1955 position: 'body', 1956 node, 1957 uniqueID, 1958 wccPh: true, 1959 categories: blockedCategories, 1960 }); 1961 }); 1962 }); 1963 _wccSetPlaceHolder(); 1964} 1965 1966ref._wccIsConsentGrantedForCategory = (category) => 1967 category ? !_wccIsCategoryToBeBlocked(category) : false;
1968 1969function _wccAddPlaceholder(htmlElm, uniqueID, categories = null) { 1970 const shortCodeData = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 1971 (code) => code.key === 'wcc_video_placeholder' 1972 ); 1973 if (!shortCodeData?.content) return; 1974 const videoPlaceHolderDataCode = shortCodeData.content; 1975 const { offsetWidth, offsetHeight } = htmlElm; 1976 if (offsetWidth === 0 || offsetHeight === 0) return; 1977 1978 htmlElm.insertAdjacentHTML 1979 ? htmlElm.insertAdjacentHTML("beforebegin", `${videoPlaceHolderDataCode}`.replace("[UNIQUEID]", uniqueID)) 1980 : document.body.insertAdjacentHTML("beforeend", `${videoPlaceHolderDataCode}`.replace("[UNIQUEID]", uniqueID)); 1981 1982 const addedNode = document.getElementById(uniqueID); 1983 if (!addedNode) return; 1984 addedNode.style.width = `${offsetWidth}px`; 1985 addedNode.style.height = `${offsetHeight}px`; 1986 1987 const innerTextElement = document.querySelector( 1988 `#${uniqueID} .video-placeholder-text-normal` 1989 ); 1990 1991 // Check for TikTok URL 1992 const isTikTok = htmlElm.src && htmlElm.src.includes('tiktok.com'); 1993 const youtubeID = !isTikTok && htmlElm.src && _wccGetYoutubeID(htmlElm.src); 1994 1995 if (innerTextElement && (youtubeID || isTikTok)) { 1996 innerTextElement.style.display = "none"; 1997 } 1998 1999 if (youtubeID && addedNode) { 2000 addedNode.classList.replace( 2001 "video-placeholder-normal", 2002 "video-placeholder-youtube" 2003 ); 2004 addedNode.style.backgroundImage = `linear-gradient(rgba(76,72,72,.7),rgba(76,72,72,.7)),url('https://img.youtube.com/vi/${youtubeID}/maxresdefault.jpg')`; 2005 innerTextElement?.classList.replace( 2006 "video-placeholder-text-normal", 2007 "video-placeholder-text-youtube" 2008 ); 2009 } else if (isTikTok && addedNode) { 2010 addedNode.classList.replace( 2011 "video-placeholder-text-normal", 2012 "video-placeholder-tiktok" 2013 ); 2014 innerTextElement?.classList.replace( 2015 "video-placeholder-text-tiktok", 2016 "video-placeholder-text-normal" 2017 ); 2018 } 2019 if (innerTextElement) { 2020 const categoryList = Array.isArray(categories) ? categories : categories ? [categories] : []; 2021 _wccUpdatePlaceholderCategoryText(uniqueID, categoryList); 2022 } 2023 2024 if (innerTextElement && !youtubeID && !isTikTok) { 2025 innerTextElement.style.display = "block"; 2026 } 2027} 2028function _wccGetYoutubeID(src) { 2029 const match = src.match( 2030 /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ 2031 ); 2032 if (match && Array.isArray(match) && match[2] && match[2].length === 11) 2033 return match[2]; 2034 return false; 2035} 2036 2037function _wccSetPlaceHolder() { 2038 const status = _wccStore._bannerConfig[currentLaw].config.videoPlaceholder.status; 2039 const styles = _wccStore._bannerConfig[currentLaw].config.videoPlaceholder.styles; 2040 if (!status) return; 2041 const placeHolders = document.querySelectorAll( 2042 `[data-tag="placeholder-title"]` 2043 ); 2044 if (placeHolders.length < 1) return; 2045 Array.from(placeHolders).forEach((placeHolder) => { 2046 placeHolder.style.display = "block"; 2047 if (!placeHolder.dataset.wccPlaceholderBound) { 2048 placeHolder.dataset.wccPlaceholderBound = "1"; 2049 placeHolder.addEventListener("click", () => { 2050 if (ref._wccGetFromStore("action")) _revisitWccConsent(); 2051 }); 2052 } 2053 for (const style in styles) { 2054 if (!styles[style]) continue; 2055 placeHolder.style[style] = styles[style]; 2056 } 2057 }); 2058} 2059function _wccAddRtlClass() { 2060 if (!_wccStore._rtl) return; 2061 const rtlElements = ['notice', 'detail', 'optout-popup', 'revisit-consent', 'video-placeholder'] 2062 rtlElements.forEach(function (item) { 2063 _wccGetElementByTag(item) && _wccGetElementByTag(item).classList.add('wcc-rtl'); 2064 }); 2065} 2066 2067function _wccSetFocus(tagName) { 2068 const element = _wccGetElementByTag(tagName); 2069 if (!element) return; 2070 element.focus(); 2071} 2072 2073function _wccSetPoweredBy() { 2074 // Cache configuration access to avoid repeated deep property lookups 2075 const poweredByStatus = _wccStore._bannerConfig[currentLaw].config.preferenceCenter.poweredBy.status; 2076 2077 // Set flag for footer wrapper styles 2078 _wccStore._poweredByHidden = !poweredByStatus; 2079 2080 // Define powered-by element selectors once 2081 const poweredBySelectors = ['detail-powered-by', 'optout-powered-by']; 2082 2083 // Determine styles based on powered-by status 2084 const styles = poweredByStatus ? { 2085 display: "flex", 2086 justifyContent: "flex-end", 2087 alignItems: "center" 2088 } : { 2089 display: "none" 2090 }; 2091 2092 // Apply styles to all powered-by elements in a single loop
2093 poweredBySelectors.forEach((key) => { 2094 const element = document.querySelector(`[data-tag="${key}"]`); 2095 if (element) { 2096 Object.assign(element.style, styles); 2097 } 2098 }); 2099} 2100function _wccWatchBannerElement() { 2101 document.querySelector("body").addEventListener("click", (event) => { 2102 const selector = ".wcc-banner-element, .wcc-banner-element *"; 2103 if ( 2104 event.target.matches 2105 ? event.target.matches(selector) 2106 : event.target.msMatchesSelector(selector) 2107 ) 2108 _revisitWccConsent(); 2109 }); 2110} 2111 2112function _wccRemoveAllDeadCookies() { 2113 for (const category of _wccStore._categories) { 2114 if (ref._wccGetFromStore(category.slug) !== "yes") 2115 _wccRemoveDeadCookies(category); 2116 } 2117} 2118function _wccSetNoticeGpcOption() { 2119 const noticeGpcWrapper = document.querySelector('[data-tag="notice-gpc-option"]'); 2120 if (!noticeGpcWrapper) { 2121 return; 2122 } 2123 2124 if (_wccGPCEnabled()) { 2125 noticeGpcWrapper.classList.remove('wcc-hide'); 2126 } else { 2127 noticeGpcWrapper.classList.add('wcc-hide'); 2128 } 2129} 2130function _wccSetCCPAOptions() { 2131 const toggle = _wccStore._bannerConfig[currentLaw].config.optOption.toggle; 2132 const activeColor = toggle.states.active.styles['background-color']; 2133 const inactiveColor = toggle.states.inactive.styles['background-color']; 2134 _wccClassRemove("=optout-option", "wcc-disabled", false); 2135 const toggleDataCode = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 2136 (code) => code.key === "wcc_optout_toggle_label" 2137 ); 2138 const optOutTitle = _wccStore._bannerConfig[currentLaw].settings.shortcodes.find( 2139 (code) => code.key === "wcc_optout_option_title" 2140 ); 2141 const formattedLabel = toggleDataCode.content.replace( 2142 `[wcc_optout_option_title]`, 2143 optOutTitle.content 2144 ); 2145 // Opt-out checked when user has revoked sale consent (store "no") or GPC applies. 2146 let checked = _wccGPCEnabled() || ref._wccGetFromStore("consent") === "no"; 2147 // Disable checkbox if GPC is enabled (user cannot change the setting) 2148 let isDisabled = _wccGPCEnabled(); 2149 2150 _wccSetCheckBoxInfo( 2151 document.getElementById(`wccCCPAOptOut`), 2152 formattedLabel, 2153 { 2154 checked, 2155 disabled: isDisabled, 2156 addListeners: !isDisabled, // Don't add listeners if GPC is enabled (prevents click on opt-out title) 2157 }, 2158 { activeColor, inactiveColor }, 2159 true 2160 ); 2161 2162 // Show/hide GPC wrapper based on GPC status 2163 const gpcWrapper = document.querySelector('[data-tag="optout-gpc-option"]'); 2164 if (gpcWrapper) { 2165 if (_wccGPCEnabled()) { 2166 gpcWrapper.style.display = ''; 2167 } else { 2168 gpcWrapper.style.display = 'none'; 2169 } 2170 } 2171 2172 // If GPC is enabled, ensure checkbox is checked and disabled, and prevent further interactions 2173 if (_wccGPCEnabled()) { 2174 const optOutCheckbox = document.getElementById(`wccCCPAOptOut`); 2175 if (optOutCheckbox) { 2176 // Ensure checkbox is checked and disabled 2177 optOutCheckbox.checked = true; 2178 optOutCheckbox.disabled = true; 2179 // Find the wrapper element to set cursor on hover 2180 const checkboxWrapper = optOutCheckbox.closest('[data-tag="optout-option"], .wcc-opt-out-checkbox-wrapper'); 2181 if (checkboxWrapper) { 2182 // Set cursor on wrapper so it works on hover 2183 checkboxWrapper.style.cursor = 'no-drop'; 2184 } 2185 // Prevent clicks on the checkbox container 2186 const checkboxContainer = optOutCheckbox.closest('.wcc-opt-out-checkbox, [data-tag="optout-gpc-option"]'); 2187 if (checkboxContainer) { 2188 checkboxContainer.style.pointerEvents = 'none'; 2189 checkboxContainer.style.opacity = '0.6'; 2190 } 2191 // Disable the opt-out option title click as well 2192 const optOutTitle = document.querySelector('[data-tag="optout-option-title"]'); 2193 if (optOutTitle) { 2194 optOutTitle.style.pointerEvents = 'none'; 2195 } 2196 // Add cursor style to checkbox 2197 optOutCheckbox.style.cursor = 'no-drop'; 2198 } 2199 } 2200} 2201function _wccSetCheckBoxInfo( 2202 boxElem, 2203 formattedLabel, 2204 { checked, disabled, addListeners }, 2205 { activeColor, inactiveColor }, 2206 isCCPA = false 2207) { 2208 if (!boxElem) return; 2209 if (isCCPA && addListeners) 2210 _wccAttachListener("=optout-option-title", () => boxElem.click()); 2211 boxElem.checked = checked; 2212 boxElem.disabled = disabled; 2213 boxElem.style.backgroundColor = checked ? activeColor : inactiveColor; 2214 _wccSetCheckBoxAriaLabel(boxElem, checked, formattedLabel, isCCPA); 2215 if (!addListeners) return; 2216 boxElem.addEventListener("change", ({ currentTarget: elem }) => { 2217 const isChecked = elem.checked; 2218 elem.style.backgroundColor = isChecked ? activeColor : inactiveColor; 2219 _wccSetCheckBoxAriaLabel(boxElem, isChecked, formattedLabel, isCCPA); 2220 }); 2221} 2222 2223window.revisitWccConsent = () => _revisitWccConsent(); 2224window.getWccConsent = function () { 2225 const cookieConsent = { 2226 currentLaw: "", 2227 categories: {}, 2228 isUserActionCompleted: false, 2229 consentID: "", 2230 languageCode: "" 2231 };
2232 try { 2233 cookieConsent.currentLaw = _wccGetLaw(); 2234 2235 _wccStore._categories.forEach(category => { 2236 cookieConsent.categories[category.slug] = ref._wccGetFromStore(category.slug) === "yes"; 2237 }); 2238 2239 cookieConsent.isUserActionCompleted = ref._wccGetFromStore("action") === "yes"; 2240 cookieConsent.consentID = ref._wccGetFromStore("consentid") || ""; 2241 cookieConsent.languageCode = _wccStore._language || ""; 2242 } catch (e) {} 2243 2244 return cookieConsent; 2245}; 2246 2247/** 2248 * Record the consent of a visitor. 2249 * 2250 * @returns {void} 2251 */ 2252async function _wccLogConsent() { 2253 if (!_wccStore._logConsent) return; 2254 try { 2255 const logData = JSON.stringify( 2256 _wccStore._categories 2257 .map(({ slug }) => ({ 2258 name: slug, 2259 status: ref._wccGetFromStore(slug) || "no", 2260 })) 2261 .concat([ 2262 { 2263 name: "Cookie Consent", 2264 status: 2265 _wccStore._bannerConfig.activeLaw === "ccpa" 2266 ? "yes" 2267 : ref._wccGetFromStore("consent") || "no", 2268 }, 2269 ]) 2270 ); 2271 const data = { 2272 log: logData, 2273 consent_id: ref._wccGetFromStore("consentid"), 2274 }; 2275 let request = new XMLHttpRequest(); 2276 request.open('POST', `${_wccApi.base}consent_logs`, true); 2277 request.setRequestHeader('Content-type', 'application/json'); 2278 request.setRequestHeader('X-WP-Nonce', _wccApi.nonce); 2279 request.send(JSON.stringify(data)); 2280 } catch (err) { 2281 console.error(err); 2282 } 2283} 2284/** 2285 * Retrieve the last modified time of a cookie. 2286 * 2287 * @param {string} cookieName - The name of the cookie 2288 * @returns {number | null} The last modified time of the cookie in milliseconds, or null if the cookie doesn't exist 2289 */ 2290function _wccGetCookieLastModified(cookieName) { 2291 const cookieValue = document.cookie 2292 .split(';') 2293 .map(cookie => cookie.trim()) 2294 .find(row => row.startsWith(`${cookieName}=`)); 2295 2296 if (cookieValue) { 2297 const cookiePairs = cookieValue.substring(cookieName.length + 1).split(","); 2298 for (const pair of cookiePairs) { 2299 const [key, value] = pair.split(":"); 2300 if (key === 'consent_time') { 2301 // Try to parse the value associated with the 'consent_time' key as a number 2302 const lastModifiedTime = Number(value); 2303 if (!isNaN(lastModifiedTime)) { 2304 return lastModifiedTime; 2305 } 2306 } 2307 } 2308 } 2309 return null; 2310} 2311function _wccPrivacySignalDetected() { 2312 return navigator.globalPrivacyControl === true 2313 || navigator.doNotTrack === "1" 2314 || navigator.doNotTrack === 1; 2315} 2316 2317/** 2318 * Check if whether plugin Respect Do Not Track & Global Privacy Control 2319 * 2320 * @since 3.2.0 2321 * @returns {boolean} True if the gpc option enabled in plugin settings and browser settings, false otherwise 2322 */ 2323function _wccGPCEnabled() { 2324 return _wccPrivacySignalDetected() && _wccStore._bannerConfig[currentLaw].config.optOption.gpcOption; 2325} 2326 2327/** 2328 * Format a date according to PHP date format string 2329 * Converts PHP date format tokens to JavaScript date formatting 2330 * 2331 * @param {Date} date The date object to format 2332 * @param {string} format PHP date format string (e.g., 'Y-m-d H:i:s') 2333 * @returns {string} Formatted date string 2334 */ 2335function _wccFormatDate(date, format) { 2336 if (!date || !(date instanceof Date) || isNaN(date.getTime())) { 2337 return ''; 2338 } 2339 2340 // PHP to JavaScript format mapping 2341 const formatMap = { 2342 'Y': () => date.getFullYear(), // 4-digit year 2343 'y': () => String(date.getFullYear()).slice(-2), // 2-digit year 2344 'm': () => String(date.getMonth() + 1).padStart(2, '0'), // 2-digit month (01-12) 2345 'n': () => String(date.getMonth() + 1), // Month without leading zeros (1-12) 2346 'd': () => String(date.getDate()).padStart(2, '0'), // 2-digit day (01-31) 2347 'j': () => String(date.getDate()), // Day without leading zeros (1-31) 2348 'H': () => String(date.getHours()).padStart(2, '0'), // 24-hour format (00-23) 2349 'h': () => { 2350 const hours = date.getHours() % 12 || 12; 2351 return String(hours).padStart(2, '0'); // 12-hour format (01-12) 2352 }, 2353 'G': () => String(date.getHours()), // 24-hour format without leading zeros (0-23) 2354 'g': () => { 2355 const hours = date.getHours() % 12 || 12; 2356 return String(hours); // 12-hour format without leading zeros (1-12) 2357 }, 2358 'i': () => String(date.getMinutes()).padStart(2, '0'), // Minutes with leading zeros (00-59) 2359 's': () => String(date.getSeconds()).padStart(2, '0'), // Seconds with leading zeros (00-59) 2360 'A': () => date.getHours() >= 12 ? 'PM' : 'AM', // Uppercase AM/PM 2361 'a': () => date.getHours() >= 12 ? 'pm' : 'am', // Lowercase am/pm 2362 }; 2363 2364 let formatted = ''; 2365 let i = 0; 2366 2367 while (i < format.length) { 2368 // Check for escape character 2369 if (format[i] === '\\' && i + 1 < format.length) { 2370 formatted += format[i + 1]; 2371 i += 2; 2372 continue; 2373 } 2374 2375 // Check if current character is a format token 2376 let matched = false;
2377 for (const [token, formatter] of Object.entries(formatMap)) { 2378 if (format.substr(i, token.length) === token) { 2379 formatted += formatter(); 2380 i += token.length; 2381 matched = true; 2382 break; 2383 } 2384 } 2385 2386 // If no token matched, add the character as-is 2387 if (!matched) { 2388 formatted += format[i]; 2389 i++; 2390 } 2391 } 2392 2393 return formatted; 2394} 2395 2396/** 2397 * Set consent details (ID and time) and handle accordion functionality 2398 * Hide the section if consent is not given 2399 * 2400 * @returns {void} 2401 */ 2402function _wccSetConsentDetails() { 2403 const consentDetailsWrappers = document.querySelectorAll('[data-tag="consent-details"]'); 2404 if (!consentDetailsWrappers || consentDetailsWrappers.length === 0) { 2405 // Set flag if no consent details elements exist 2406 _wccStore._consentDetailsHidden = true; 2407 return; 2408 } 2409 2410 // Check if consent has been given 2411 const action = ref._wccGetFromStore("action"); 2412 const consent = ref._wccGetFromStore("consent"); 2413 const hasConsent = action === "yes"; 2414 2415 // Set flag for footer wrapper styles 2416 _wccStore._consentDetailsHidden = !hasConsent; 2417 2418 consentDetailsWrappers.forEach((wrapper) => { 2419 // Hide the entire section if consent is not given 2420 if (!hasConsent) { 2421 wrapper.classList.add('wcc-hide'); 2422 return; 2423 } 2424 2425 // Show the section 2426 wrapper.classList.remove('wcc-hide'); 2427 2428 // Get consent ID and time from cookie value 2429 const cookieValue = ref._wccGetCookie("wt_consent"); 2430 let consentID = ''; 2431 let consentTime = ''; 2432 2433 if (cookieValue) { 2434 try { 2435 // Parse cookie value: "key1:value1,key2:value2,..." 2436 const cookiePairs = cookieValue.split(","); 2437 for (const pair of cookiePairs) { 2438 const [key, value] = pair.split(":"); 2439 if (key === 'consentid' && value) { 2440 consentID = value; 2441 } else if (key === 'consent_time' && value) { 2442 consentTime = value; 2443 } 2444 } 2445 } catch (e) { 2446 // If parsing fails, try to get from store as fallback 2447 consentID = ref._wccGetFromStore("consentid") || ''; 2448 consentTime = ref._wccGetFromStore("consent_time") || ''; 2449 } 2450 } else { 2451 // Fallback to store if cookie not found 2452 consentID = ref._wccGetFromStore("consentid") || ''; 2453 consentTime = ref._wccGetFromStore("consent_time") || ''; 2454 } 2455 2456 // Format consent time using the configured date format 2457 let formattedTime = ''; 2458 if (consentTime) { 2459 try { 2460 const timeValue = parseInt(consentTime, 10); 2461 if (!isNaN(timeValue)) { 2462 const date = new Date(timeValue); 2463 // Get date format from config 2464 const dateFormat = _wccStore._bannerConfig && _wccStore._bannerConfig[currentLaw] && _wccStore._bannerConfig[currentLaw].settings && _wccStore._bannerConfig[currentLaw].settings.consentDetails && _wccStore._bannerConfig[currentLaw].settings.consentDetails.dateFormat 2465 ? _wccStore._bannerConfig[currentLaw].settings.consentDetails.dateFormat 2466 : 'Y-m-d H:i:s'; // Default format 2467 formattedTime = _wccFormatDate(date, dateFormat); 2468 } 2469 } catch (e) { 2470 formattedTime = consentTime; 2471 } 2472 } 2473 2474 // Populate consent details 2475 const consentIdElement = wrapper.querySelector('[data-tag="consent-id"]'); 2476 const consentTimeElement = wrapper.querySelector('[data-tag="consent-time"]'); 2477 2478 if (consentIdElement) { 2479 consentIdElement.textContent = consentID || 'N/A'; 2480 } 2481 if (consentTimeElement) { 2482 consentTimeElement.textContent = formattedTime || 'N/A'; 2483 } 2484 2485 // Handle accordion click - using standard accordion structure 2486 const accordion = wrapper.querySelector('#wccConsentDetails'); 2487 if (accordion) { 2488 const accordionBody = accordion.querySelector('.wcc-accordion-body'); 2489 const accordionHeaderWrapper = accordion.querySelector('.wcc-accordion-header-wrapper'); 2490 2491 if (accordionHeaderWrapper) { 2492 // Remove any existing click handlers by cloning the element 2493 const newHeaderWrapper = accordionHeaderWrapper.cloneNode(true); 2494 accordionHeaderWrapper.parentNode.replaceChild(newHeaderWrapper, accordionHeaderWrapper); 2495 2496 // Get fresh references after replacement 2497 const freshAccordion = wrapper.querySelector('#wccConsentDetails'); 2498 const freshAccordionBody = freshAccordion ? freshAccordion.querySelector('.wcc-accordion-body') : null; 2499 const freshAccordionHeaderWrapper = freshAccordion ? freshAccordion.querySelector('.wcc-accordion-header-wrapper') : null; 2500 2501 if (freshAccordionHeaderWrapper && freshAccordion) { 2502 freshAccordionHeaderWrapper.addEventListener('click', (e) => { 2503 e.preventDefault(); 2504 e.stopPropagation(); 2505 2506 // Toggle accordion state 2507 const isActive = freshAccordion.classList.toggle('wcc-accordion-active'); 2508 freshAccordion.removeAttribute('aria-expanded'); 2509 const consentBtn = freshAccordion.querySelector('.wcc-accordion-btn'); 2510 if (consentBtn) consentBtn.setAttribute('aria-expanded', isActive ? 'true' : 'false'); 2511 if (freshAccordionBody) { 2512 freshAccordionBody.setAttribute('aria-hidden', isActive ? 'false' : 'true'); 2513 } 2514 }); 2515 } 2516 } 2517 } 2518 }); 2519} 2520 2521/** 2522 * Apply custom CSS to footer wrapper when both consent details and PoweredBy are hidden 2523 * 2524 * @returns {void} 2525 */ 2526function _wccSetFooterWrapperStyles() { 2527 const poweredByHidden = _wccStore._poweredByHidden; 2528 const consentDetailsHidden = _wccStore._consentDetailsHidden; 2529 // Apply custom CSS when both are hidden 2530 if (poweredByHidden && consentDetailsHidden) { 2531 const footerWrapper = document.querySelector('.wcc-footer-bottom-wrapper'); 2532 if (footerWrapper) { 2533 footerWrapper.style.display = "none"; 2534 } 2535 } else { 2536 const footerWrapper = document.querySelector('.wcc-footer-bottom-wrapper'); 2537 if (footerWrapper) { 2538 footerWrapper.style.display = "flex"; 2539 } 2540 } 2541}
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.