1(function(cc) { 2 // stop from running again, if accidently included more than once. 3 if (cc.hasInitialised) return; 4 5 var util = { 6 // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex 7 escapeRegExp: function(str) { 8 return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); 9 }, 10 11 hasClass: function(element, selector) { 12 var s = ' '; 13 return element.nodeType === 1 && 14 (s + element.className + s).replace(/[\n\t]/g, s).indexOf(s + selector + s) >= 0; 15 }, 16 17 addClass: function(element, className) { 18 element.className += ' ' + className; 19 }, 20 21 removeClass: function(element, className) { 22 var regex = new RegExp('\\b' + this.escapeRegExp(className) + '\\b'); 23 element.className = element.className.replace(regex, ''); 24 }, 25 26 interpolateString: function(str, callback) { 27 var marker = /{{([a-z][a-z0-9\-_]*)}}/ig; 28 return str.replace(marker, function(matches) { 29 return callback(arguments[1]) || ''; 30 }) 31 }, 32 33 getCookie: function(name) { 34 var value = '; ' + document.cookie; 35 var parts = value.split('; ' + name + '='); 36 return parts.length != 2 ? 37 undefined : parts.pop().split(';').shift(); 38 }, 39
40 setCookie: function(name, value, expiryDays, domain, path) { 41 var exdate = new Date(); 42 exdate.setDate(exdate.getDate() + (expiryDays || 365)); 43 44 var cookie = [ 45 name + '=' + value, 46 'expires=' + exdate.toUTCString(), 47 'path=' + (path || '/') 48 ]; 49 50 if (domain) { 51 cookie.push('domain=' + domain); 52 } 53 document.cookie = cookie.join(';'); 54 }, 55 56 // only used for extending the initial options 57 deepExtend: function(target, source) { 58 for (var prop in source) { 59 if (source.hasOwnProperty(prop)) { 60 if (prop in target && this.isPlainObject(target[prop]) && this.isPlainObject(source[prop])) { 61 this.deepExtend(target[prop], source[prop]); 62 } else { 63 target[prop] = source[prop]; 64 } 65 } 66 } 67 return target; 68 }, 69 70 // only used for throttling the 'mousemove' event (used for animating the revoke button when `animateRevokable` is true) 71 throttle: function(callback, limit) { 72 var wait = false; 73 return function() { 74 if (!wait) { 75 callback.apply(this, arguments); 76 wait = true; 77 setTimeout(function() { 78 wait = false; 79 }, limit); 80 } 81 } 82 }, 83 84 // only used for hashing json objects (used for hash mapping palette objects, used when custom colours are passed through JavaScript) 85 hash: function(str) { 86 var hash = 0, 87 i, chr, len; 88 if (str.length === 0) return hash; 89 for (i = 0, len = str.length; i < len; ++i) { 90 chr = str.charCodeAt(i); 91 hash = ((hash << 5) - hash) + chr; 92 hash |= 0; 93 } 94 return hash; 95 }, 96 97 normaliseHex: function(hex) { 98 if (hex[0] == '#') { 99 hex = hex.substr(1); 100 } 101 if (hex.length == 3) { 102 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; 103 } 104 return hex; 105 }, 106 107 // used to get text colors if not set 108 getContrast: function(hex) { 109 hex = this.normaliseHex(hex); 110 var r = parseInt(hex.substr(0, 2), 16); 111 var g = parseInt(hex.substr(2, 2), 16); 112 var b = parseInt(hex.substr(4, 2), 16); 113 var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000; 114 return (yiq >= 128) ? '#000' : '#fff'; 115 }, 116 117 // used to change color on highlight 118 getLuminance: function(hex) { 119 var num = parseInt(this.normaliseHex(hex), 16), 120 amt = 38, 121 R = (num >> 16) + amt, 122 B = (num >> 8 & 0x00FF) + amt, 123 G = (num & 0x0000FF) + amt; 124 var newColour = (0x1000000 + (R<255?R<1?0:R:255)*0x10000 + (B<255?B<1?0:B:255)*0x100 + (G<255?G<1?0:G:255)).toString(16).slice(1); 125 return '#'+newColour; 126 }, 127 128 isMobile: function() { 129 return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 130 }, 131 132 isPlainObject: function(obj) { 133 // The code "typeof obj === 'object' && obj !== null" allows Array objects 134 return typeof obj === 'object' && obj !== null && obj.constructor == Object; 135 }, 136 }; 137 138 // valid cookie values 139 cc.status = { 140 deny: 'deny', 141 allow: 'allow', 142 dismiss: 'dismiss', 143 settings: 'settings' 144 }; 145 146 // detects the `transitionend` event name 147 cc.transitionEnd = (function() { 148 var el = document.createElement('div'); 149 var trans = { 150 t: "transitionend", 151 OT: "oTransitionEnd", 152 msT: "MSTransitionEnd", 153 MozT: "transitionend", 154 WebkitT: "webkitTransitionEnd", 155 }; 156 157 for (var prefix in trans) { 158 if (trans.hasOwnProperty(prefix) && typeof el.style[prefix + 'ransition'] != 'undefined') { 159 return trans[prefix]; 160 } 161 } 162 return ''; 163 }()); 164 165 cc.hasTransition = !!cc.transitionEnd; 166 167 // array of valid regexp escaped statuses 168 var __allowedStatuses = Object.keys(cc.status).map(util.escapeRegExp); 169 170 // contains references to the custom <style> tags 171 cc.customStyles = {}; 172 173 cc.Popup = (function() { 174 175 var defaultOptions = { 176 177 // if false, this prevents the popup from showing (useful for giving to control to another piece of code) 178 enabled: true, 179 180 // optional (expecting a HTML element) if passed, the popup is appended to this element. default is `document.body` 181 container: null, 182 183 // defaults cookie options - it is RECOMMENDED to set these values to correspond with your server 184 cookie: { 185 // This is the name of this cookie - you can ignore this 186 name: 'cookieconsent_status', 187 188 // This is the url path that the cookie 'name' belongs to. The cookie can only be read at this location 189 path: '/', 190 191 // This is the domain that the cookie 'name' belongs to. The cookie can only be read on this domain. 192 // - Guide to cookie domains - http://erik.io/blog/2014/03/04/definitive-guide-to-cookie-domains/ 193 domain: '', 194 195 // The cookies expire date, specified in days (specify -1 for no expiry) 196 expiryDays: 365, 197 }, 198 199 // these callback hooks are called at certain points in the program execution 200 onPopupOpen: function() {}, 201 onPopupClose: function() {}, 202 onInitialise: function(status) {}, 203 onStatusChange: function(status, chosenBefore) {}, 204 onRevokeChoice: function() {}, 205 206 // each item defines the inner text for the element that it references 207 content: {
208 header: 'Cookies used on the website!', 209 message: 'This website uses cookies to ensure you get the best experience on our website.', 210 dismiss: 'Got it!', 211 allow: 'Allow cookies', 212 deny: 'Decline', 213 link: 'Learn more', 214 settings: 'Cookie Preferences', 215 href: 'http://isenselabs.com', 216 close: '❌', 217 }, 218 219 // This is the HTML for the elements above. The string {{header}} will be replaced with the equivalent text below. 220 // You can remove "{{header}}" and write the content directly inside the HTML if you want. 221 // 222 // - ARIA rules suggest to ensure controls are tabbable (so the browser can find the first control), 223 // and to set the focus to the first interactive control (http://w3c.github.io/aria-in-html/) 224 elements: { 225 header: '<span class="cc-header">{{header}}</span> ', 226 message: '<span id="cookieconsent:desc" class="cc-message">{{message}}</span>', 227 messagelink: '<span id="cookieconsent:desc" class="cc-message">{{message}} <a aria-label="learn more about cookies" role=button tabindex="0" class="cc-link" href="{{href}}" rel="noopener noreferrer nofollow" target="_blank">{{link}}</a></span>', 228 dismiss: '<a aria-label="dismiss cookie message" role=button tabindex="0" class="cc-btn cc-dismiss">{{dismiss}}</a>', 229 allow: '<a aria-label="allow cookies" role=button tabindex="0" class="cc-btn cc-allow">{{allow}}</a>', 230 deny: '<a aria-label="deny cookies" role=button tabindex="0" class="cc-btn cc-deny">{{deny}}</a>', 231 settings: '<a aria-label="settings cookies" role=button tabindex="0" class="cc-btn cc-settings">{{settings}}</a>', 232 link: '<a aria-label="learn more about cookies" role=button tabindex="0" class="cc-link" href="{{href}}" target="_blank">{{link}}</a>', 233 close: '<span aria-label="dismiss cookie message" role=button tabindex="0" class="cc-close">{{close}}</span>' 234 //compliance: compliance is also an </span>' 235 //compliance: compliance is also an , Marketing</span>' 236 //compliance: compliance is also an element, but it is generated by the application, depending on `type` below 237 }, 238 239 // The placeholders {{classes}} and {{children}} both get replaced during initialisation: 240 // - {{classes}} is where additional classes get added 241 // - {{children}} is where the HTML children are placed 242 window: '<div role="dialog" aria-live="polite" aria-label="cookieconsent" aria-describedby="cookieconsent:desc" class="cc-window {{classes}}"><!--googleoff: all-->{{children}}<!--googleon: all--></div>', 243 244 // This is the html for the revoke button. This only shows up after the user has selected their level of consent 245 // It can be enabled of disabled using the `revokable` option 246 revokeBtn: '<div class="cc-revoke {{classes}}">Cookie Policy</div>', 247 248 // define types of 'compliance' here. '{{value}}' strings in here are linked to `elements` 249 compliance: { 250 'info': '<div class="cc-compliance">{{dismiss}}</div>', 251 'opt-in': '<div class="cc-compliance cc-highlight">{{dismiss}}{{settings}}{{allow}}</div>', 252 'opt-out': '<div class="cc-compliance cc-highlight">{{deny}}{{dismiss}}</div>', 253 }, 254 255 // select your type of popup here 256 type: 'info', // refers to `compliance` (in other words, the buttons that are displayed) 257 258 // define layout layouts here 259 layouts: { 260 // the 'block' layout tend to be for square floating popups 261 'basic': '{{messagelink}}{{compliance}}', 262 'basic-close': '{{messagelink}}{{compliance}}{{close}}', 263 'basic-header': '{{header}}{{message}}{{link}}{{compliance}}', 264 265 // add a custom layout here, then add some new css with the class '.cc-layout-my-cool-layout' 266 //'my-cool-layout': '<div class="my-special-layout">{{message}}{{compliance}}</div>{{close}}', 267 }, 268 269 // default layout (see above) 270 layout: 'basic', 271 272 // this refers to the popup windows position. we currently support: 273 // - banner positions: top, bottom 274 // - floating positions: top-left, top-right, bottom-left, bottom-right 275 // 276 // adds a class `cc-floating` or `cc-banner` which helps when styling 277 position: 'bottom', // default position is 'bottom' 278 279 // Available styles 280 // -block (default, no extra classes) 281 // -edgeless 282 // -classic 283 // use your own style name and use `.cc-theme-STYLENAME` class in CSS to edit. 284 // Note: style "wire" is used for the configurator, but has no CSS styles of its own, only palette is used. 285 theme: 'block', 286 287 // The popup is `fixed` by default, but if you want it to be static (inline with the page content), set this to fal
287se 288 // Note: by default, we animate the height of the popup from 0 to full size 289 static: false, 290 291 // if you want custom colours, pass them in here. this object should look like this. 292 // ideally, any custom colours/themes should be created in a separate style sheet, as this is more efficient. 293 // { 294 // popup: {background: '#000000', text: '#fff', link: '#fff'}, 295 // button: {background: 'transparent', border: '#f8e71c', text: '#f8e71c'}, 296 // highlight: {background: '#f8e71c', border: '#f8e71c', text: '#000000'}, 297 // } 298 // `highlight` is optional and extends `button`. if it exists, it will apply to the first button 299 // only background needs to be defined for every element. if not set, other colors can be calculated from it 300 palette: null, 301 302 // Some countries REQUIRE that a user can change their mind. You can configure this yourself. 303 // Most of the time this should be false, but the `cookieconsent.law` can change this to `true` if it detects that it should 304 revokable: false, 305 306 alwaysShow: false, 307 308 // if true, the revokable button will tranlate in and out 309 animateRevokable: true, 310 311 // used to disable link on existing layouts 312 // replaces element messagelink with message and removes content of link 313 showLink: true, 314 315 // set value as scroll range to enable 316 dismissOnScroll: false, 317 318 // set value as time in milliseconds to autodismiss after set time 319 dismissOnTimeout: false, 320 321 // The application automatically decide whether the popup should open. 322 // Set this to false to prevent this from happening and to allow you to control the behaviour yourself 323 autoOpen: true, 324 325 // By default the created HTML is automatically appended to the container (which defaults to <body>). You can prevent this behaviour 326 // by setting this to false, but if you do, you must attach the `element` yourself, which is a public property of the popup instance: 327 // 328 // var instance = cookieconsent.factory(options); 329 // document.body.appendChild(instance.element); 330 // 331 autoAttach: true, 332 333 // simple whitelist/blacklist for pages. specify page by: 334 // - using a string : '/index.html' (matches '/index.html' exactly) OR 335 // - using RegExp : /\/page_[\d]+\.html/ (matched '/page_1.html' and '/page_2.html' etc) 336 whitelistPage: [], 337 blacklistPage: [], 338 339 // If this is defined, then it is used as the inner html instead of layout. This allows for ultimate customisation. 340 // Be sure to use the classes `cc-btn` and `cc-allow`, `cc-deny` or `cc-dismiss`. They enable the app to register click 341 // handlers. You can use other pre-existing classes too. See `src/styles` folder. 342 overrideHTML: null, 343 }; 344 345 function CookiePopup() { 346 this.initialise.apply(this, arguments); 347 } 348 349 CookiePopup.prototype.initialise = function(options) { 350 if (this.options) { 351 this.destroy(); // already rendered 352 } 353 354 // set options back to default options 355 util.deepExtend(this.options = {}, defaultOptions); 356 357 // merge in user options 358 if (util.isPlainObject(options)) { 359 util.deepExtend(this.options, options); 360 } 361 362 // returns true if `onComplete` was called 363 if (checkCallbackHooks.call(this)) { 364 // user has already answered 365 this.options.enabled = false; 366 } 367 368 // apply blacklist / whitelist 369 if (arrayContainsMatches(this.options.blacklistPage, location.pathname)) { 370 this.options.enabled = false; 371 } 372 if (arrayContainsMatches(this.options.whitelistPage, location.pathname)) { 373 this.options.enabled = true; 374 } 375 376 // the full markup either contains the wrapper or it does not (for multiple instances) 377 var cookiePopup = this.options.window 378 .replace('{{classes}}', getPopupClasses.call(this).join(' ')) 379 .replace('{{children}}', getPopupInnerMarkup.call(this)); 380 381 // if user passes html, use it instead 382 var customHTML = this.options.overrideHTML; 383 if (typeof customHTML == 'string' && customHTML.length) {
384 cookiePopup = customHTML; 385 } 386 387 // if static, we need to grow the element from 0 height so it doesn't jump the page 388 // content. we wrap an element around it which will mask the hidden content 389 if (this.options.static) { 390 // `grower` is a wrapper div with a hidden overflow whose height is animated 391 var wrapper = appendMarkup.call(this, '<div class="cc-grower">' + cookiePopup + '</div>'); 392 393 wrapper.style.display = ''; // set it to visible (because appendMarkup hides it) 394 this.element = wrapper.firstChild; // get the `element` reference from the wrapper 395 this.element.style.display = 'none'; 396 util.addClass(this.element, 'cc-invisible'); 397 } else { 398 this.element = appendMarkup.call(this, cookiePopup); 399 } 400 401 applyAutoDismiss.call(this); 402 403 applyRevokeButton.call(this); 404 405 if (this.options.autoOpen) { 406 this.autoOpen(); 407 } 408 }; 409 410 CookiePopup.prototype.destroy = function() { 411 if (this.onButtonClick && this.element) { 412 this.element.removeEventListener('click', this.onButtonClick); 413 this.onButtonClick = null; 414 } 415 416 if (this.dismissTimeout) { 417 clearTimeout(this.dismissTimeout); 418 this.dismissTimeout = null; 419 } 420 421 if (this.onWindowScroll) { 422 window.removeEventListener('scroll', this.onWindowScroll); 423 this.onWindowScroll = null; 424 } 425 426 if (this.onMouseMove) { 427 window.removeEventListener('mousemove', this.onMouseMove); 428 this.onMouseMove = null; 429 } 430 431 if (this.element && this.element.parentNode) { 432 this.element.parentNode.removeChild(this.element); 433 } 434 this.element = null; 435 436 if (this.revokeBtn && this.revokeBtn.parentNode) { 437 this.revokeBtn.parentNode.removeChild(this.revokeBtn); 438 } 439 this.revokeBtn = null; 440 441 removeCustomStyle(this.options.palette); 442 this.options = null; 443 }; 444 445 CookiePopup.prototype.open = function(callback) { 446 if (!this.element) return; 447 448 if (!this.isOpen()) { 449 if (cc.hasTransition) { 450 this.fadeIn(); 451 } else { 452 this.element.style.display = ''; 453 } 454 455 if (this.options.revokable) { 456 this.toggleRevokeButton(); 457 } 458 this.options.onPopupOpen.call(this); 459 } 460 461 return this; 462 }; 463 464 CookiePopup.prototype.close = function(showRevoke) { 465 if (!this.element) return; 466 467 if (this.isOpen()) { 468 if (cc.hasTransition) { 469 this.fadeOut(); 470 } else { 471 this.element.style.display = 'none'; 472 } 473 474 if (showRevoke && this.options.revokable) { 475 this.toggleRevokeButton(true); 476 } 477 this.options.onPopupClose.call(this); 478 } 479 480 return this; 481 }; 482 483 CookiePopup.prototype.fadeIn = function() { 484 var el = this.element; 485 486 if (!cc.hasTransition || !el) 487 return; 488 489 // This should always be called AFTER fadeOut (which is governed by the 'transitionend' event). 490 // 'transitionend' isn't all that reliable, so, if we try and fadeIn before 'transitionend' has 491 // has a chance to run, then we run it ourselves 492 if (this.afterTransition) { 493 afterFadeOut.call(this, el) 494 } 495 496 if (util.hasClass(el, 'cc-invisible')) { 497 el.style.display = ''; 498 499 if (this.options.static) { 500 var height = this.element.clientHeight; 501 this.element.parentNode.style.maxHeight = height + 'px'; 502 } 503 504 var fadeInTimeout = 20; // (ms) DO NOT MAKE THIS VALUE SMALLER. See below 505 506 // Although most browsers can handle values less than 20ms, it should remain above this value. 507 // This is because we are waiting for a "browser redraw" before we remove the 'cc-invisible' class. 508 // If the class is remvoed before a redraw could happen, then the fadeIn effect WILL NOT work, and 509 // the popup will appear from nothing. Therefore we MUST allow enough time for the browser to do 510 // its thing. The actually difference between using 0 and 20 in a set timeout is neglegible anyway 511 this.openingTimeout = setTimeout(afterFadeIn.bind(this, el), fadeInTimeout); 512 } 513 }; 514
515 CookiePopup.prototype.fadeOut = function() { 516 var el = this.element; 517 518 if (!cc.hasTransition || !el) 519 return; 520 521 if (this.openingTimeout) { 522 clearTimeout(this.openingTimeout); 523 afterFadeIn.bind(this, el); 524 } 525 526 if (!util.hasClass(el, 'cc-invisible')) { 527 if (this.options.static) { 528 this.element.parentNode.style.maxHeight = ''; 529 } 530 531 this.afterTransition = afterFadeOut.bind(this, el); 532 el.addEventListener(cc.transitionEnd, this.afterTransition); 533 534 util.addClass(el, 'cc-invisible'); 535 } 536 }; 537 538 CookiePopup.prototype.isOpen = function() { 539 return this.element && this.element.style.display == '' && (cc.hasTransition ? !util.hasClass(this.element, 'cc-invisible') : true); 540 }; 541 542 CookiePopup.prototype.toggleRevokeButton = function(show) { 543 if (this.revokeBtn) this.revokeBtn.style.display = show ? '' : 'none'; 544 }; 545 546 CookiePopup.prototype.revokeChoice = function(preventOpen) { 547 this.options.enabled = true; 548 this.clearStatus(); 549 550 this.options.onRevokeChoice.call(this); 551 552 if (!preventOpen) { 553 this.autoOpen(); 554 } 555 }; 556 557 // returns true if the cookie has a valid value 558 CookiePopup.prototype.hasAnswered = function(options) { 559 return Object.keys(cc.status).indexOf(this.getStatus()) >= 0; 560 }; 561 562 // returns true if the cookie indicates that consent has been given 563 CookiePopup.prototype.hasConsented = function(options) { 564 var val = this.getStatus(); 565 return val == cc.status.allow || val == cc.status.dismiss; 566 }; 567 568 // opens the popup if no answer has been given 569 CookiePopup.prototype.autoOpen = function(options) { 570 !this.hasAnswered() && this.options.enabled && this.open(); 571 }; 572 573 CookiePopup.prototype.setStatus = function(status) { 574 var c = this.options.cookie; 575 var value = util.getCookie(c.name); 576 var chosenBefore = Object.keys(cc.status).indexOf(value) >= 0; 577 578 // if `status` is valid 579 if (Object.keys(cc.status).indexOf(status) >= 0) { 580 util.setCookie(c.name, status, c.expiryDays, c.domain, c.path); 581 582 this.options.onStatusChange.call(this, status, chosenBefore); 583 } else { 584 this.clearStatus(); 585 } 586 }; 587 588 CookiePopup.prototype.getStatus = function() { 589 return util.getCookie(this.options.cookie.name); 590 }; 591 592 CookiePopup.prototype.clearStatus = function() { 593 var c = this.options.cookie; 594 util.setCookie(c.name, '', -1, c.domain, c.path); 595 }; 596 597 // This needs to be called after 'fadeIn'. This is the code that actually causes the fadeIn to work 598 // There is a good reason why it's called in a timeout. Read 'fadeIn'; 599 function afterFadeIn(el) { 600 this.openingTimeout = null; 601 util.removeClass(el, 'cc-invisible'); 602 } 603 604 // This is called on 'transitionend' (only on the transition of the fadeOut). That's because after we've faded out, we need to 605 // set the display to 'none' (so there aren't annoying invisible popups all over the page). If for whenever reason this function 606 // is not called (lack of support), the open/close mechanism will still work. 607 function afterFadeOut(el) { 608 el.style.display = 'none'; // after close and before open, the display should be none 609 el.removeEventListener(cc.transitionEnd, this.afterTransition); 610 this.afterTransition = null; 611 } 612 613 // this function calls the `onComplete` hook and returns true (if needed) and returns false otherwise 614 function checkCallbackHooks() { 615 var complete = this.options.onInitialise.bind(this); 616 617 if (!window.navigator.cookieEnabled) { 618 complete(cc.status.deny); 619 return true; 620 } 621 622 if (window.CookiesOK || window.navigator.CookiesOK) { 623 complete(cc.status.allow); 624 return true; 625 } 626 627 var allowed = Object.keys(cc.status); 628 var answer = this.getStatus(); 629 var match = allowed.indexOf(answer) >= 0; 630 631 if (match) { 632 complete(answer); 633 } 634 return match; 635 } 636 637 function getPositionClasses() { 638 var positions = this.options.position.split('-'); // top, bottom, left, right 639 var classes = []; 640 641 // top, left, right, bottom 642 positions.forEach(function(cur) { 643 classes.push('cc-' + cur); 644 }); 645 646 return classes; 647 } 648 649 function getPopupClasses() { 650 var opts = this.options; 651 var positionStyle = (opts.position == 'top' || opts.position == 'bottom') ? 'banner' : 'floating'; 652 653 if (util.isMobile()) { 654 positionStyle = 'floating'; 655 } 656 657 var classes = [ 658 'cc-' + positionStyle, // floating or banner 659 'cc-type-' + opts.type, // add the compliance type 660 'cc-theme-' + opts.theme, // add the theme 661 ]; 662 663 if (opts.static) { 664 classes.push('cc-static'); 665 } 666 667 classes.push.apply(classes, getPositionClasses.call(this)); 668 669 // we only add extra styles if `palette` has been set to a valid value 670 var didAttach = attachCustomPalette.call(this, this.options.palette); 671 672 // if we override the palette, add the class that enables this 673 if (this.customStyleSelector) { 674 classes.push(this.customStyleSelector); 675 } 676 677 return classes; 678 } 679 680 function getPopupInnerMarkup() { 681 var interpolated = {}; 682 var opts = this.options; 683 684 // removes link if showLink is false 685 if (!opts.showLink) { 686 opts.elements.link = ''; 687 opts.elements.messagelink = opts.elements.message; 688 } 689
690 Object.keys(opts.elements).forEach(function(prop) { 691 interpolated[prop] = util.interpolateString(opts.elements[prop], function(name) { 692 var str = opts.content[name]; 693 return (name && typeof str == 'string' && str.length) ? str : ''; 694 }) 695 }); 696 697 // checks if the type is valid and defaults to info if it's not 698 var complianceType = opts.compliance[opts.type]; 699 if (!complianceType) { 700 complianceType = opts.compliance.info; 701 } 702 703 // build the compliance types from the already interpolated `elements` 704 interpolated.compliance = util.interpolateString(complianceType, function(name) { 705 return interpolated[name]; 706 }); 707 708 // checks if the layout is valid and defaults to basic if it's not 709 var layout = opts.layouts[opts.layout]; 710 if (!layout) { 711 layout = opts.layouts.basic; 712 } 713 714 return util.interpolateString(layout, function(match) { 715 return interpolated[match]; 716 }); 717 } 718 719 function appendMarkup(markup) { 720 var opts = this.options; 721 var div = document.createElement('div'); 722 var cont = (opts.container && opts.container.nodeType === 1) ? opts.container : document.body; 723 724 div.innerHTML = markup; 725 726 var el = div.children[0]; 727 if (((" " + el.className + " ").replace(/[\n\t]/g, " ").indexOf("cc-revoke") > -1) && this.options.alwaysShow==true) { 728 //do nothing 729 } else { 730 el.style.display = 'none'; 731 } 732 733 if (util.hasClass(el, 'cc-window') && cc.hasTransition) { 734 util.addClass(el, 'cc-invisible'); 735 } 736 737 // save ref to the function handle so we can unbind it later 738 this.onButtonClick = handleButtonClick.bind(this); 739 740 el.addEventListener('click', this.onButtonClick); 741 742 if (opts.autoAttach) { 743 if (!cont.firstChild) { 744 cont.appendChild(el); 745 } else { 746 cont.insertBefore(el, cont.firstChild) 747 } 748 } 749 750 return el; 751 } 752 753 function handleButtonClick(event) { 754 var targ = event.target; 755 if (util.hasClass(targ, 'cc-btn')) { 756 757 var matches = targ.className.match(new RegExp("\\bcc-(" + __allowedStatuses.join('|') + ")\\b")); 758 var match = (matches && matches[1]) || false; 759 if (match && match != 'settings') { 760 this.setStatus(match); 761 this.close(true); 762 } 763 } 764 765 if (util.hasClass(targ, 'cc-settings')) { 766 $('.cc-settings-view').fadeToggle(100); 767 } 768 769 if (util.hasClass(targ, 'cc-close')) { 770 this.setStatus(cc.status.dismiss); 771 this.close(true); 772 } 773 if (util.hasClass(targ, 'cc-revoke')) { 774 this.revokeChoice(); 775 } 776 } 777 778 // I might change this function to use inline styles. I originally chose a stylesheet because I could select many elements with a 779 // single rule (something that happened a lot), the apps has changed slightly now though, so inline styles might be more applicable. 780 function attachCustomPalette(palette) { 781 var hash = util.hash(JSON.stringify(palette)); 782 var selector = 'cc-color-override-' + hash; 783 var isValid = util.isPlainObject(palette); 784 785 this.customStyleSelector = isValid ? selector : null; 786 787 if (isValid) { 788 addCustomStyle(hash, palette, '.' + selector); 789 } 790 return isValid; 791 } 792 793 function addCustomStyle(hash, palette, prefix) { 794 795 // only add this if a style like it doesn't exist 796 if (cc.customStyles[hash]) { 797 // custom style already exists, so increment the reference count 798 ++cc.customStyles[hash].references; 799 return; 800 } 801 802 var colorStyles = {}; 803 var popup = palette.popup; 804 var button = palette.button; 805 var highlight = palette.highlight; 806 807 // needs background colour, text and link will be set to black/white if not specified 808 if (popup) { 809 // assumes popup.background is set 810 popup.text = popup.text ? popup.text : util.getContrast(popup.background); 811 popup.link = popup.link ? popup.link : popup.text; 812 colorStyles[prefix + '.cc-window'] = [ 813 'color: ' + popup.text, 814 'background-color: ' + popup.background 815 ]; 816 colorStyles[prefix + '.cc-revoke'] = [ 817 'color: ' + popup.text, 818 'background-color: ' + popup.background 819 ]; 820 colorStyles[prefix + ' .cc-link,' + prefix + ' .cc-link:active,' + prefix + ' .cc-link:visited']
820= [ 821 'color: ' + popup.link 822 ]; 823 824 /*Cookie Preferences*/ 825 colorStyles['.cc-settings-dialog'] = [ 826 'color: ' + popup.text, 827 'background-color: ' + popup.background 828 ]; 829 830 if (button) { 831 colorStyles['.cc-btn-save-settings'] = [ 832 'color: ' + button.text, 833 'border-color: ' + button.border, 834 'background-color: ' + button.background, 835 'border-radius: 3px' 836 ]; 837 838 // assumes button.background is set 839 button.text = button.text ? button.text : util.getContrast(button.background); 840 button.border = button.border ? button.border : 'transparent'; 841 colorStyles[prefix + ' .cc-btn'] = [ 842 'color: ' + button.text, 843 'border-color: ' + button.border, 844 'background-color: ' + button.background, 845 'outline: none', 846 'border-radius: 3px' 847 ]; 848 849 colorStyles[prefix + ' .cc-settings'] = [ 850 'color: ' + popup.text, 851 'border-color: ' + button.background, 852 'background-color: ' + popup.background 853 ]; 854 855 colorStyles['.cc-btn-close-settings'] = [ 856 'color: ' + popup.text, 857 'border-color: ' + button.background, 858 'background-color: ' + popup.background 859 ]; 860 861 colorStyles[prefix + ' .cc-settings:hover'] = [ 862 'color: ' + button.text, 863 'border-color: ' + button.background, 864 'background-color: ' + popup.background 865 ]; 866 colorStyles[prefix + ' .cc-settings:focus'] = [ 867 'color: ' + button.text, 868 'border-color: ' + button.background, 869 'background-color: ' + popup.background, 870 'text-decoration: none' 871 ]; 872 873 if(button.background != 'transparent') 874 colorStyles[prefix + ' .cc-btn:hover, ' + prefix + ' .cc-btn:focus'] = [ 875 'background-color: ' + getHoverColour(button.background) 876 ]; 877 878 if (highlight) { 879 //assumes highlight.background is set 880 highlight.text = highlight.text ? highlight.text : util.getContrast(highlight.background); 881 highlight.border = highlight.border ? highlight.border : 'transparent'; 882 colorStyles[prefix + ' .cc-highlight .cc-btn:first-child'] = [ 883 'color: ' + highlight.text, 884 'border-color: ' + highlight.border, 885 'background-color: ' + highlight.background 886 ]; 887 } else { 888 // sets highlight text color to popup text. background and border are transparent by default. 889 colorStyles[prefix + ' .cc-highlight .cc-btn:first-child'] = [ 890 'color: ' + popup.text 891 ]; 892 } 893 } 894 895 } 896 897 // this will be interpretted as CSS. the key is the selector, and each array element is a rule 898 var style = document.createElement('style'); 899 document.head.appendChild(style); 900 901 // custom style doesn't exist, so we create it 902 cc.customStyles[hash] = { 903 references: 1, 904 element: style.sheet 905 }; 906 907 var ruleIndex = -1; 908 for (var prop in colorStyles) { 909 if (colorStyles.hasOwnProperty(prop)) { 910 style.sheet.insertRule(prop + '{' + colorStyles[prop].join(';') + '}', ++ruleIndex); 911 } 912 } 913 } 914 915 function getHoverColour(hex) { 916 hex = util.normaliseHex(hex); 917 // for black buttons 918 if (hex == '000000') { 919 return '#222'; 920 } 921 return util.getLuminance(hex); 922 } 923 924 function removeCustomStyle(palette) { 925 if (util.isPlainObject(palette)) { 926 var hash = util.hash(JSON.stringify(palette)); 927 var customStyle = cc.customStyles[hash]; 928 if (customStyle && !--customStyle.references) { 929 var styleNode = customStyle.element.ownerNode; 930 if (styleNode && styleNode.parentNode) { 931 styleNode.parentNode.removeChild(styleNode); 932 } 933 cc.customStyles[hash] = null; 934 } 935 } 936 } 937 938 function arrayContainsMatches(array, search) { 939 for (var i = 0, l = array.length; i < l; ++i) { 940 var str = array[i]; 941 // if regex matches or string is equal, return true 942 if ((str instanceof RegExp && str.test(search)) || 943 (typeof str == 'string' && str.length && str === search)) { 944 return true; 945 } 946 } 947 return false; 948 } 949 950 function applyAutoDismiss() { 951 var setStatus = this.setStatus.bind(this); 952
953 var delay = this.options.dismissOnTimeout; 954 if (typeof delay == 'number' && delay >= 0) { 955 this.dismissTimeout = window.setTimeout(function() { 956 setStatus(cc.status.dismiss); 957 }, Math.floor(delay)); 958 } 959 960 var scrollRange = this.options.dismissOnScroll; 961 if (typeof scrollRange == 'number' && scrollRange >= 0) { 962 var onWindowScroll = function(evt) { 963 if (window.pageYOffset > Math.floor(scrollRange)) { 964 setStatus(cc.status.dismiss); 965 966 window.removeEventListener('scroll', onWindowScroll); 967 this.onWindowScroll = null; 968 } 969 }; 970 971 this.onWindowScroll = onWindowScroll; 972 window.addEventListener('scroll', onWindowScroll); 973 } 974 } 975 976 function applyRevokeButton() { 977 // revokable is true if advanced compliance is selected 978 if (this.options.type != 'info') this.options.revokable = true; 979 // animateRevokable false for mobile devices 980 if (util.isMobile()) this.options.animateRevokable = false; 981 982 if (this.options.revokable) { 983 var classes = getPositionClasses.call(this); 984 if (this.options.animateRevokable) { 985 classes.push('cc-animate'); 986 } 987 if (this.customStyleSelector) { 988 classes.push(this.customStyleSelector) 989 } 990 var revokeBtn = this.options.revokeBtn.replace('{{classes}}', classes.join(' ')); 991 this.revokeBtn = appendMarkup.call(this, revokeBtn); 992 993 var btn = this.revokeBtn; 994 if (this.options.animateRevokable) { 995 var wait = false; 996 var onMouseMove = util.throttle(function(evt) { 997 var active = false; 998 var minY = 20; 999 var maxY = (window.innerHeight - 20); 1000 1001 if (util.hasClass(btn, 'cc-top') && evt.clientY < minY) active = true; 1002 if (util.hasClass(btn, 'cc-bottom') && evt.clientY > maxY) active = true; 1003 1004 if (active) { 1005 if (!util.hasClass(btn, 'cc-active')) { 1006 util.addClass(btn, 'cc-active'); 1007 } 1008 } else { 1009 if (util.hasClass(btn, 'cc-active')) { 1010 util.removeClass(btn, 'cc-active'); 1011 } 1012 } 1013 }, 200); 1014 1015 this.onMouseMove = onMouseMove; 1016 window.addEventListener('mousemove', onMouseMove); 1017 } 1018 } 1019 } 1020 1021 return CookiePopup 1022 }()); 1023 1024 cc.Location = (function() { 1025 1026 // An object containing all the location services we have already set up. 1027 // When using a service, it could either return a data structure in plain text (like a JSON object) or an executable script 1028 // When the response needs to be executed by the browser, then `isScript` must be set to true, otherwise it won't work. 1029 1030 // When the service uses a script, the chances are that you'll have to use the script to make additional requests. In these 1031 // cases, the services `callback` property is called with a `done` function. When performing async operations, this must be called 1032 // with the data (or Error), and `cookieconsent.locate` will take care of the rest 1033 var defaultOptions = { 1034 1035 // The default timeout is 5 seconds. This is mainly needed to catch JSONP requests that error. 1036 // Otherwise there is no easy way to catch JSONP errors. That means that if a JSONP fails, the 1037 // app will take `timeout` milliseconds to react to a JSONP network error. 1038 timeout: 5000, 1039 1040 // the order that services will be attempted in 1041 services: [ 1042 'freegeoip', 1043 'ipinfo', 1044 'maxmind' 1045 1046 /* 1047 1048 // 'ipinfodb' requires some options, so we define it using an object 1049 // this object will be passed to the function that defines the service 1050 1051 { 1052 name: 'ipinfodb', 1053 interpolateUrl: { 1054 // obviously, this is a fake key 1055 api_key: 'vOgI3748dnIytIrsJcxS7qsDf6kbJkE9lN4yEDrXAqXcKUNvjjZPox3ekXqmMMld' 1056 }, 1057 }, 1058 1059 // as well as defining an object, you can define a function that returns an object 1060 1061 function () { 1062 return {name: 'ipinfodb'}; 1063 }, 1064 1065 */ 1066 ], 1067 1068 serviceDefinitions: { 1069 1070 freegeoip: function() { 1071 return { 1072 // This service responds with JSON, but they do not have
1072CORS set, so we must use JSONP and provide a callback 1073 // The `{callback}` is automatically rewritten by the tool 1074 url: '//freegeoip.net/json/?callback={callback}', 1075 isScript: true, // this is JSONP, therefore we must set it to run as a script 1076 callback: function(done, response) { 1077 try{ 1078 var json = JSON.parse(response); 1079 return json.error ? toError(json) : { 1080 code: json.country_code 1081 }; 1082 } catch (err) { 1083 return toError({error: 'Invalid response ('+err+')'}); 1084 } 1085 } 1086 } 1087 }, 1088 1089 ipinfo: function() { 1090 return { 1091 // This service responds with JSON, so we simply need to parse it and return the country code 1092 url: '//ipinfo.io', 1093 headers: ['Accept: application/json'], 1094 callback: function(done, response) { 1095 try{ 1096 var json = JSON.parse(response); 1097 return json.error ? toError(json) : { 1098 code: json.country 1099 }; 1100 } catch (err) { 1101 return toError({error: 'Invalid response ('+err+')'}); 1102 } 1103 } 1104 } 1105 }, 1106 1107 // This service requires an option to define `key`. Options are proived using objects or functions 1108 ipinfodb: function(options) { 1109 return { 1110 // This service responds with JSON, so we simply need to parse it and return the country code 1111 url: '//api.ipinfodb.com/v3/ip-country/?key={api_key}&format=json&callback={callback}', 1112 isScript: true, // this is JSONP, therefore we must set it to run as a script 1113 callback: function(done, response) { 1114 try{ 1115 var json = JSON.parse(response); 1116 return json.statusCode == 'ERROR' ? toError({error: json.statusMessage}) : { 1117 code: json.countryCode 1118 }; 1119 } catch (err) { 1120 return toError({error: 'Invalid response ('+err+')'}); 1121 } 1122 } 1123 } 1124 }, 1125 1126 maxmind: function() { 1127 return { 1128 // This service responds with a JavaScript file which defines additional functionality. Once loaded, we must 1129 // make an additional AJAX call. Therefore we provide a `done` callback that can be called asynchronously 1130 url: '//js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js', 1131 isScript: true, // this service responds with a JavaScript file, so it must be run as a script 1132 callback: function(done) { 1133 // if everything went okay then `geoip2` WILL be defined 1134 if (!window.geoip2) { 1135 done(new Error('Unexpected response format. The downloaded script should have exported `geoip2` to the global scope')); 1136 return; 1137 } 1138 1139 geoip2.country(function(location) { 1140 try { 1141 done({ 1142 code: location.country.iso_code 1143 }); 1144 } catch (err) { 1145 done(toError(err)); 1146 } 1147 }, function(err) { 1148 done(toError(err)); 1149 }); 1150 1151 // We can't return anything, because we need to wait for the second AJAX call to return. 1152 // Then we can 'complete' the service by passing data or an error to the `done` callback. 1153 } 1154 } 1155 }, 1156 }, 1157 }; 1158 1159 function Location(options) { 1160 // Set up options 1161 util.deepExtend(this.options = {}, defaultOptions); 1162 1163 if (util.isPlainObject(options)) { 1164 util.deepExtend(this.options, options); 1165 } 1166 1167 this.currentServiceIndex = -1; // the index (in options) of the service we're currently using 1168 } 1169 1170 Location.prototype.getNextService = function() { 1171 var service; 1172 1173 do { 1174 service = this.getServiceByIdx(++this.currentServiceIndex); 1175 } while (this.currentServiceIndex < this.options.services.length && !service); 1176 1177 return service; 1178 }; 1179 1180 Location.prototype.getServiceByIdx = function(idx) { 1181 // This can either be the name of a default locationService, or a function. 1182 var serviceOption = this.options.services[idx]; 1183 1184 // If it's a string, use one of the location services. 1185 if (typeof serviceOption === 'function') { 1186 var dynamicOpts = serviceOption(); 1187 if (dynamicOpts.name) { 1188 util.deepExtend(dynamicOpts, this.options.serviceDefinitions[dynamicOpts.name](dynamicOpts)); 1189 } 1190 return dynamicOpts; 1191 } 1192 1193 // If it's a string, use one of the location services. 1194 if (typeof serviceOption === 'string') { 1195 return this.options.serviceDefinitions[serviceOption](); 1196 } 1197 1198 // If it's an object, assume {name: 'ipinfo', ...otherOptions} 1199 // Allows user to pass in API keys etc. 1200 if (util.isPlainObject(serviceOption)) { 1201 return this.options.serviceDefinitions[serviceOption.name](serviceOption); 1202 } 1203 1204 return null; 1205 }; 1206 1207 // This runs the service located at index `currentServiceIndex`. 1208 // If the service fails, `runNextServiceOnError` will continue trying each service until all fail, or one completes successfully 1209 Location.prototype.locate = function(complete, error) { 1210 var service = this.getNextService(); 1211 1212 if (!service) { 1213 error(new Error('No services to run')); 1214 return; 1215 } 1216 1217 this.callbackComplete = complete; 1218 this.callbackError = error; 1219 1220 this.runService(service, this.runNextServiceOnError.bind(this)); 1221 }; 1222 1223 // Potentially adds a callback to a url for jsonp. 1224 Location.prototype.setupUrl = function(service) { 1225 var serviceOpts = this.getCurrentServiceOpts(); 1226 return service.url.replace(/\{(.*?)\}/g, function(_, param) { 1227 if (param === 'callback') { 1228 var tempName = 'callback' + Date.now(); 1229 window[tempName] = function(res) { 1230 service.__JSONP_DATA = JSON.stringify(res); 1231 } 1232 return tempName; 1233 } 1234 if (param in serviceOpts.interpolateUrl) { 1235 return serviceOpts.interpolateUrl[param]; 1236 } 1237 }); 1238 }; 1239 1240 // requires a `service` object that defines at least a `url` and `callback` 1241 Location.prototype.runService = function(service, complete) { 1242 var self = this; 1243 1244 // basic check to ensure it resembles a `service` 1245 if (!service || !service.url || !service.callback) { 1246 return; 1247 } 1248 1249 // we call either `getScript` or `makeAsyncRequest` depending on the type of resource 1250 var requestFunction = service.isScript ? getScript : makeAsyncRequest; 1251 1252 var url = this.setupUrl(service); 1253 1254 // both functions have similar signatures so we can pass the same arguments to both 1255 requestFunction(url, function(xhr) {
1256 // if `!xhr`, then `getScript` function was used, so there is no response text 1257 var responseText = xhr ? xhr.responseText : ''; 1258 1259 // if the resource is a script, then this function is called after the script has been run. 1260 // if the script is JSONP, then a time defined function `callback_{Date.now}` has already 1261 // been called (as the JSONP callback). This callback sets the __JSONP_DATA property 1262 if (service.__JSONP_DATA) { 1263 responseText = service.__JSONP_DATA; 1264 delete service.__JSONP_DATA; 1265 } 1266 1267 // call the service callback with the response text (so it can parse the response) 1268 self.runServiceCallback.call(self, complete, service, responseText); 1269 1270 }, this.options.timeout, service.data, service.headers); 1271 1272 // `service.data` and `service.headers` are optional (they only count if `!service.isScript` anyway) 1273 }; 1274 1275 // The service request has run (and possibly has a `responseText`) [no `responseText` if `isScript`] 1276 // We need to run its callback which determines if its successful or not 1277 // `complete` is called on success or failure 1278 Location.prototype.runServiceCallback = function(complete, service, responseText) { 1279 var self = this; 1280 // this is the function that is called if the service uses the async callback in its handler method 1281 var serviceResultHandler = function (asyncResult) { 1282 // if `result` is a valid value, then this function shouldn't really run 1283 // even if it is called by `service.callback` 1284 if (!result) { 1285 self.onServiceResult.call(self, complete, asyncResult) 1286 } 1287 }; 1288 1289 // the function `service.callback` will either extract a country code from `responseText` and return it (in `result`) 1290 // or (if it has to make additional requests) it will call a `done` callback with the country code when it is ready 1291 var result = service.callback(serviceResultHandler, responseText); 1292 1293 if (result) { 1294 this.onServiceResult.call(this, complete, result); 1295 } 1296 }; 1297 1298 // This is called with the `result` from `service.callback` regardless of how it provided that result (sync or async). 1299 // `result` will be whatever is returned from `service.callback`. A service callback should provide an object with data 1300 Location.prototype.onServiceResult = function(complete, result) { 1301 // convert result to nodejs style async callback 1302 if (result instanceof Error || (result && result.error)) { 1303 complete.call(this, result, null); 1304 } else { 1305 complete.call(this, null, result); 1306 } 1307 }; 1308 1309 // if `err` is set, the next service handler is called 1310 // if `err` is null, the `onComplete` handler is called with `data` 1311 Location.prototype.runNextServiceOnError = function(err, data) { 1312 if (err) { 1313 this.logError(err); 1314 1315 var nextService = this.getNextService(); 1316 1317 if (nextService) { 1318 this.runService(nextService, this.runNextServiceOnError.bind(this)); 1319 } else { 1320 this.completeService.call(this, this.callbackError, new Error('All services failed')); 1321 } 1322 } else { 1323 this.completeService.call(this, this.callbackComplete, data); 1324 } 1325 }; 1326 1327 Location.prototype.getCurrentServiceOpts = function() { 1328 var val = this.options.services[this.currentServiceIndex]; 1329 1330 if (typeof val == 'string') { 1331 return {name: val}; 1332 } 1333 1334 if (typeof val == 'function') { 1335 return val(); 1336 } 1337 1338 if (util.isPlainObject(val)) { 1339 return val; 1340 } 1341 1342 return {}; 1343 }; 1344 1345 // calls the `onComplete` callback after resetting the `currentServiceIndex` 1346 Location.prototype.completeService = function(fn, data) { 1347 this.currentServiceIndex = -1; 1348 1349 fn && fn(data); 1350 }; 1351 1352 Location.prototype.logError = function (err) { 1353 var idx = this.currentServiceIndex; 1354 var service = this.getServiceByIdx(idx); 1355 1356 console.error('The service[' + idx + '] (' + service.url + ') responded with the following error', err); 1357 }; 1358 1359 function getScript(url, callback, timeout) { 1360 var timeoutIdx, s = document.createElement('script'); 1361 1362 s.type = 'text/' + (url.type || 'javascript'); 1363 s.src = url.src || url; 1364 s.async = false; 1365 1366 s.onreadystatechange = s.onload = function() { 1367 // this code handles two scenarios, whether called by onload or onreadystatechange 1368 var state = s.readyState; 1369 1370 clearTimeout(timeoutIdx); 1371 1372 if (!callback.done && (!state || /loaded|complete/.test(state))) { 1373 callback.done = true; 1374 callback(); 1375 s.onreadystatechange = s.onload = null; 1376 } 1377 }; 1378 1379 document.body.appendChild(s); 1380 1381 // You can't catch JSONP Errors, because it's handled by the script tag 1382 // one way is to use a timeout 1383 timeoutIdx = setTimeout(function () { 1384 callback.done = true; 1385 callback(); 1386 s.onreadystatechange = s.onload = null; 1387 }, timeout); 1388 } 1389 1390 function makeAsyncRequest(url, onComplete, timeout, postData, requestHeaders) { 1391 var xhr = new(window.XMLHttpRequest || window.ActiveXObject)('MSXML2.XMLHTTP.3.0'); 1392 1393 xhr.open(postData ? 'POST' : 'GET', url, 1); 1394 1395 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 1396 xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); 1397 1398 if (Array.isArray(requestHeaders)) { 1399 for (var i = 0, l = requestHeaders.length; i < l; ++i) { 1400 var split = requestHeaders[i].split(':', 2) 1401 xhr.setRequestHeader(split[0].replace(/^\s+|\s+$/g, ''), split[1].replace(/^\s+|\s+$/g, '')); 1402 } 1403 } 1404 1405 if (typeof onComplete == 'function') { 1406 xhr.onreadystatechange = function() { 1407 if (xhr.readyState > 3) { 1408 onComplete(xhr); 1409 } 1410 }; 1411 } 1412 1413 xhr.send(postData); 1414 } 1415 1416 function toError(obj) { 1417 return new Error('Error [' + (obj.code || 'UNKNOWN') + ']: ' + obj.error); 1418 } 1419 1420 return Location; 1421 }()); 1422 1423 cc.Law = (function() { 1424 1425 var defaultOptions = { 1426 // Make this false if you want to disable all regional overrides for settings. 1427 // If true, options can differ by country, depending on their cookie law. 1428 // It does not affect hiding the popup for countries that do not have cookie law. 1429 regionalLaw: true, 1430 1431 // countries that enforce some version of a cookie law 1432 hasLaw: ['AT', 'BE', 'BG', 'HR', 'CZ', 'CY', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PL', 'PT', 'SK', '
1432SI', 'ES', 'SE', 'GB', 'UK'], 1433 1434 // countries that say that all cookie consent choices must be revokable (a user must be able too change their mind) 1435 revokable: ['HR', 'CY', 'DK', 'EE', 'FR', 'DE', 'LV', 'LT', 'NL', 'PT', 'ES'], 1436 1437 // countries that say that a person can only "consent" if the explicitly click on "I agree". 1438 // in these countries, consent cannot be implied via a timeout or by scrolling down the page 1439 explicitAction: ['HR', 'IT', 'ES'], 1440 }; 1441 1442 function Law(options) { 1443 this.initialise.apply(this, arguments); 1444 } 1445 1446 Law.prototype.initialise = function(options) { 1447 // set options back to default options 1448 util.deepExtend(this.options = {}, defaultOptions); 1449 1450 // merge in user options 1451 if (util.isPlainObject(options)) { 1452 util.deepExtend(this.options, options); 1453 } 1454 }; 1455 1456 Law.prototype.get = function(countryCode) { 1457 var opts = this.options; 1458 return { 1459 hasLaw: opts.hasLaw.indexOf(countryCode) >= 0, 1460 revokable: opts.revokable.indexOf(countryCode) >= 0, 1461 explicitAction: opts.explicitAction.indexOf(countryCode) >= 0, 1462 }; 1463 }; 1464 1465 Law.prototype.applyLaw = function(options, countryCode) { 1466 var country = this.get(countryCode); 1467 1468 if (!country.hasLaw) { 1469 // The country has no cookie law 1470 options.enabled = false; 1471 } 1472 1473 if (this.options.regionalLaw) { 1474 if (country.revokable) { 1475 // We must provide an option to revoke consent at a later time 1476 options.revokable = true; 1477 } 1478 1479 if (country.explicitAction) { 1480 // The user must explicitly click the consent button 1481 options.dismissOnScroll = false; 1482 options.dismissOnTimeout = false; 1483 } 1484 } 1485 return options; 1486 }; 1487 1488 return Law; 1489 }()); 1490 1491 // This function initialises the app by combining the use of the Popup, Locator and Law modules 1492 // You can string together these three modules yourself however you want, by writing a new function. 1493 cc.initialise = function(options, complete, error) { 1494 var law = new cc.Law(options.law); 1495 1496 if (!complete) complete = function() {}; 1497 if (!error) error = function() {}; 1498 1499 cc.getCountryCode(options, function(result) { 1500 // don't need the law or location options anymore 1501 delete options.law; 1502 delete options.location; 1503 1504 if (result.code) { 1505 options = law.applyLaw(options, result.code); 1506 } 1507 1508 complete(new cc.Popup(options)); 1509 }, function(err) { 1510 // don't need the law or location options anymore 1511 delete options.law; 1512 delete options.location; 1513 1514 error(err, new cc.Popup(options)); 1515 }); 1516 }; 1517 1518 // This function tries to find your current location. It either grabs it from a hardcoded option in 1519 // `options.law.countryCode`, or attempts to make a location service request. This function accepts 1520 // options (which can configure the `law` and `location` modules) and fires a callback with which 1521 // passes an object `{code: countryCode}` as the first argument (which can have undefined properties) 1522 cc.getCountryCode = function(options, complete, error) { 1523 if (options.law && options.law.countryCode) { 1524 complete({ 1525 code: options.law.countryCode 1526 }); 1527 return; 1528 } 1529 if (options.location) { 1530 var locator = new cc.Location(options.location); 1531 locator.locate(function(serviceResult) { 1532 complete(serviceResult || {}); 1533 }, error); 1534 return; 1535 } 1536 complete({}); 1537 }; 1538 1539 // export utils (no point in hiding them, so we may as well expose them) 1540 cc.utils = util; 1541 1542 // prevent this code from being run twice 1543 cc.hasInitialised = true; 1544 1545 window.cookieconsent = cc; 1546 1547}(window.cookieconsent || {}));
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.