1/* 2 * Copyright (C) 2012 PrimeBox 3 * 4 * This work is licensed under the Creative Commons 5 * Attribution 3.0 Unported License. To view a copy 6 * of this license, visit 7 * http://creativecommons.org/licenses/by/3.0/. 8 * 9 * Documentation available at: 10 * http://www.primebox.co.uk/projects/cookie-bar/ 11 * 12 * When using this software you use it at your own risk. We hold 13 * no responsibility for any damage caused by using this plugin 14 * or the documentation provided. 15 */ 16(function ($) { 17 $.cookieBar = function (options, val) { 18 if (options == 'cookies') { 19 var doReturn = 'cookies'; 20 } else if (options == 'set') { 21 var doReturn = 'set'; 22 } else { 23 var doReturn = false; 24 } 25 var URL = window.location.protocol + "//" + window.location.host + window.location.pathname; 26 var host = window.location.protocol + "//" + window.location.host; 27 var newURL; 28 if (URL.indexOf("168.11") > 0) { 29 newURL = URL.replace("pages/index.asp", "") + "pages/index.asp?title=privacy-policy"; 30 } else { 31 32 newURL = window.location.protocol + "//" + window.location.host + "/cookie-management"; 33 34 } 35 var defaults = { 36 message: 'In order to provide you with the best online experience this website uses cookies. By clicking \'Accept\', you agree to the storing of cookies. ', //Message displayed on bar 37 acceptButton: true, //Set to true to show accept/enable button 38 acceptText: 'Accept', //Text on accept/enable button 39 acceptFunction: function (cookieValue) { if (cookieValue === 'enabled' || cookieValue === 'accepted') { if (typeof window.gtmDeferred === 'function') { console.log('Consent given. Loading GTM...'); window.gtmDeferred(); window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'cookieConsentAccepted' }); } } }, // Function to run after accept and Load GTM after user accepts cookies 40 declineButton: true, //Set to true to show decline/disable button 41 declineText: 'Disable Cookies', //Text on decline/disable button 42 declineFunction: function (cookieValue) { if (cookieValue == 'enabled' || cookieValue == 'accepted') window.location = window.location.href; }, //Function to run after decline 43 policyButton: true, //Set to true to show Cookie Policy button 44 policyText: 'Cookie Policy.', //Text on Cookie Policy button 45 policyURL: newURL, //URL of Cookie Policy 46 manageCookiesButton: true, //Set to true to show Manage Cookies button 47 manageCookiesText: 'Manage Cookies', //Text on Manage Cookies button 48 manageCookiesURL: newURL, //URL of Manage Cookies pop up 49 autoEnable: true, //Set to true for cookies to be accepted automatically. Banner still shows 50 acceptOnContinue: false, //Set to true to accept cookies when visitor moves to another page 51 acceptOnScroll: false, //Set to true to accept cookies when visitor scrolls X pixels up or down 52 acceptAnyClick: false, //Set to true to accept cookies when visitor clicks anywhere on the page 53 expireDays: 365, //Number of days for cookieBar cookie to be stored for 54 renewOnVisit: false, //Renew the cookie upon revisit to website 55 forceShow: false, //Force cookieBar to show regardless of user cookie preference 56 effect: 'slide', //Options: slide, fade, hide 57 element: 'body', //Element to append/prepend cookieBar to. Remember "." for class or "#" for id. 58 append: false, //Set to true for cookieBar HTML to be placed at base of website. Actual position may change according to CSS 59 fixed: true, //Set to true to add the class "fixed" to the cookie bar. Default CSS should fix the position 60 bottom: true, //Force CSS when fixed, so bar appears at bottom of website 61 zindex: '', //Can be set in CSS, although some may prefer to set here 62 domain: String(window.location.hostname), //Location of privacy policy 63 referrer: String(document.referrer) //Where visitor has come from 64 }; 65 var options = $.extend(defaults, options); 66 67 //Sets expiration date for cookie 68 var expireDate = new Date(); 69 expireDate.setTime(expireDate.getTime() + (options.expireDays * 86400000)); 70 expireDate = expireDate.toGMTString(); 71 72 var cookieEntry = 'cb-enabled={value}; expires=' + expireDate + '; path=/'; 73 74 //Retrieves current cookie preference 75 var i, cookieValue = '', aCookie, aCookies = document.cookie.split('; '); 76 for (i = 0; i < aCookies.length; i++) { 77 aCookie = aCookies[i].split('='); 78 if (aCookie[0] == 'cb-enabled') { 79 cookieValue = aCookie[1]; 80 } 81 } 82 83 // Transition fallback for legacy users prior to new site going live 84 var cookieMigrationTimestamp = new Date('2025-08-01T11:45:00Z').getTime(); 85 var legacyCookieConsent = (cookieValue === 'accepted'); 86 var lastClosed = localStorage.getItem('cookieLastClosed'); 87 88 if (legacyCookieConsent && !localStorage.getItem('cookiePreference')) { 89 // If the user previously clicked "Okay, Thanks" but hasn't opted in to the new system 90 // And if the legacy cookie bar was likely shown before the new rollout 91 var now = new Date().getTime(); 92 var barPreviouslyClosed = lastClosed && parseInt(lastClosed, 10) < cookieMigrationTimestamp; 93 94 if (!lastClosed || barPreviouslyClosed) { 95 // Remove legacy cookie and force re-consent 96 document.cookie = 'cb-enabled=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; 97 cookieValue = ''; // Reset so bar will be shown again 98 } 99 } 100 101 102 var lastClosed = localStorage.getItem('cookieLastClosed'); 103 var preference = localStorage.getItem('cookiePreference'); 104 105 if (preference === 'disabled' && lastClosed) { 106 var hoursPassed = (new Date().getTime() - parseInt(lastClosed, 10)) / (1000 * 60 * 60); 107 if (hoursPassed >= 12) {
108 localStorage.removeItem('cookiePreference'); 109 localStorage.removeItem('cookieLastClosed'); 110 document.cookie = cookieEntry.replace('{value}', ''); 111 cookieValue = ''; 112 preference = ''; 113 } 114 } 115 116 if (!preference) { 117 // Delete existing GA cookies set before cookie consent updates 118 var gaCookies = ['_ga', '_ga_34HB6C06LC', '_gid', '_gat']; 119 gaCookies.forEach(function (name) { 120 // Try deleting both with and without domain prefix 121 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; 122 document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=' + window.location.hostname.replace(/^www\./, ''); 123 }); 124 } 125 126 //Sets up default cookie preference if not already set 127 if (cookieValue == '' && doReturn != 'cookies' && options.autoEnable) { 128 cookieValue = 'enabled'; 129 document.cookie = cookieEntry.replace('{value}', 'enabled'); 130 } else if ((cookieValue == 'accepted' || cookieValue == 'declined') && doReturn != 'cookies' && options.renewOnVisit) { 131 document.cookie = cookieEntry.replace('{value}', cookieValue); 132 } 133 if (options.acceptOnContinue) { 134 if (options.referrer.indexOf(options.domain) >= 0 && String(window.location.href).indexOf(options.policyURL) == -1 && doReturn != 'cookies' && doReturn != 'set' && cookieValue != 'accepted' && cookieValue != 'declined') { 135 doReturn = 'set'; 136 val = 'accepted'; 137 } 138 } 139 if (doReturn == 'cookies') { 140 //Returns true if cookies are enabled, false otherwise 141 if (cookieValue == 'enabled' || cookieValue == 'accepted') { 142 return true; 143 } else { 144 return false; 145 } 146 } else if (doReturn == 'set' && (val == 'accepted' || val == 'declined')) { 147 //Sets value of cookie to 'accepted' or 'declined' 148 document.cookie = cookieEntry.replace('{value}', val); 149 if (val == 'accepted') { 150 return true; 151 } else { 152 return false; 153 } 154 } else { 155 //Sets up enable/accept button if required 156 var message = options.message.replace('{policy_url}', options.policyURL); 157 158 if (options.acceptButton) { 159 var acceptButton = '<a href="" class="cb-enable">' + options.acceptText + '</a>'; 160 } else { 161 var acceptButton = ''; 162 } 163 //Sets up disable/decline button if required 164 if (options.declineButton) { 165 var declineButton = '<a href="" class="cb-disable">' + options.declineText + '</a>'; 166 } else { 167 var declineButton = ''; 168 } 169 //Sets up privacy policy button if required 170 if (options.policyButton) { 171 var policyButton = '<a href="' + options.policyURL + '" class="cb-policy">' + options.policyText + '</a>'; 172 } else { 173 var policyButton = ''; 174 } 175 //Sets up manage cookies button if required 176 if (options.manageCookiesButton) { 177 var manageCookiesButton = '<a href="#" class="cb-manage" onclick="showCookiePopup(event)">' + options.manageCookiesText + '</a>'; 178 } else { 179 var manageCookiesButton = ''; 180 } 181 182 // Toggle Label Update Function 183 function updateToggleLabel() { 184 const toggle = document.getElementById('cookieToggle'); 185 const label = document.getElementById('toggleStatus'); 186 if (toggle && label) { 187 label.textContent = toggle.checked ? 'Enabled' : 'Disabled'; 188 } 189 } 190 191 // Function to display cookie management popup 192 window.showCookiePopup = function (e) { 193 if (e) e.preventDefault(); // Prevent default link behavior 194 195 // Create backdrop if not already present 196 if (!document.getElementById('cookie-backdrop')) { 197 var backdrop = document.createElement('div'); 198 backdrop.id = 'cookie-backdrop'; 199 document.body.appendChild(backdrop); 200 } 201 202 // Create popup if not already present 203 if (!document.getElementById('cookie-popup')) { 204 var popup = document.createElement('div'); 205 popup.id = 'cookie-popup'; 206 popup.innerHTML = ` 207 <div class="popup-content"> 208 <button class="popup-close" onclick="closeCookiePopup()"> 209 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 210 <line x1="4" y1="4" x2="20" y2="20" stroke="black" stroke-width="2" stroke-linecap="round"/> 211 <line x1="20" y1="4" x2="4" y2="20" stroke="black" stroke-width="2" stroke-linecap="round"/> 212 </svg> 213 </button> 214 <span>Manage Your Cookies Preferences</span> 215 <p>When you visit a website, it may store or access information on your browser, primarily through cookies. This data could relate to you, your preferences, or your device, and is generally used to ensure the site functions as expected. While it does not usually identify you directly, it can enhance your browsing experience by making it more tailored to you.</p> 216 <p>As we value your privacy, you have the option to manage your cookie preferences. You can read more information below to learn more and adjust the default settings. However, restricting certain types of cookies may affect your experience on the site and limit the services we can provide.</p> 217 <div class="spacer clearboth"></div> 218 <strong>
218Analytics Cookies</strong> 219 <!-- Toggle + Label --> 220 <div class="cookie-toggle"> 221 <label class="toggle-switch" for="cookieToggle" title="toggle switch" tabindex="0" role="button" aria-label="Toggle switch" onkeydown="if(event.key==='Enter' || event.key===' ') this.click();"> 222 <input type="checkbox" id="cookieToggle" checked> 223 <span class="toggle-slider"></span> 224 </label> 225 <span id="toggleStatus">Enabled</span> 226 </div> 227 <p>These cookies help us track visitor numbers and traffic sources, allowing us to measure and enhance our site's performance. They enable us to identify the most and least popular pages and understand how users navigate through the site. The data collected by these cookies is aggregated and remains anonymous. If you choose to disable them, we will be unable to track your visits or monitor site performance.</p> 228 <div class="spacer clearboth"></div> 229 <strong>Essential Cookies</strong> 230 <p>These cookies are necessary for the website to function correctly, you can find more information on our `+ policyButton + `</p> 231 <button id="savePreferences">Save & Close</button> 232 <div class="clearboth"></div> 233 </div> 234 `; 235 document.body.appendChild(popup); 236 } 237 // ---- FOCUS TRAP ACCESSIBILITY START ---- 238 (function() { 239 const popup = document.getElementById('cookie-popup'); 240 const focusableSelectors = 'a, button, input, select, label, textarea, [tabindex]:not([tabindex="-1"])'; 241 const focusableEls = Array.from(popup.querySelectorAll(focusableSelectors)); 242 243 if (!focusableEls.length) return; 244 245 const firstEl = focusableEls[0]; 246 const lastEl = focusableEls[focusableEls.length - 1]; 247 248 // Move focus to first element 249 firstEl.focus(); 250 251 function trapFocus(e) { 252 if (e.key === 'Tab') { 253 if (e.shiftKey) { // Shift + Tab 254 if (document.activeElement === firstEl) { 255 e.preventDefault(); 256 lastEl.focus(); 257 } 258 } else { // Tab 259 if (document.activeElement === lastEl) { 260 e.preventDefault(); 261 firstEl.focus(); 262 } 263 } 264 } 265 if (e.key === 'Escape') { 266 closeCookiePopup(); 267 } 268 } 269 270 document.addEventListener('keydown', trapFocus); 271 272 // Override existing close function to remove listener and return focus 273 const oldClose = window.closeCookiePopup; 274 window.closeCookiePopup = function() { 275 oldClose(); // original popup hide 276 document.removeEventListener('keydown', trapFocus); 277 const manageLink = document.querySelector('.cb-manage'); 278 if (manageLink) manageLink.focus(); 279 }; 280 })(); 281 // FOCUS TRAP ACCESSIBILITY END 282 283 // Default toggle to ON, but respect saved preferences 284 let savedPreference = localStorage.getItem('cookiePreference'); 285 let toggle = document.getElementById('cookieToggle'); 286 287 if (savedPreference === "disabled") { 288 toggle.checked = false; 289 } else { 290 toggle.checked = true; // Default ON 291 } 292 293 // Add event listener for toggle change 294 toggle.addEventListener('change', updateToggleLabel); 295 296 // Sync label with current toggle state 297 updateToggleLabel(); 298 299 // Save button functionality 300 document.getElementById('savePreferences').addEventListener('click', function () { 301 let toggleState = document.getElementById('cookieToggle').checked; 302 if (toggleState) { 303 cookieAccept(); 304 } else { 305 cookieDecline(); 306 } 307 removeBar(); 308 closeCookiePopup(); 309 }); 310 311 // Show popup 312 document.getElementById('cookie-backdrop').style.display = 'block'; 313 document.getElementById('cookie-popup').style.display = 'block'; 314 }; 315 316 // Close popup function 317 window.closeCookiePopup = function () { 318 document.getElementById('cookie-backdrop').style.display = 'none'; 319 document.getElementById('cookie-popup').style.display = 'none'; 320 } 321 322 323 //Whether to add "fixed" class to cookie bar 324 if (options.fixed) { 325 if (options.bottom) { 326 var fixed = ' class="fixed bottom"'; 327 } else { 328 var fixed = ' class="fixed"'; 329 } 330 } else { 331 var fixed = ''; 332 } 333 if (options.zindex != '') { 334 var zindex = ' style="z-index:' + options.zindex + ';"'; 335 } else { 336 var zindex = ''; 337 } 338 339 // Check if user declined more than 12 hours ago 340 var lastClosed = localStorage.getItem('cookieLastClosed'); 341 var preference = localStorage.getItem('cookiePreference'); 342 343 if (preference === 'disabled' && lastClosed) { 344 var hoursPassed = (new Date().getTime() - parseInt(lastClosed, 10)) / (1000 * 60 * 60); 345 if (hoursPassed >= 12) { 346 // Clear decline preference so bar can reappear
347 localStorage.removeItem('cookiePreference'); 348 localStorage.removeItem('cookieLastClosed'); 349 document.cookie = cookieEntry.replace('{value}', ''); // Clear cb-enabled 350 cookieValue = ''; // So the check below sees it as unset 351 } 352 } 353 354 //Displays the cookie bar if arguments met 355 if (options.forceShow || cookieValue == 'enabled' || cookieValue == '') { 356 if (options.append) { 357 //$(options.element).append('<div id="cookie-bar"'+fixed+zindex+'><p>'+message+policyButton+manageCookiesButton+declineButton+acceptButton+'</p></div>'); 358 $(options.element).append('<div id="cookie-bar"' + fixed + zindex + '><p>' + message + policyButton + manageCookiesButton + acceptButton + '</p></div>'); 359 } else { 360 //$(options.element).prepend('<div id="cookie-bar"'+fixed+zindex+'><p>'+message+policyButton+manageCookiesButton+declineButton+acceptButton+'</p></div>'); 361 $(options.element).prepend('<div id="cookie-bar"' + fixed + zindex + '><p>' + message + policyButton + manageCookiesButton + acceptButton + '</p></div>'); 362 } 363 } 364 365 //accessibility cookie banner 366 function trapFocusInBanner() { 367 const banner = document.getElementById('cookie-bar'); 368 if (!banner) return; 369 370 const focusable = Array.from(banner.querySelectorAll('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])')); 371 if (!focusable.length) return; 372 373 // Focus the first element immediately 374 focusable[0].focus(); 375 376 // Loop focus inside the banner 377 banner.addEventListener('keydown', function(e) { 378 if (e.key !== 'Tab') return; 379 380 const first = focusable[0]; 381 const last = focusable[focusable.length - 1]; 382 383 if (e.shiftKey) { // Shift + Tab 384 if (document.activeElement === first) { 385 e.preventDefault(); 386 last.focus(); 387 } 388 } else { // Tab 389 if (document.activeElement === last) { 390 e.preventDefault(); 391 first.focus(); 392 } 393 } 394 }); 395 } 396 trapFocusInBanner(); 397 398 var removeBar = function (func) { 399 if (options.acceptOnScroll) $(document).off('scroll'); 400 if (typeof (func) === 'function') func(cookieValue); 401 if (options.effect == 'slide') { 402 $('#cookie-bar').slideUp(300, function () { $('#cookie-bar').remove(); const manageLink = document.querySelector('.cb-manage'); if (manageLink) manageLink.focus(); }); 403 } else if (options.effect == 'fade') { 404 $('#cookie-bar').fadeOut(300, function () { $('#cookie-bar').remove();const manageLink = document.querySelector('.cb-manage'); if (manageLink) manageLink.focus(); }); 405 } else { 406 $('#cookie-bar').hide(0, function () { $('#cookie-bar').remove(); const manageLink = document.querySelector('.cb-manage'); if (manageLink) manageLink.focus();}); 407 } 408 $(document).unbind('click', anyClick); 409 }; 410 var cookieAccept = function () { 411 document.cookie = cookieEntry.replace('{value}', 'accepted'); 412 localStorage.setItem('cookiePreference', 'enabled'); 413 removeBar(options.acceptFunction); 414 initTrackingAfterConsent(); 415 }; 416 var cookieDecline = function () { 417 var deleteDate = new Date(); 418 deleteDate.setTime(deleteDate.getTime() - (864000000)); // ~10 days ago 419 deleteDate = deleteDate.toGMTString(); 420 421 // Set preference flags 422 document.cookie = cookieEntry.replace('{value}', 'declined'); 423 localStorage.setItem('cookiePreference', 'disabled'); 424 localStorage.setItem('cookieLastClosed', new Date().getTime()); // track time of decline 425 localStorage.setItem('pendingCookieCleanup', 'true'); // defer analytics cookie deletion 426 427 removeBar(options.declineFunction); 428 deleteUserTrackingCookie(); 429 }; 430 431 // Tracking code if a user submits a form with the class trackUser on the submit 432 function setUserTrackingCookie() { 433 if (document.cookie.split('; ').find(row => row.startsWith('trackingID='))) { 434 return; // Cookie already exists 435 } 436 const trackingID = 'track_' + Math.random().toString(36).substr(2, 9); 437 const expirationDate = new Date(); 438 expirationDate.setFullYear(expirationDate.getFullYear() + 1); 439 document.cookie = `trackingID=${trackingID}; expires=${expirationDate.toUTCString()}; path=/; Secure; SameSite=Strict`; 440 } 441 442 function initTrackingAfterConsent() { 443 document.querySelectorAll('.trackUser').forEach(button => { 444 button.addEventListener('click', setUserTrackingCookie); 445 }); 446 } 447 448 function deleteUserTrackingCookie() { 449 document.cookie = "trackingID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; 450 } 451 452 // If user already accepted cookies, immediately enable tracking 453 if (document.cookie.split('; ').find(row => row.startsWith('cb-enabled=accepted')) || localStorage.getItem('cookiePreference') === 'enabled') { 454 initTrackingAfterConsent(); 455 } 456 457 // Load saved preference on pop-up open 458 window.onload = function () { 459 let savedPreference = localStorage.getItem('cookiePreference'); 460 if (savedPreference) { 461 let toggle = document.getElementById('cookieToggle'); 462 if (toggle) { 463 toggle.checked = (savedPreference === "enabled"); 464 document.getElementById('toggleText').innerText = (savedPreference === "enabled") ? "Cookies Enabled" : "Cookies Disabled"; 465 } 466 } 467 }; 468 469 // Auto-inject GTM on page load if user already gave consent 470 (function () { 471 var cookiePref = localStorage.getItem('cookiePreference'); 472 if ((cookiePref === 'enabled' || cookiePref === 'accepted') && typeof window.gtmDeferred === 'function') { 473 console.log('Consent previously given. Re-injecting GTM...'); 474 window.gtmDeferred(); 475 } 476 })(); 477 478 479 var anyClick = function (e) { 480 if (!$(e.target).hasClass('cb-policy')) cookieAccept(); 481 }; 482 483 $('#cookie-bar .cb-enable').click(function () { cookieAccept(); return false; }); 484 $('#cookie-bar .cb-disable').click(function () { cookieDecline(); return false; }); 485 if (options.acceptOnScroll) { 486 var scrollStart = $(document).scrollTop(), scrollNew, scrollDiff; 487 $(document).on('scroll', function () { 488 scrollNew = $(document).scrollTop(); 489 if (scrollNew > scrollStart) { 490 scrollDiff = scrollNew - scrollStart; 491 } else { 492 scrollDiff = scrollStart - scrollNew; 493 } 494 if (scrollDiff >= Math.round(options.acceptOnScroll)) cookieAccept(); 495 }); 496 } 497 if (options.acceptAnyClick) { 498 $(document).bind('click', anyClick); 499 } 500 // IF user changes their preference in the pop up in cookie managment page to off, this code runs after the localstorage cookie preference is updated and deletes the analytics cookies. 501 (function () { 502 if (localStorage.getItem('pendingCookieCleanup') === 'true') { 503 localStorage.removeItem('pendingCookieCleanup'); 504 505 var analyticsCookies = ['_ga', '_gid', '_gat']; 506 var allCookies = document.cookie.split(';'); 507
508 allCookies.forEach(function (cookie) { 509 var cookieName = cookie.split('=')[0].trim(); 510 511 if (analyticsCookies.includes(cookieName) || /^_ga/.test(cookieName)) { 512 var domainParts = window.location.hostname.split('.'); 513 for (var j = 0; j < domainParts.length - 1; j++) { 514 var domain = domainParts.slice(j).join('.'); 515 document.cookie = cookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.' + domain; 516 } 517 // Fallback delete without domain 518 document.cookie = cookieName + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'; 519 } 520 }); 521 } 522 })(); 523 } 524 }; 525})(jQuery);
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.