1 /* 2 * Copyright 2018 Leo Sutic <leo@sutic.nu> 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 /** 18 * A reference to the cookie consent notice manager. 19 * 20 * @namespace A cookie consent notice manager. The user can be in three states: 21 * no consent, implicit consent and explicit consent. No consent is the state the 22 * user starts in on first visiting the website. At that point a cookie is set 23 * that will indicate implicit consent if the user returns. When the user returns, 24 * they are considered to have given implicit consent. If the user then dismisses 25 * the cookie notification they move to explicit consent. 26 * 27 * <p>The actions to take when the user is in either of the consenting states 28 * are determined by functions <code>push</code>ed onto two lists: 29 * <code>self.cookie_consent_implicit</code> and <code>self.cookie_consent_explicit</code>. 30 * 31 * <p>You therefore start by creating these lists as needed, if they don't exist. For 32 * example, the following creates the list of actions to take in case of implicit consent: 33 * 34 * <pre><code>self.cookie_consent_implicit = self.cookie_consent_implicit || [];</code></pre> 35 * 36 * You then push functions onto the lists: 37 * 38 * <pre><code>self.cookie_consent_implicit.push(function () { 39 * console.log("Action taken on implicit consent."); 40 * })</code></pre> 41 * 42 * Finally, you call <code>cookie_consent.initialize()</code> to start the manager. 43 * 44 * <p><b>Note</b>: explicit consent implies implicit consent. The function above will 45 * also be called for explicit consent. 46 * 47 * <h2>UI Elements</h2> 48 * 49 * The notice is assumed to be an initially invisible (<code>display: none</code>) element 50 * in the page with the id <code>cookie-consent</code>. In it you should put the notice text 51 * and a button that, when clicked, calls <code>cookie_consent.consent()</code>. 52 * 53 * @example 54 * <!-- 55 * Load Google Analytics if the user has given implicit consent. 56 * --> 57 * <head> 58 * <script> 59 * function manage_cookie_consent() { 60 * // Set up what happens in case of implicit consent 61 * self.cookie_consent_implicit = self.cookie_consent_implicit || []; 62 * self.cookie_consent_implicit.push(function () { 63 * // Implicit consent means we load Google Analytics 64 * var script = document.createElement ("script"); 65 * script.async = true; 66 * script.src = "https://www.googletagmanager.com/gtag/js?id=YOUR_ID_HERE"; 67 * document.body.appendChild (script); 68 * 69 * window.dataLayer = window.dataLayer || []; 70 * function gtag(){dataLayer.push(arguments);} 71 * gtag('js', new Date()); 72 * 73 * gtag('config', 'YOUR_ID_HERE'); 74 * }); 75 * 76 * // Do the cookie detection 77 * cookie_consent.initialize (); 78 * } 79 * </script> 80 * <script async="async" defer="defer" 81 * src="cookie-consent.js">/* */</script> 82 * </head> 83 * <body onload="manage_cookie_consent()"> 84 * <div id="cookie-consent" style="display:none;"> 85 * <p> 86 * example.com uses cookies to make 87 * your browsing experience better. By using 88 * the site you agree to our use of cookies. 89 * <button 90 * type="button" 91 * onclick="cookie_consent.consent()"> 92 * OK 93 * </button> 94 * </p> 95 * </div> 96 * . 97 * . 98 * . 99 * </body> 100 */ 101 cookie_consent = { 102 /** 103 * The ID of the element that shows the cookie consent notice. 104 * 105 * @type String 106 */ 107 ELEMENT_ID : "cookie-consent", 108 109 /** 110 * The name of the consent cookie, where we store the user's implicit or explicit consent. 111 * 112 * @type String 113 */ 114 COOKIE_NAME : "cookie-consent", 115 116 /** 117 * Expiration of the consent cookie, in days. 118 * 119 * @type int 120 */ 121 COOKIE_EXPIRATION : 360, 122 123 /** 124 * Enable implicit consent. This will set a first-party session cookie 125 * on the first page view. On the second page view, the user is considered 126 * to have given implicit consent. 127 * 128 * @type boolean 129 */ 130 IMPLICIT_CONSENT : true, 131 132 /** 133 * State of the user's consent on page load. Either: 134 * <ul> 135 * <li><code>null</code> - no consent given, implicit or explicit 136 * <li><code>"implicit"</code> - implicit consent given, by continuing to use the 137 * website after being shown the notice. 138 * <li><code>"explicit"</code> - explicit consent given by clicking on the "OK" button 139 * in the notice and dismissing it. 140 * <li><code>"rejected"</code> - consent explicitly rejected (via DNT) 141 * </ul> 142 * 143 * @type String 144 */ 145 state : null, 146 147 /** 148 * Call to initialize the cookie consent object. 149 */ 150 initialize : function () { 151 var element = document.getElementById(this.ELEMENT_ID); 152 var state = this.getCookie(this.COOKIE_NAME); 153 this.state = state; 154 if (state == null) { 155 element.style.display = "block"; 156 // First party session cookies are OK 157 if (this.IMPLICIT_CONSENT) { 158 this.setCookie (this.COOKIE_NAME, "implicit", false); 159 } 160 } else if (window.doNotTrack == "1" || navigator.doNotTrack == "1") { 161 this.state = "rejected"; 162 } else if (state == "implicit") { 163 element.style.display = "block"; 164 this.runQueue ("cookie_consent_implicit"); 165 } else { 166 this.runQueue ("cookie_consent_implicit"); 167 this.runQueue ("cookie_consent_explicit"); 168 } 169 }, 170 171 /** 172 * Utility function to run one of the implicit or explicit event handlers. 173 * 174 * @param {String} name of the event handler. 175 * @private 176 */ 177 runQueue : function (name) { 178 var q = self[name]; 179 if (q != null && q.cookie_consent_instant_queue != true) { 180 for (var i = 0; i < q.length; ++i) { 181 q[i](); 182 } 183 self[name] = { 184 cookie_consent_instant_queue : true, 185 push : function (f) { 186 f(); 187 } 188 }; 189 } 190 }, 191 192 /** 193 * Give explicit consent. This should be called from the <code>onclick</code> event 194 * handler in the "OK" button in the cookie consent notice. 195 */ 196 consent : function () { 197 document.getElementById(this.ELEMENT_ID).style.display = "none"; 198 199 var state = this.getCookie(this.COOKIE_NAME); 200 201 this.setCookie (this.COOKIE_NAME, "explicit", true); 202 203 if (state == null || state == "implicit") { 204 this.runQueue ("cookie_consent_implicit"); 205 } 206 this.runQueue ("cookie_consent_explicit"); 207 window.dispatchEvent(new Event('resize')); 208 }, 209 210 /** 211 * Retracts consent and optionally reloads the current document. 212 * 213 * @param {boolean} reload reload the current document 214 */ 215 retractConsent : function (reload) { 216 this.clearCookie (this.COOKIE_NAME); 217 if (reload) { 218 location.reload (); 219 } 220 }, 221 222 /** 223 * Retrieves a cookie. 224 * 225 * @private 226 * @param {String} key the cookie name 227 * @type String 228 * @returns the cookie value, if it exists, <code>null</code> otherwise. 229 */ 230 getCookie : function (key) { 231 var cookies = document.cookie.split (";"); 232 for (var i = 0; i < cookies.length; ++i) { 233 var kv = cookies[i].split("="); 234 kv[0] = kv[0].replace(/^\s*/, "").replace(/\s*$/, ""); 235 if (kv[0] == key) { 236 return kv[1].replace(/^\s*/, "").replace(/\s*$/, ""); 237 } 238 } 239 return null; 240 }, 241 242 /** 243 * Sets a cookie. 244 * 245 * @private 246 * @param {String} key the cookie name 247 * @param {String} value the cookie value 248 */ 249 setCookie : function (key, value, persist) { 250 var d = new Date(); 251 d.setTime (d.getTime() + (this.COOKIE_EXPIRATION * 24 * 60 * 60 * 1000)); 252 var expires = persist ? ";expires=" + d.toUTCString () : ""; 253 254 document.cookie = key + "=" + value + expires + ";path=/"; 255 }, 256 257 /** 258 * Sets a cookie. 259 * 260 * @private 261 * @param {String} key the cookie name 262 * @param {String} value the cookie value 263 */ 264 clearCookie : function (key) { 265 var d = new Date(0); 266 var expires = ";expires=" + d.toUTCString (); 267 268 document.cookie = key + "=" + expires + ";path=/"; 269 } 270 }; 271 272