1/** 2 * GDPR Cookie Consent Banner 3 * Provides Accept All / Necessary Only / Decline options 4 * Stores consent level in cc_cookie_consent cookie 5 */ 6(function () { 7 'use strict'; 8 9 // The consent cookie (cc_cookie_consent) is HttpOnly + Secure and is only ever 10 // written server-side (backend.php?action=setGDPRCookie). It is never read or 11 // written from JS. The current level is delivered inline at page render time 12 // via window.__ccConsentLevel and kept up to date in memory after a choice. 13 14 function getConsent() { 15 return window.__ccConsentLevel || null; 16 } 17 18 // Persist the chosen level server-side; onDone (optional) fires when the 19 // HttpOnly cookie has been committed. 20 function setConsent(level, onDone) { 21 window.__ccConsentLevel = level; 22 23 var xhr = new XMLHttpRequest(); 24 xhr.open('GET', '/backend.php?action=setGDPRCookie&level=' + encodeURIComponent(level), true); 25 if (onDone) { 26 xhr.onreadystatechange = function () { 27 if (xhr.readyState === 4) onDone(); 28 }; 29 } 30 xhr.send(); 31 } 32 33 function removeAnalyticsCookies() { 34 var hostname = window.location.hostname; 35 var secure = location.protocol === 'https:' ? ';secure' : ''; 36 // Build list of domains to try: hostname, .hostname, and parent domain levels 37 // e.g. for club.intelligentgolf.co.uk: try .club.intelligentgolf.co.uk, .intelligentgolf.co.uk, .co.uk 38 var domains = ['', '.' + hostname]; 39 var parts = hostname.split('.'); 40 for (var d = 1; d < parts.length - 1; d++) { 41 domains.push('.' + parts.slice(d).join('.')); 42 } 43 // Known GA cookie names 44 var gaCookies = ['_ga', '_gid', '_gat', '__utma', '__utmb', '__utmc', '__utmz']; 45 // Also find any _ga_XXXXX cookies (GA4 measurement ID cookies) 46 var allCookies = document.cookie.split(';'); 47 for (var j = 0; j < allCookies.length; j++) { 48 var name = allCookies[j].split('=')[0].trim(); 49 if (name.indexOf('_ga_') === 0 && gaCookies.indexOf(name) === -1) { 50 gaCookies.push(name); 51 } 52 } 53 var expired = '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; 54 for (var i = 0; i < gaCookies.length; i++) { 55 for (var k = 0; k < domains.length; k++) { 56 var domainPart = domains[k] ? ';domain=' + domains[k] : ''; 57 document.cookie = gaCookies[i] + expired + ';path=/' + domainPart; 58 document.cookie = gaCookies[i] + expired + ';path=/' + domainPart + secure; 59 } 60 } 61 } 62 63 function hideBanner() { 64 var banner = document.getElementById('cc-consent-banner'); 65 if (banner) { 66 banner.style.opacity = '0'; 67 setTimeout(function () { 68 banner.parentNode.removeChild(banner); 69 }, 300); 70 } 71 } 72 73 // Tracks what consent was before the settings panel was opened 74 var _previousLevel = null; 75 76 function handleConsent(level) { 77 var previousLevel = _previousLevel || getConsent(); 78 _previousLevel = null; 79 // If changing away from 'all', reload to stop already-loaded GA scripts. 80 // Wait for the server to commit the HttpOnly cookie before reloading so the 81 // reloaded page sees the new consent level. 82 var needsReload = previousLevel === 'all' && level !== 'all'; 83 setConsent(level, needsReload ? function () { window.location.reload(); } : null); 84 if (level !== 'all') { 85 removeAnalyticsCookies(); 86 } 87 if (!needsReload) { 88 hideBanner(); 89 } 90 } 91 92 function showBanner(policyLink) { 93 // Don't show if consent already given 94 if (getConsent()) return; 95 96 var banner = document.createElement('div'); 97 banner.id = 'cc-consent-banner'; 98 banner.className = 'cc-consent-banner'; 99 banner.setAttribute('role', 'dialog'); 100 banner.setAttribute('aria-label', 'Cookie consent'); 101 102 banner.innerHTML = 103 '<div class="cc-consent-content">' + 104 '<p class="cc-consent-message">' + 105 'We use cookies to ensure the website works correctly and to improve your experience. ' + 106 'Some cookies are essential for the site to function, while others help us understand how you use the site. ' + 107 '<a href="' + policyLink + '" class="cc-consent-link">Read our cookie policy</a>' + 108 '</p>' + 109 '<div class="cc-consent-buttons">' + 110 '<button type="button" class="cc-btn cc-btn-accept" data-consent="all">Accept All</button>' + 111 '<button type="button" class="cc-btn cc-btn-necessary" data-consent="necessary">Necessary Only</button>' + 112 '<button type="button" class="cc-btn cc-btn-decline" data-consent="declined">Decline All</button>' + 113 '</div>' + 114 '</div>'; 115 116 document.body.appendChild(banner); 117 118 // Force reflow then show 119 banner.offsetHeight; 120 banner.style.opacity = '1'; 121 122 // Bind button clicks 123 var buttons = banner.querySelectorAll('.cc-btn'); 124 for (var i = 0; i < buttons.length; i++) { 125 buttons[i].addEventListener('click', function () { 126 handleConsent(this.getAttribute('data-consent')); 127 }); 128 } 129 } 130 131 function showSettingsPanel(policyLink) { 132 // Remove existing if present 133 var existing = document.getElementById('cc-consent-banner'); 134 if (existing) existing.parentNode.removeChild(existing); 135 136 // Remember current level before clearing, so handleConsent knows whether to reload 137 _previousLevel = getConsent(); 138 139 // Clear the in-memory consent so the banner shows. The server cookie is 140 // overwritten once the user makes a new choice (cannot be cleared from JS 141 // as it is HttpOnly). 142 window.__ccConsentLevel = null; 143 144 showBanner(policyLink); 145 } 146 147 // On page load, clean up any analytics cookies that shouldn't be present 148 // This catches cookies re-set by GA scripts during the reload race 149 var currentConsent = getConsent(); 150 if (currentConsent && currentConsent !== 'all') { 151 removeAnalyticsCookies(); 152 } 153 154 // Public API 155 window.CookieConsent = { 156 show: showBanner, 157 showSettings: showSettingsPanel, 158 getLevel: getConsent, 159 hasConsented: function () { return getConsent() !== null; }, 160 analyticsAllowed: function () { return getConsent() === 'all'; } 161 }; 162})();
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.