1(function () { 2 'use strict'; 3 4 var COOKIE_NAME = 'kncc_consent'; 5 var COOKIE_DAYS = 365; 6 var config = window.knccConfig || {}; 7 8 function getConsent() { 9 var match = document.cookie.match(new RegExp('(?:^|; )' + COOKIE_NAME + '=([^;]*)')); 10 if (!match) return null; 11 try { 12 return JSON.parse(decodeURIComponent(match[1])); 13 } catch (e) { 14 return null; 15 } 16 } 17 18 function setConsent(consent) { 19 consent.timestamp = new Date().toISOString(); 20 var val = encodeURIComponent(JSON.stringify(consent)); 21 var expires = new Date(Date.now() + COOKIE_DAYS * 864e5).toUTCString(); 22 var cookie = COOKIE_NAME + '=' + val + ';expires=' + expires + ';path=/;SameSite=Lax'; 23 if (location.protocol === 'https:') { 24 cookie += ';Secure'; 25 } 26 document.cookie = cookie; 27 } 28 29 function dispatchConsentCommand(payload) { 30 if (typeof window.gtag === 'function') { 31 window.gtag('consent', 'update', payload); 32 return; 33 } 34 window.dataLayer = window.dataLayer || []; 35 window.dataLayer.push(['consent', 'update', payload]); 36 } 37 38 function updateConsentMode(consent) { 39 if (!config.consentMode) return; 40 41 dispatchConsentCommand({ 42 analytics_storage: consent.analytics ? 'granted' : 'denied', 43 ad_storage: consent.marketing ? 'granted' : 'denied', 44 ad_user_data: consent.marketing ? 'granted' : 'denied', 45 ad_personalization: consent.marketing ? 'granted' : 'denied', 46 functionality_storage: consent.preferences ? 'granted' : 'denied', 47 personalization_storage: consent.preferences ? 'granted' : 'denied', 48 security_storage: 'granted', 49 }); 50 } 51 52 function pushDataLayer(consent) { 53 window.dataLayer = window.dataLayer || []; 54 window.dataLayer.push({ 55 event: 'consent_update', 56 consent_necessary: true, 57 consent_analytics: !!consent.analytics, 58 consent_marketing: !!consent.marketing, 59 consent_preferences: !!consent.preferences, 60 }); 61 } 62 63 function isCategoryGranted(consent, key) { 64 if (key === 'necessary') { 65 return true; 66 } 67 if (consent.choice === 'accept') { 68 return true; 69 } 70 if (consent.choice === 'reject') { 71 return false; 72 } 73 var value = consent[key]; 74 return value === true || value === 1 || value === '1' || value === 'true'; 75 } 76 77 function getWpConsentPrefix() { 78 if (typeof window.consent_api !== 'undefined' && consent_api.cookie_prefix) { 79 return consent_api.cookie_prefix; 80 } 81 return 'wp_consent'; 82 } 83 84 function dispatchWpConsentChange(category, value) { 85 try { 86 var detail = {}; 87 detail[category] = value; 88 document.dispatchEvent(new CustomEvent('wp_listen_for_consent_change', { detail: detail })); 89 } catch (e) {} 90 } 91 92 function setWpConsentCookie(category, value) { 93 if (value !== 'allow' && value !== 'deny') { 94 return; 95 } 96 97 if (typeof wp_set_consent === 'function') { 98 wp_set_consent(category, value); 99 return; 100 } 101 102 // Fallback: WP Consent API JS not ready (e.g. delayed by a caching plugin). 103 // Write the cookie directly so the value is never left stale, then notify 104 // integrators like Google Site Kit that consent changed. 105 var prefix = getWpConsentPrefix(); 106 var days = (typeof window.consent_api !== 'undefined' && consent_api.cookie_expiration) 107 ? consent_api.cookie_expiration 108 : 30; 109 var date = new Date(); 110 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); 111 var secure = location.protocol === 'https:' ? ';secure' : ''; 112 document.cookie = prefix + '_' + category + '=' + value + secure + ';expires=' + date.toUTCString() + ';path=/'; 113 dispatchWpConsentChange(category, value); 114 } 115 116 function clearStaleWpConsentCookie(category) { 117 var prefix = getWpConsentPrefix(); 118 var name = prefix + '_' + category; 119 getDeletionDomains().forEach(function (domain) { 120 deleteCookie(name, domain || null); 121 });
122 } 123 124 function syncWpConsentApi(consent) { 125 if (!consent) return; 126 127 var analytics = isCategoryGranted(consent, 'analytics'); 128 129 // Remove any stale statistics cookies (host + parent domains) before writing, 130 // so a lingering deny on a parent domain cannot shadow the fresh value. 131 if (analytics) { 132 clearStaleWpConsentCookie('statistics'); 133 clearStaleWpConsentCookie('statistics-anonymous'); 134 } 135 136 setWpConsentCookie('functional', 'allow'); 137 setWpConsentCookie('statistics', analytics ? 'allow' : 'deny'); 138 setWpConsentCookie('statistics-anonymous', analytics ? 'allow' : 'deny'); 139 setWpConsentCookie('marketing', isCategoryGranted(consent, 'marketing') ? 'allow' : 'deny'); 140 setWpConsentCookie('preferences', isCategoryGranted(consent, 'preferences') ? 'allow' : 'deny'); 141 } 142 143 function scheduleWpConsentSync(consent) { 144 if (!consent) return; 145 syncWpConsentApi(consent); 146 if (typeof wp_set_consent !== 'function') { 147 setTimeout(function () { syncWpConsentApi(consent); }, 50); 148 setTimeout(function () { syncWpConsentApi(consent); }, 250); 149 } 150 } 151 152 function renderPolicyConsentStatus() { 153 var el = document.getElementById('kncc-policy-consent-status'); 154 if (!el) return; 155 156 var labels = {}; 157 try { 158 labels = JSON.parse(el.getAttribute('data-labels') || '{}'); 159 } catch (e) { 160 labels = {}; 161 } 162 163 var cats = (el.getAttribute('data-categories') || '').split(',').filter(function (cat) { 164 return cat !== ''; 165 }); 166 if (!cats.length) return; 167 168 var strings = config.policyStatus || {}; 169 var consent = getConsent(); 170 171 el.textContent = ''; 172 173 if (!consent) { 174 var note = document.createElement('p'); 175 note.className = 'kncc-policy__consent-status-note'; 176 note.textContent = strings.noChoice || 'You have not made a choice yet.'; 177 el.appendChild(note); 178 return; 179 } 180 181 var list = document.createElement('ul'); 182 list.className = 'kncc-policy__consent-status-list'; 183 184 cats.forEach(function (cat) { 185 var item = document.createElement('li'); 186 var statusText; 187 if (cat === 'necessary') { 188 statusText = strings.alwaysActive || 'Always active'; 189 } else if (isCategoryGranted(consent, cat)) { 190 statusText = strings.allowed || 'Allowed'; 191 } else { 192 statusText = strings.denied || 'Denied'; 193 } 194 item.textContent = (labels[cat] || cat) + ': ' + statusText; 195 list.appendChild(item); 196 }); 197 198 el.appendChild(list); 199 } 200 201 function deleteCookie(name, domain) { 202 var expired = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/'; 203 document.cookie = expired; 204 if (domain) { 205 document.cookie = expired + ';domain=' + domain; 206 } 207 } 208 209 function getDeletionDomains() { 210 var hostname = location.hostname; 211 var domains = ['']; 212 if (hostname.indexOf('www.') === 0) { 213 domains.push('.' + hostname.substring(4)); 214 } 215 domains.push('.' + hostname); 216 var parts = hostname.split('.'); 217 if (parts.length > 2) { 218 domains.push('.' + parts.slice(-2).join('.')); 219 } 220 return domains; 221 } 222 223 function matchesClearPattern(name, pattern) { 224 if (!pattern) return false; 225 if (pattern.charAt(pattern.length - 1) === '*') { 226 return name.indexOf(pattern.slice(0, -1)) === 0; 227 } 228 return name === pattern; 229 } 230 231 function clearRejectedCategoryCookies(consent) { 232 var patterns = config.cookiePatterns || {}; 233 var domains = getDeletionDomains(); 234 var optional = ['analytics', 'marketing', 'preferences']; 235 236 document.cookie.split(';').forEach(function (entry) { 237 var name = entry.split('=')[0].trim(); 238 if (!name || name === COOKIE_NAME) return; 239 if (name.indexOf('wp_consent_') === 0) return; 240 241 optional.forEach(function (cat) { 242 if (consent[cat]) return; 243 (patterns[cat] || []).forEach(function (pattern) { 244 if (matchesClearPattern(name, pattern)) { 245 domains.forEach(function (domain) { 246 deleteCookie(name, domain || null); 247 }); 248 } 249 }); 250 }); 251 });
252 } 253 254 function recordStat(choice, consent) { 255 if (!config.ajaxUrl) return; 256 var body = new FormData(); 257 body.append('action', 'kncc_record_consent'); 258 body.append('choice', choice); 259 body.append('nonce', config.statsNonce || ''); 260 body.append('categories', JSON.stringify({ 261 necessary: true, 262 analytics: !!consent.analytics, 263 marketing: !!consent.marketing, 264 preferences: !!consent.preferences, 265 })); 266 // sendBeacon reports failure by returning false rather than throwing, so the 267 // return value has to be checked or a dropped beacon never reaches the fallback. 268 var queued = false; 269 try { 270 queued = !!(navigator.sendBeacon && navigator.sendBeacon(config.ajaxUrl, body)); 271 } catch (e) { 272 queued = false; 273 } 274 275 if (!queued) { 276 fetch(config.ajaxUrl, { method: 'POST', body: body, keepalive: true }).catch(function () {}); 277 } 278 } 279 280 /* ââ Script / iframe activation after consent ââ */ 281 282 function activateBlockedScripts(consent) { 283 // Keep the text/plain source in the DOM and mark it loaded. Removing it made 284 // Reject â Accept impossible without a full page reload (nothing left to run). 285 var blocked = document.querySelectorAll('script[type="text/plain"][data-kncc-category]'); 286 blocked.forEach(function (el) { 287 var cat = el.getAttribute('data-kncc-category'); 288 if (!consent[cat]) return; 289 if (el.getAttribute('data-kncc-loaded') === '1') return; 290 291 var newScript = document.createElement('script'); 292 newScript.type = 'text/javascript'; 293 newScript.setAttribute('data-kncc-injected', '1'); 294 newScript.setAttribute('data-kncc-category', cat); 295 296 if (el.src) { 297 newScript.src = el.src; 298 if (el.hasAttribute('async')) newScript.async = true; 299 if (el.hasAttribute('defer')) newScript.defer = true; 300 } else { 301 newScript.textContent = el.textContent; 302 } 303 304 el.parentNode.insertBefore(newScript, el.nextSibling); 305 el.setAttribute('data-kncc-loaded', '1'); 306 }); 307 308 var iframes = document.querySelectorAll('iframe[data-kncc-src][data-kncc-category]'); 309 iframes.forEach(function (el) { 310 var cat = el.getAttribute('data-kncc-category'); 311 if (!consent[cat]) return; 312 313 el.src = el.getAttribute('data-kncc-src'); 314 el.removeAttribute('data-kncc-src'); 315 el.classList.remove('kncc-blocked-iframe'); 316 var placeholder = el.previousElementSibling; 317 if (placeholder && placeholder.classList.contains('kncc-iframe-placeholder')) { 318 placeholder.remove(); 319 } 320 }); 321 } 322 323 /** 324 * After Reject / custom deny: allow the same scripts to run again on the next Accept 325 * without reloading the page. 326 */ 327 function resetBlockedScriptsForRejected(consent) { 328 var optional = ['analytics', 'marketing', 'preferences']; 329 optional.forEach(function (cat) { 330 if (consent[cat]) return; 331 332 document.querySelectorAll('script[type="text/plain"][data-kncc-category="' + cat + '"]').forEach(function (el) { 333 el.removeAttribute('data-kncc-loaded'); 334 }); 335 336 document.querySelectorAll('script[data-kncc-injected="1"][data-kncc-category="' + cat + '"]').forEach(function (el) { 337 el.remove(); 338 }); 339 340 document.querySelectorAll('iframe[data-kncc-category="' + cat + '"]').forEach(function (el) { 341 if (!el.getAttribute('data-kncc-src') && el.getAttribute('src')) { 342 el.setAttribute('data-kncc-src', el.getAttribute('src')); 343 el.removeAttribute('src'); 344 el.classList.add('kncc-blocked-iframe'); 345 } 346 }); 347 }); 348 } 349 350 function applyConsent(consent, choice) { 351 setConsent(consent); 352 scheduleWpConsentSync(consent); 353 updateConsentMode(consent); 354 pushDataLayer(consent); 355 activateBlockedScripts(consent); 356 if (choice) recordStat(choice, consent); 357 showFloatingBtn(); 358 hideBanner(); 359 renderPolicyConsentStatus(); 360 } 361 362 function acceptAll() { 363 var consent = { necessary: true, analytics: true, marketing: true, preferences: true, choice: 'accept' }; 364 applyConsent(consent, 'accept'); 365 } 366 367 function rejectAll() {
368 var consent = { necessary: true, analytics: false, marketing: false, preferences: false, choice: 'reject' }; 369 clearRejectedCategoryCookies(consent); 370 resetBlockedScriptsForRejected(consent); 371 applyConsent(consent, 'reject'); 372 } 373 374 function saveCustom() { 375 var consent = { necessary: true, choice: 'custom' }; 376 var optional = ['analytics', 'marketing', 'preferences']; 377 var checks = document.querySelectorAll('.kncc-category__check'); 378 var present = {}; 379 380 checks.forEach(function (el) { 381 var cat = el.getAttribute('data-category'); 382 if (cat !== 'necessary') { 383 consent[cat] = el.checked; 384 present[cat] = true; 385 } 386 }); 387 388 optional.forEach(function (cat) { 389 if (!present[cat]) { 390 consent[cat] = false; 391 } 392 }); 393 394 clearRejectedCategoryCookies(consent); 395 resetBlockedScriptsForRejected(consent); 396 applyConsent(consent, 'custom'); 397 } 398 399 function closeBanner() { 400 // Reveal the floating button first: hideBanner() restores focus to whatever 401 // opened the banner, which is often this button, and a hidden element cannot 402 // receive focus. 403 showFloatingBtn(); 404 hideBanner(); 405 } 406 407 var banner = null; 408 var details = null; 409 var floatingBtn = null; 410 var lastFocusedBeforeBanner = null; 411 412 var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'; 413 414 function isBannerVisible() { 415 return !!banner && banner.style.display !== 'none'; 416 } 417 418 function isModalBanner() { 419 return !!banner && banner.getAttribute('data-kncc-modal') === '1'; 420 } 421 422 function focusableInBanner() { 423 if (!banner) return []; 424 return Array.prototype.filter.call(banner.querySelectorAll(FOCUSABLE), function (el) { 425 return el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement; 426 }); 427 } 428 429 /** 430 * Keep Tab inside the modal banner. Bar and corner banners do not block the page, 431 * so tabbing out of them is the expected behaviour. 432 */ 433 function trapTab(e) { 434 if (e.key !== 'Tab' || !isModalBanner() || !isBannerVisible()) return; 435 436 var items = focusableInBanner(); 437 if (!items.length) return; 438 439 var first = items[0]; 440 var last = items[items.length - 1]; 441 442 if (e.shiftKey && document.activeElement === first) { 443 e.preventDefault(); 444 last.focus(); 445 } else if (!e.shiftKey && document.activeElement === last) { 446 e.preventDefault(); 447 first.focus(); 448 } 449 } 450 451 function showBanner(trigger) { 452 if (banner) { 453 lastFocusedBeforeBanner = trigger || document.activeElement; 454 455 banner.style.display = ''; 456 banner.removeAttribute('aria-hidden'); 457 458 var existing = getConsent(); 459 if (existing) { 460 var checks = banner.querySelectorAll('.kncc-category__check'); 461 checks.forEach(function (el) { 462 var cat = el.getAttribute('data-category'); 463 if (cat !== 'necessary' && existing[cat] !== undefined) { 464 el.checked = existing[cat]; 465 } 466 }); 467 } 468 469 // A modal banner traps focus, so it must receive it. A bar or corner banner 470 // shown automatically on page load must not steal focus from the document; 471 // it only takes focus when the visitor deliberately reopened it. 472 if (trigger || isModalBanner()) { 473 var items = focusableInBanner(); 474 if (items.length) { 475 items[0].focus(); 476 } 477 } 478 } 479 } 480 481 function hideBanner() { 482 if (banner) { 483 banner.style.display = 'none'; 484 banner.setAttribute('aria-hidden', 'true'); 485 } 486 if (details) { 487 details.style.display = 'none'; 488 } 489 490 // Return focus to whatever opened the banner so keyboard users are not dropped 491 // back at the top of the document. 492 if (lastFocusedBeforeBanner && document.contains(lastFocusedBeforeBanner)) { 493 try { lastFocusedBeforeBanner.focus(); } catch (e) {} 494 } 495 lastFocusedBeforeBanner = null; 496 } 497 498 function showFloatingBtn() { 499 if (floatingBtn) { 500 floatingBtn.style.display = ''; 501 } 502 } 503 504 function hideFloatingBtn() { 505 if (floatingBtn) { 506 floatingBtn.style.display = 'none'; 507 } 508 } 509 510 function toggleDetails() {
511 if (!details) return; 512 var hidden = details.style.display === 'none'; 513 details.style.display = hidden ? '' : 'none'; 514 } 515 516 /* ââ Inline iframe accept buttons ââ */ 517 518 function initIframeAcceptButtons() { 519 document.addEventListener('click', function (e) { 520 var btn = e.target.closest('[data-kncc-iframe-accept]'); 521 if (!btn) return; 522 523 var cat = btn.getAttribute('data-kncc-iframe-accept'); 524 var consent = getConsent() || { necessary: true }; 525 consent[cat] = true; 526 setConsent(consent); 527 scheduleWpConsentSync(consent); 528 updateConsentMode(consent); 529 pushDataLayer(consent); 530 531 // Activate all iframes for this category 532 var iframes = document.querySelectorAll('iframe[data-kncc-src][data-kncc-category="' + cat + '"]'); 533 iframes.forEach(function (el) { 534 el.src = el.getAttribute('data-kncc-src'); 535 el.removeAttribute('data-kncc-src'); 536 el.classList.remove('kncc-blocked-iframe'); 537 var placeholder = el.previousElementSibling; 538 if (placeholder && placeholder.classList.contains('kncc-iframe-placeholder')) { 539 placeholder.remove(); 540 } 541 }); 542 543 // Remove remaining placeholders for this category 544 var placeholders = document.querySelectorAll('.kncc-iframe-placeholder[data-kncc-category="' + cat + '"]'); 545 placeholders.forEach(function (el) { el.remove(); }); 546 renderPolicyConsentStatus(); 547 }); 548 } 549 550 /* ââ Global API for reopening consent from policy page or external links ââ */ 551 552 window.knccEditConsent = function (trigger) { 553 hideFloatingBtn(); 554 showBanner(trigger || document.activeElement); 555 }; 556 557 /** 558 * Delegated handler for the [kncc_cookie_settings] shortcode link and the policy 559 * page's "Edit cookie preferences" button, so neither needs an inline onclick. 560 */ 561 function initOpenSettingsControls() { 562 document.addEventListener('click', function (e) { 563 var trigger = e.target.closest('.kncc-open-settings, .kncc-edit-consent-btn'); 564 if (!trigger) return; 565 566 e.preventDefault(); 567 window.knccEditConsent(trigger); 568 }); 569 } 570 571 function init() { 572 renderPolicyConsentStatus(); 573 574 var consent = getConsent(); 575 if (consent) { 576 scheduleWpConsentSync(consent); 577 clearRejectedCategoryCookies(consent); 578 updateConsentMode(consent); 579 activateBlockedScripts(consent); 580 } 581 582 banner = document.getElementById('kncc-banner'); 583 floatingBtn = document.getElementById('kncc-floating-btn'); 584 585 initOpenSettingsControls(); 586 587 if (!banner) { 588 if (consent && floatingBtn) { 589 showFloatingBtn(); 590 } 591 return; 592 } 593 594 details = banner.querySelector('.kncc-details'); 595 596 banner.addEventListener('click', function (e) { 597 var target = e.target.closest('[data-kncc]'); 598 if (!target) return; 599 600 var action = target.getAttribute('data-kncc'); 601 switch (action) { 602 case 'accept': 603 acceptAll(); 604 break; 605 case 'reject': 606 rejectAll(); 607 break; 608 case 'toggle-details': 609 toggleDetails(); 610 break; 611 case 'close': 612 closeBanner(); 613 break; 614 case 'save': 615 saveCustom(); 616 break; 617 } 618 }); 619 620 if (floatingBtn) { 621 floatingBtn.addEventListener('click', function () { 622 hideFloatingBtn(); 623 showBanner(floatingBtn); 624 }); 625 } 626 627 document.addEventListener('keydown', function (e) { 628 if (!isBannerVisible()) return; 629 630 if (e.key === 'Escape') { 631 closeBanner(); 632 return; 633 } 634 635 trapTab(e); 636 }); 637 638 initIframeAcceptButtons(); 639 640 if (consent) { 641 showFloatingBtn(); 642 } else { 643 showBanner(); 644 } 645 } 646 647 var existingConsent = getConsent(); 648 if (existingConsent) { 649 scheduleWpConsentSync(existingConsent); 650 } 651 652 if (document.readyState === 'loading') { 653 document.addEventListener('DOMContentLoaded', init); 654 } else { 655 init(); 656 } 657})();
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.