1"use strict" 2 3function CookieConsentSettings(props) { 4 5 const self = this 6 let detailedSettingsShown = false 7 this.props = { 8 privacyPolicyUrl: undefined, // the URL of your privacy policy page 9 legalNoticeUrl: undefined, // the URL of you legal notice page (Impressum) 10 contentURL: "/cookie-consent-content", // this folder must contain the language-files in the needed languages (`[lang].js`) 11 buttonAgreeClass: "btn btn-primary", // the "Agree to all" buttons class 12 buttonDontAgreeClass: "btn btn-link text-decoration-none", // the "I do not agree" buttons class 13 buttonSaveClass: "btn btn-secondary", // the "Save selection" buttons class 14 autoShowModal: true, // disable autoShowModal on the privacy policy and legal notice pages, to make these pages readable 15 alsoUseLocalStorage: true, // if true, the settings are stored in localStorage, too 16 postSelectionCallback: undefined, // callback function, called after the user has saved the settings 17 lang: navigator.language, // the language, in which the modal is shown 18 defaultLang: "en", // default language, if `lang` is not available as translation in `cookie-consent-content` 19 categories: ["necessary", "statistics", "marketing", "personalization"], // the categories for selection, must be contained in the language files 20 cookieName: "cookie-consent-settings", // the name of the cookie in which the configuration is stored as JSON 21 cookieStorageDays: 365, // the duration the cookie configuration is stored on the client 22 modalId: "cookieConsentSettingsModal" // the id of the modal dialog element 23 } 24 if (!props.privacyPolicyUrl) { 25 console.error("please set `privacyPolicyUrl` in the props of cookieConsentSettings") 26 } 27 if (!props.legalNoticeUrl) { 28 console.error("please set `legalNoticeUrl` in the props of cookieConsentSettings") 29 } 30 for (const property in props) { 31 // noinspection JSUnfilteredForInLoop 32 this.props[property] = props[property] 33 } 34 this.lang = this.props.lang 35 if (this.lang.indexOf("-") !== -1) { 36 this.lang = this.lang.split("-")[0] 37 } 38 39 // read the cookie, and if its content does not fit the categories, remove it 40 const cookieContent = getCookie(this.props.cookieName) 41 if (cookieContent) { 42 try { 43 for (const category of this.props.categories) { 44 if (cookieContent[category] === undefined) { 45 console.log("cookie settings changed, removing settings cookie") 46 removeCookie(this.props.cookieName) 47 break 48 } 49 } 50 } catch (e) { 51 // problems with the cookie, remove it 52 console.warn("cookie settings changed, removing settings cookie", e) 53 removeCookie(this.props.cookieName) 54 } 55 } 56 57 /** 58 * Read the language file and render the modal 59 */ 60 fetchContent(self.lang, (result) => { 61 self.content = JSON.parse(result) 62 renderModal() 63 }) 64 65 function renderModal() { 66 const _t = self.content 67 const linkPrivacyPolicy = '<a href="' + self.props.privacyPolicyUrl + '" class="text-underline">' + _t.privacyPolicy + '</a>' 68 const linkLegalNotice = '<a href="' + self.props.legalNoticeUrl + '" class="btn btn-secondary btn-sm">' + _t.legalNotice + '</a>' 69 if (self.content[self.lang] === undefined) { 70 self.lang = self.props.defaultLang 71 } 72 self.content.body = self.content.body.replace(/--privacy-policy--/, linkPrivacyPolicy) 73 let optionsHtml = "" 74 for (const category of self.props.categories) { 75 const categoryContent = self.content.categories[category] 76 if (!categoryContent) { 77 console.error("no content for category", category, "found in language file", self.lang) 78 } 79 let descriptionList = "" 80 for (const descriptionElement of categoryContent.description) { 81 descriptionList += `<li class='list-group-item' style='font-size:90%;'>${descriptionElement}</li>` 82 } 83 optionsHtml += `<hr><div class="bccs-option" style='font-size:90%;' data-name="${category}"> 84 <div class="mb-1"> 85 <div class="form-check form-switch"> 86 <input class="form-check-input" type="checkbox" role="switch" id="bccs-checkbox-${category}" checked> 87 <label class="form-check-label" for="bccs-checkbox-${category}"><b>${categoryContent.title}</b></label> 88 </div> 89 </div> 90 <ul class='list-group list-group-flush'> 91 ${descriptionList} 92 </ul> 93 </div>` 94 } 95 self.modalContent = ` 96<div class="modal-dialog modal-lg shadow" role="document"> 97 <div class="modal-content"> 98 <div class="modal-header"> 99 <img src="https://img.coupontools.com/2020/logo/logo3.svg" height="57.52" width="300" alt="Logo Coupontools.com | Coupontools Digital Loyalty Cards, Coupons and Digital Payment Vouchers."> 100 <h4 class="modal-title">${self.content.title}</h4> 101 </div> 102 <div class="modal-body"> 103 <div class="bccs-body-text mb-2" style="font-size: 80%"> 104 <p>${self.content.body}</p> 105 </div> 106 <p class="d-flex justify-content-between mb-0"> 107 ${linkLegalNotice} 108 <a href="#bccs-options" class='btn btn-secondary btn-sm' data-toggle="collapse">${self.content.mySettings}</a> 109 </p> 110 <div id="bccs-options" class="collapse"> 111 <div class="mt-4"> 112 ${optionsHtml} 113 </div> 114 </div> 115 </div> 116 <div class="modal-footer"> 117 <button id="bccs-buttonSave" type="button" class="${self.props.buttonSaveClass}"> 118 ${self.content.buttonSaveSelection} 119 </button>
120 <button id="bccs-buttonAgreeAll" type="button" class="${self.props.buttonAgreeClass}">${self.content.buttonAgreeAll}</button> 121 <button id="bccs-buttonAgree" type="button" class="${self.props.buttonAgreeClass}">${self.content.buttonAgree}</button> 122 </div> 123 </div> 124</div>` 125 if (!getCookie(self.props.cookieName) && self.props.autoShowModal) { 126 showDialog() 127 } 128 } 129 130 function showDialog() { 131 documentReady(function () { 132 self.modalElement = document.getElementById(self.props.modalId) 133 if (!self.modalElement) { 134 self.modalElement = document.createElement("div") 135 self.modalElement.id = self.props.modalId 136 self.modalElement.setAttribute("class", "modal fade") 137 self.modalElement.setAttribute("tabindex", "-1") 138 self.modalElement.setAttribute("role", "dialog") 139 self.modalElement.setAttribute("aria-labelledby", self.props.modalId) 140 self.modalElement.innerHTML = self.modalContent 141 document.body.append(self.modalElement) 142 if (self.props.postSelectionCallback) { 143 $("#"+self.props.modalId).on("hidden.bs.modal", function () { 144 self.props.postSelectionCallback() 145 }) 146 } 147 self.modal = new bootstrap.Modal(self.modalElement, { 148 backdrop: "static", 149 keyboard: false 150 }) 151 self.modal.show() 152 //self.buttonDoNotAgree = self.modalElement.querySelector("#bccs-buttonDoNotAgree") 153 self.buttonAgree = self.modalElement.querySelector("#bccs-buttonAgree") 154 self.buttonSave = self.modalElement.querySelector("#bccs-buttonSave") 155 self.buttonAgreeAll = self.modalElement.querySelector("#bccs-buttonAgreeAll") 156 updateButtons() 157 updateOptionsFromCookie() 158 $('#bccs-options').on('hide.bs.collapse', function () { 159 detailedSettingsShown = false 160 updateButtons() 161 }); 162 $('#bccs-options').on('show.bs.collapse', function () { 163 detailedSettingsShown = true 164 updateButtons() 165 }); 166 167 /*self.buttonDoNotAgree.addEventListener("click", function () { 168 doNotAgree() 169 })*/ 170 self.buttonAgree.addEventListener("click", function () { 171 agreeAll() 172 }) 173 self.buttonSave.addEventListener("click", function () { 174 saveSettings() 175 }) 176 self.buttonAgreeAll.addEventListener("click", function () { 177 agreeAll() 178 }) 179 } else { 180 self.modal.show() 181 } 182 }.bind(this)) 183 } 184 185 function updateOptionsFromCookie() { 186 const settings = self.getSettings() 187 if (settings) { 188 for (let setting in settings) { 189 const checkboxElement = self.modalElement.querySelector("#bccs-checkbox-" + setting) 190 checkboxElement.checked = settings[setting] === "true" 191 } 192 } 193 const checkboxNecessary = self.modalElement.querySelector("#bccs-checkbox-necessary") 194 checkboxNecessary.checked = true 195 checkboxNecessary.disabled = true 196 } 197 198 function updateButtons() { 199 if (detailedSettingsShown) { 200 //self.buttonDoNotAgree.style.display = "none" 201 self.buttonAgree.style.display = "none" 202 self.buttonSave.style.removeProperty("display") 203 self.buttonAgreeAll.style.removeProperty("display") 204 } else { 205 //self.buttonDoNotAgree.style.removeProperty("display") 206 self.buttonAgree.style.removeProperty("display") 207 self.buttonSave.style.display = "none" 208 self.buttonAgreeAll.style.display = "none" 209 } 210 } 211 212 function gatherOptions(setAllTo = undefined) { 213 const options = {} 214 for (const category of self.props.categories) { 215 if (setAllTo === undefined) { 216 const checkbox = self.modalElement.querySelector("#bccs-checkbox-" + category) 217 if (!checkbox) { 218 console.error("checkbox not found for category", category) 219 } 220 options[category] = checkbox.checked 221 } else { 222 options[category] = setAllTo 223 } 224 } 225 options["necessary"] = true // necessary is necessary 226 return options 227 } 228 229 function agreeAll() {
230 setCookie(self.props.cookieName, gatherOptions(true), self.props.cookieStorageDays) 231 self.modal.hide() 232 } 233 234 function doNotAgree() { 235 setCookie(self.props.cookieName, gatherOptions(false), self.props.cookieStorageDays) 236 self.modal.hide() 237 } 238 239 function saveSettings() { 240 setCookie(self.props.cookieName, gatherOptions(), self.props.cookieStorageDays) 241 self.modal.hide() 242 } 243 244 function fetchContent(lang, callback) { 245 const request = new XMLHttpRequest() 246 request.overrideMimeType("application/json"); 247 const url = self.props.contentURL + '/en.json'; 248 request.open('GET', url, true) 249 request.onreadystatechange = function () { 250 if (request.readyState === 4 && request.status === 200) { 251 if (request.status === 200) { 252 callback(request.responseText) 253 } else { 254 console.error(url, request.status) 255 } 256 } 257 } 258 request.onloadend = function () { 259 if (request.status === 404 && lang !== self.props.defaultLang) { 260 console.warn("language " + lang + " not found trying defaultLang " + self.props.defaultLang) 261 fetchContent(self.props.defaultLang, callback) 262 } 263 } 264 request.send(null) 265 } 266 267 function setCookie(name, object, days) { 268 let expires = "" 269 if (days) { 270 const date = new Date() 271 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)) 272 expires = "; expires=" + date.toUTCString() 273 } 274 const value = new URLSearchParams(object).toString() 275 document.cookie = name + "=" + (value || "") + expires + "; Path=/; SameSite=Strict;" 276 // store value also in localStorage 277 localStorage.setItem(name, value) 278 } 279 280 function getCookie(name) { 281 const nameEQ = name + "=" 282 const ca = document.cookie.split(';') 283 for (let i = 0; i < ca.length; i++) { 284 let c = ca[i] 285 while (c.charAt(0) === ' ') { 286 c = c.substring(1, c.length) 287 } 288 if (c.indexOf(nameEQ) === 0) { 289 const urlSearchParams = new URLSearchParams(c.substring(nameEQ.length, c.length)) 290 const result = {} 291 for (const [key, value] of urlSearchParams) { 292 result[key] = value 293 } 294 return result 295 } 296 } 297 // if cookie not found, try localStorage 298 const value = localStorage.getItem(name) 299 if (value) { 300 const urlSearchParams = new URLSearchParams(value) 301 const result = {} 302 for (const [key, value] of urlSearchParams) { 303 result[key] = value 304 } 305 setCookie(name, result, self.props.cookieStorageDays) 306 return result 307 } 308 return null 309 } 310 311 function removeCookie(name) { 312 document.cookie = name + '=; Path=/; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:01 GMT;' 313 } 314 315 function documentReady(callback) { 316 if (document.readyState !== 'loading') { 317 callback() 318 } else { 319 document.addEventListener('DOMContentLoaded', callback) 320 } 321 } 322 323 // API 324 this.showDialog = function () { 325 showDialog() 326 } 327 this.getSettings = function (optionName) { 328 const cookieContent = getCookie(self.props.cookieName) 329 if (cookieContent) { 330 if (optionName === undefined) { 331 return cookieContent 332 } else { 333 if (cookieContent) { 334 return cookieContent[optionName] 335 } else { 336 return false 337 } 338 } 339 } else { 340 return undefined 341 } 342 } 343 this.setSetting = function (name, value) { 344 let settings = self.getSettings() || {} 345 for (const category of this.props.categories) { 346 if(settings[category] === undefined) { 347 settings[category] = true 348 } 349 } 350 settings[name] = value
351 setCookie(self.props.cookieName, settings, self.props.cookieStorageDays) 352 } 353}
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.