1function escapeHtml(str) { 2 return String(str == null ? '' : str) 3 .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') 4 .replace(/"/g, '"').replace(/'/g, '''); 5} 6 7function removeWildcardCookie(name) { 8 try { 9 var host = window.location.hostname; 10 var parts = host.split('.'); 11 for (var i = 1; i < parts.length - 1; i++) { 12 var d = '.' + parts.slice(i).join('.'); 13 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=' + d; 14 } 15 } catch (e) {} 16} 17 18function setCookie(name, value, days) { 19 var hostOnly = !!(window.cookie_bar && window.cookie_bar.settings && window.cookie_bar.settings.forceHostOnly); 20 if (hostOnly) { 21 removeWildcardCookie(name); 22 } 23 // Intentionally omit `domain` so the cookie is host-only per RFC 6265 (no Domain attribute). 24 Cookies.set(name, (value || ""), { expires: days, path: '/', sameSite: 'Lax' }); 25} 26 27function getCookieValue(name) { 28 const cookie = document.cookie.match(new RegExp(`(^|; )${name}=([^;]*)`)); 29 return cookie ? decodeURIComponent(cookie[2]) : null; 30} 31 32class CookieBar { 33 constructor(settings = {}, categories = []) { 34 this.defaultSettings = { 35 'settingsDuration': 3600 * 24 * 365, 36 'htmlFile': 'cookie_bar.html', 37 'appearDelay': 10, 38 'settingsCookieName': 'settings66' 39 }; 40 41 this.consentSettings = { 'granted': [], 'denied': [] }; 42 this.show = true; 43 this.snippets = {}; 44 this.settings = { ...this.defaultSettings, ...settings }; 45 this.categories = categories; 46 47 this.defaultConsentSettings = { 48 'granted': this.categories.filter(c => c.required).map(c => c.id), 49 'denied': this.categories.filter(c => !c.required).map(c => c.id) 50 }; 51 52 this.gcmMapping = {}; 53 this.categories.forEach(cat => { 54 this.gcmMapping[cat.id] = cat.gcm_types || []; 55 }); 56 57 let setConsentSettingsScript = "setCookie('" + this.settings['settingsCookieName'] + "', JSON.stringify(window.cookie_bar.getConsentSettings()), 365);"; 58 this.addSnippet(setConsentSettingsScript, "essential"); 59 60 if ((this.consentSettings = this.getConsentSettingsFromStorage()) === false) { 61 this.consentSettings = this.defaultConsentSettings; 62 } else { 63 if (this.isFullConsentStatusKnown()) { 64 this.show = false; 65 this.setDataLayerConsentStatus(); 66 } 67 } 68 } 69 70 start() { 71 if (this.show) { 72 this.render(); 73 } else { 74 this.executeSnippets(); 75 this.addReopenButton(); 76 } 77 } 78 79 addSnippet(snippet, requiredKey) { 80 if (this.snippets.hasOwnProperty(requiredKey) && (this.snippets[requiredKey].indexOf(snippet) === -1)) { 81 this.snippets[requiredKey].push(snippet); 82 } else { 83 this.snippets[requiredKey] = [snippet]; 84 } 85 } 86 87 executeSnippets() { 88 let snippetsQueue = []; 89 let resolvedKeys = new Set(); 90 91 for (let categoryId of this.consentSettings.granted) { 92 resolvedKeys.add(categoryId); 93 if (this.gcmMapping[categoryId]) { 94 this.gcmMapping[categoryId].forEach(gcmType => resolvedKeys.add(gcmType)); 95 } 96 } 97 98 for (let key of resolvedKeys) { 99 if (this.snippets.hasOwnProperty(key)) { 100 snippetsQueue = snippetsQueue.concat(this.snippets[key]); 101 } 102 } 103 104 // "Consent changed" snippets always run, and always last 105 if (this.snippets.hasOwnProperty('__consent_changed')) { 106 snippetsQueue = snippetsQueue.concat(this.snippets['__consent_changed']); 107 } 108 109 for (let snippet of snippetsQueue) { 110 var script = document.createElement('script'); 111 script.type = 'text/javascript'; 112 script.innerHTML = snippet; 113 document.head.appendChild(script); 114 } 115 } 116 117 getConsentSettingsFromStorage() { 118 let storage = JSON.parse(getCookieValue(this.settings['settingsCookieName'])); 119 if (!storage) return false;
120 121 let categoryIds = this.categories.map(c => c.id); 122 let allStored = [...(storage.granted || []), ...(storage.denied || [])]; 123 let hasOldFormat = allStored.length > 0 && !allStored.some(key => categoryIds.includes(key)); 124 if (hasOldFormat) return false; 125 126 return storage; 127 } 128 129 isFullConsentStatusKnown() { 130 let categoryIds = this.categories.map(c => c.id); 131 let allConsentValues = new Set([ 132 ...this.consentSettings.granted, 133 ...this.consentSettings.denied, 134 ]); 135 return categoryIds.every(id => allConsentValues.has(id)); 136 } 137 138 addReopenButton() { 139 let existing = document.getElementById('cookie-settings-reopen'); 140 if (existing) return; 141 142 let btn = document.createElement('button'); 143 btn.id = 'cookie-settings-reopen'; 144 btn.className = 'cookie-settings-reopen'; 145 btn.title = this.settings.reopenLabel || 'Cookie settings'; 146 btn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>'; 147 document.body.appendChild(btn); 148 149 let theClass = this; 150 btn.addEventListener('click', function () { 151 theClass.reopenSettings(); 152 }); 153 } 154 155 reopenSettings() { 156 let existingWrap = document.querySelector('.cookie-bar-wrap'); 157 if (existingWrap) existingWrap.remove(); 158 let reopenBtn = document.getElementById('cookie-settings-reopen'); 159 if (reopenBtn) reopenBtn.remove(); 160 this.show = true; 161 this.render(); 162 } 163 164 hideBanner() { 165 document.querySelector(".cookie-bar-wrap").classList.add("hidden"); 166 this.addReopenButton(); 167 } 168 169 rejectAll() { 170 let cs = this.getConsentSettings(); 171 cs.granted = this.categories.filter(c => c.required).map(c => c.id); 172 cs.denied = this.categories.filter(c => !c.required).map(c => c.id); 173 this.setConsentSettings(cs); 174 this.hideBanner(); 175 } 176 177 acceptAll() { 178 let cs = this.getConsentSettings(); 179 cs.granted = this.categories.map(c => c.id); 180 cs.denied = []; 181 this.setConsentSettings(cs); 182 this.hideBanner(); 183 } 184 185 saveSelected() { 186 let cs = this.getConsentSettings(); 187 let checkboxes = document.querySelectorAll(".cookie-bar-wrap .cb-switch-input"); 188 let newGranted = []; 189 let newDenied = []; 190 checkboxes.forEach(function (cb) { 191 if (cb.checked) newGranted.push(cb.id); 192 else newDenied.push(cb.id); 193 }); 194 cs.granted = newGranted; 195 cs.denied = newDenied; 196 this.setConsentSettings(cs); 197 this.hideBanner(); 198 } 199 200 render() { 201 let showDelay = this.settings['appearDelay']; 202 let theClass = this; 203 204 var request = new XMLHttpRequest(); 205 request.open('GET', this.settings['htmlFile'], true); 206 request.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 207 request.onload = function () { 208 if (this.status >= 200 && this.status < 400) { 209 document.querySelector("body").insertAdjacentHTML("afterbegin", this.response); 210 211 // Build layer 2 categories 212 let catContainer = document.querySelector(".cookie-bar-wrap .cb-categories"); 213 214 theClass.categories.forEach(category => { 215 let isGranted = theClass.consentSettings.granted.includes(category.id); 216 let checked = (category.required || isGranted) ? " checked" : ""; 217 let disabled = category.required ? " disabled" : ""; 218 219 let servicesHtml = '';
220 if (category.services && category.services.length > 0) { 221 let isDetailed = typeof category.services[0] === 'object' && category.services[0] !== null; 222 let listInner; 223 if (isDetailed) { 224 let cols = theClass.settings.serviceColumns || { 225 name: 'Name', provider: 'Provider / Domain', expiration: 'Expiration', description: 'Description' 226 }; 227 let head = 228 '<tr>' + 229 '<th>' + escapeHtml(cols.name) + '</th>' + 230 '<th>' + escapeHtml(cols.provider) + '</th>' + 231 '<th>' + escapeHtml(cols.expiration) + '</th>' + 232 '<th>' + escapeHtml(cols.description) + '</th>' + 233 '</tr>'; 234 let rows = category.services.map(s => 235 '<tr>' + 236 '<td>' + escapeHtml(s.name || '') + '</td>' + 237 '<td>' + escapeHtml(s.provider || '') + '</td>' + 238 '<td>' + escapeHtml(s.expiration || '') + '</td>' + 239 '<td>' + escapeHtml(s.description || '') + '</td>' + 240 '</tr>' 241 ).join(''); 242 listInner = '<table class="cb-services-table"><thead>' + head + '</thead><tbody>' + rows + '</tbody></table>'; 243 } else { 244 listInner = category.services.map(s => '<span class="cb-service-item">' + escapeHtml(s) + '</span>').join(''); 245 } 246 servicesHtml = 247 '<div class="cb-services">' + 248 '<button class="cb-services-toggle" type="button">' + 249 (theClass.settings.servicesLabel || 'Services') + 250 ' <span class="cb-chevron"></span>' + 251 '</button>' + 252 '<div class="cb-services-list' + (isDetailed ? ' cb-services-list--table' : '') + ' cb-hidden">' + listInner + '</div>' + 253 '</div>'; 254 } 255 256 let cat = document.createElement('div'); 257 cat.className = 'cb-category'; 258 cat.innerHTML = 259 '<div class="cb-category-header">' + 260 '<div class="cb-category-info">' + 261 '<span class="cb-category-title">' + category.title + '</span>' + 262 '<span class="cb-category-desc">' + (category.desc || '') + '</span>' + 263 '</div>' + 264 '<label class="cb-switch">' + 265 '<input class="cb-switch-input" type="checkbox" id="' + category.id + '"' + checked + disabled + '>' + 266 '<span class="cb-switch-track"></span>' + 267 '</label>' + 268 '</div>' + 269 servicesHtml; 270 271 catContainer.appendChild(cat); 272 273 if (!category.required) { 274 cat.querySelector('.cb-switch-input').addEventListener("change", function () { 275 theClass.updateConsentSettings(category.id, this.checked); 276 }); 277 } 278 }); 279 280 // Services expand toggles 281 document.querySelectorAll('.cb-services-toggle').forEach(function (btn) { 282 btn.addEventListener('click', function () { 283 let list = this.nextElementSibling; 284 list.classList.toggle('cb-hidden'); 285 this.querySelector('.cb-chevron').classList.toggle('cb-chevron--open'); 286 }); 287 }); 288 289 // Domains expand toggle 290 let domainToggle = document.querySelector('.cb-domains-toggle'); 291 if (domainToggle) { 292 domainToggle.addEventListener('click', function () { 293 let list = this.nextElementSibling; 294 list.classList.toggle('cb-hidden'); 295 this.querySelector('.cb-chevron').classList.toggle('cb-chevron--open'); 296 }); 297 } 298 299 // Layer switching
300 let layer1 = document.getElementById('cb-layer-1'); 301 let layer2 = document.getElementById('cb-layer-2'); 302 303 document.getElementById('customize-btn').addEventListener('click', function () { 304 layer1.classList.add('cb-hidden'); 305 layer2.classList.remove('cb-hidden'); 306 }); 307 308 document.getElementById('cb-back').addEventListener('click', function () { 309 layer2.classList.add('cb-hidden'); 310 layer1.classList.remove('cb-hidden'); 311 }); 312 313 // Close button (X) = reject all 314 document.getElementById('cb-close').addEventListener('click', function () { 315 theClass.rejectAll(); 316 }); 317 318 // Layer 1 buttons 319 document.getElementById('accept-all').addEventListener('click', function () { theClass.acceptAll(); }); 320 document.getElementById('reject-all').addEventListener('click', function () { theClass.rejectAll(); }); 321 322 // Layer 2 buttons 323 document.getElementById('accept-all-2').addEventListener('click', function () { theClass.acceptAll(); }); 324 document.getElementById('reject-all-2').addEventListener('click', function () { theClass.rejectAll(); }); 325 document.getElementById('accept-selected').addEventListener('click', function () { theClass.saveSelected(); }); 326 327 // Show 328 setTimeout(function () { 329 document.querySelector(".cookie-bar-wrap").classList.remove("hidden"); 330 }, showDelay); 331 332 } else { 333 console.log("Cookie bar HTML could not be loaded."); 334 } 335 }; 336 337 request.onerror = function () { 338 console.log("Cookie bar HTML could not be loaded."); 339 }; 340 request.send(); 341 } 342 343 getConsentSettings() { 344 return this.consentSettings; 345 } 346 347 setConsentSettings(consentSettings) { 348 this.consentSettings = consentSettings; 349 350 // First: explicitly deny all GCM types for denied categories 351 let deniedGcmTypes = new Set(); 352 let grantedGcmTypes = new Set(); 353 354 for (let categoryId of this.consentSettings.denied) { 355 if (this.gcmMapping[categoryId]) { 356 this.gcmMapping[categoryId].forEach(gcmType => deniedGcmTypes.add(gcmType)); 357 } 358 } 359 for (let categoryId of this.consentSettings.granted) { 360 if (this.gcmMapping[categoryId]) { 361 this.gcmMapping[categoryId].forEach(gcmType => grantedGcmTypes.add(gcmType)); 362 } 363 } 364 365 // Send denied updates for GCM types that are not granted 366 let consentUpdate = {}; 367 deniedGcmTypes.forEach(function(gcmType) { 368 if (!grantedGcmTypes.has(gcmType)) { 369 consentUpdate[gcmType] = 'denied'; 370 } 371 }); 372 if (Object.keys(consentUpdate).length > 0 && typeof gtag === 'function') { 373 gtag('consent', 'update', consentUpdate); 374 } 375 376 // Then execute granted snippets 377 this.executeSnippets(); 378 this.setDataLayerConsentStatus(); 379 } 380 381 updateConsentSettings(categoryId, granted) { 382 var isGranted = this.consentSettings['granted'].indexOf(categoryId); 383 var isDenied = this.consentSettings['denied'].indexOf(categoryId); 384 385 if (granted) { 386 if (isGranted === -1) this.consentSettings['granted'].push(categoryId); 387 if (isDenied !== -1) this.consentSettings['denied'].splice(isDenied, 1); 388 } else { 389 if (isDenied === -1) this.consentSettings['denied'].push(categoryId); 390 if (isGranted !== -1) this.consentSettings['granted'].splice(isGranted, 1); 391 } 392 } 393 394 setDataLayerConsentStatus() { 395 window.dataLayer = window.dataLayer || []; 396 window.dataLayer.push({ 'event': '' }); 397 } 398}
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.