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