├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── cookie-consent-content ├── de.json ├── en.json ├── oc.json └── sk.json ├── examples ├── cookie-banner-example.html ├── legal-notice.html └── privacy-policy.html ├── favicon.ico ├── index.html ├── package-lock.json ├── package.json ├── php └── Shaack │ └── BootstrapCookieConsentSettings.php └── src └── bootstrap-cookie-consent-settings.js /.gitattributes: -------------------------------------------------------------------------------- 1 | src/bootstrap-cookie-consent-settings.js linguist-vendored=false 2 | index.html linguist-documentation 3 | demo/legal-notice.html linguist-documentation 4 | demo/privacy-policy.html linguist-documentation -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stefan Haack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bootstrap-cookie-consent-settings 2 | 3 | A modal dialog (cookie banner) and framework to handle the EU law (as written by EuGH, 1.10.2019 – C-673/17) 4 | about cookies in a website. Needs Bootstrap 5. 5 | 6 | ## References 7 | 8 | - [Demo page](https://shaack.com/projekte/bootstrap-cookie-consent-settings) 9 | - [GitHub Repository](https://github.com/shaack/bootstrap-cookie-consent-settings) 10 | - [npm package](https://www.npmjs.com/package/bootstrap-cookie-consent-settings) 11 | 12 | ## Usage 13 | 14 | ### Construct 15 | 16 | Initialize the cookie consent framework with the constructor 17 | 18 | ```js 19 | var cookieSettings = new BootstrapCookieConsentSettings(props) 20 | ``` 21 | 22 | You should configure the framework with the `props` object, at least the properties `privacyPolicyUrl`, `legalNoticeUrl` 23 | and `contentURL`. The default configuration is 24 | 25 | ```js 26 | this.props = { 27 | privacyPolicyUrl: undefined, // the URL of your privacy policy page 28 | legalNoticeUrl: undefined, // the URL of you legal notice page (Impressum) 29 | contentURL: "/cookie-consent-content", // this folder must contain the language-files in the needed languages (`[lang].js`) 30 | buttonAgreeClass: "btn btn-primary", // the "Agree to all" buttons class 31 | buttonDontAgreeClass: "btn btn-link text-decoration-none", // the "I do not agree" buttons class 32 | buttonSaveClass: "btn btn-secondary", // the "Save selection" buttons class 33 | autoShowModal: true, // disable autoShowModal on the privacy policy and legal notice pages, to make these pages readable 34 | alsoUseLocalStorage: true, // if true, the settings are stored in localStorage, too 35 | postSelectionCallback: undefined, // callback function, called after the user has saved the settings 36 | lang: navigator.language, // the language, in which the modal is shown 37 | defaultLang: "en", // default language, if `lang` is not available as translation in `cookie-consent-content` 38 | categories: ["necessary", "statistics", "marketing", "personalization"], // the categories for selection, must be contained in the language files 39 | cookieName: "cookie-consent-settings", // the name of the cookie in which the configuration is stored as JSON 40 | cookieStorageDays: 365, // the duration the cookie configuration is stored on the client 41 | modalId: "bootstrapCookieConsentSettingsModal" // the id of the modal dialog element 42 | } 43 | ``` 44 | 45 | ### Show dialog 46 | 47 | On a new visit the dialog is shown automatically. 48 | 49 | To allow the user a reconfiguration you can show the Dialog again with 50 | 51 | ```js 52 | cookieSettings.showDialog() 53 | ``` 54 | 55 | ### Read the settings in JavaScript 56 | 57 | Read all cookie settings with 58 | 59 | ```js 60 | cookieSettings.getSettings() 61 | ``` 62 | 63 | It should return some JSON like 64 | 65 | ```json 66 | { 67 | "necessary": true, 68 | "statistics": true, 69 | "marketing": true, 70 | "personalization": true 71 | } 72 | ``` 73 | 74 | or `undefined`, before the user has chosen his cookie options. 75 | 76 | Read a specific cookie setting with 77 | 78 | ```js 79 | cookieSettings.getSettings('statistics') 80 | ``` 81 | 82 | for the `statistics` cookie settings. Also returns `undefined`, before the user has chosen his cookie options. 83 | 84 | ### Read the settings from the backend 85 | 86 | You can read the settings with all server languages, you just have to read the cookie `cookie-consent-settings`. 87 | The content of the cookie is encoded like a http query string. 88 | 89 | ``` 90 | necessary=true&statistics=false&marketing=true&personalization=true 91 | ``` 92 | 93 | #### PHP helper class 94 | 95 | I provide a PHP helper class that you can use to read and write the settings from a PHP backend. 96 | 97 | It is located in `php/Shaack/BootstrapCookieConsentSettings.php`. 98 | 99 | You can use it as described in the following example. 100 | 101 | ```PHP 102 | $cookieSettings = new \Shaack\BootstrapCookieConsentSettings(); 103 | // read all settings 104 | $allSettings = $cookieSettings->getSettings(); 105 | // read a specific setting 106 | $statisticsAllowed = $cookieSettings->getSetting("statistics"); 107 | // write a specific setting 108 | $cookieSettings->setSetting("statistics", false); 109 | ``` 110 | 111 | ### Internationalization 112 | 113 | You find the language files in `./cookie-consent-content`. You can add here language files or modify the existing. If 114 | you add language files please consider a pull request to also add them in this repository. Thanks. 115 | 116 | ## Disclaimer 117 | 118 | You can use this banner for your website free of charge under the [MIT-License](./LICENSE). 119 | 120 | The banner and framework was designed in cooperation with data protection officers and lawyers. However, we can not 121 | guarantee whether the banner is correct for your website and assume no liability for its use. 122 | 123 | --- 124 | 125 | Find more high quality modules from [shaack.com](https://shaack.com) 126 | on [our projects page](https://shaack.com/works). 127 | -------------------------------------------------------------------------------- /cookie-consent-content/de.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Privatsphäre Einstellungen", 3 | "body": "Wir nutzen Cookies und ähnliche Technologien, die zum Betrieb der Website erforderlich sind. Zusätzliche Cookies werden nur mit Ihrer Zustimmung verwendet. Es steht Ihnen frei, Ihre Zustimmung jederzeit zu geben, zu verweigern oder zurückzuziehen, indem Sie den Link \"Cookie-Einstellungen\" unten auf jeder Seite nutzen. Sie können der Verwendung von Cookies durch uns zustimmen, indem Sie auf \"Einverstanden\" klicken. Für weitere Informationen darüber, welche Daten gesammelt und wie sie an unsere Partner weitergegeben werden, lesen Sie bitte unsere --privacy-policy--.", 4 | "privacyPolicy": "Datenschutzerklärung", 5 | "legalNotice": "Impressum", 6 | "mySettings": "Meine Einstellungen", 7 | "buttonNotAgree": "Ich bin nicht einverstanden", 8 | "buttonAgree": "Einverstanden", 9 | "buttonSaveSelection": "Auswahl speichern", 10 | "buttonAgreeAll": "Allen zustimmen", 11 | "categories": { 12 | "necessary": { 13 | "title": "Notwendig", 14 | "description": ["Zum Betrieb der Website erforderlich"] 15 | }, 16 | "statistics": { 17 | "title": "Statistik", 18 | "description": ["Beobachtung der Website-Nutzung und Optimierung der Benutzererfahrung"] 19 | }, 20 | "marketing": { 21 | "title": "Marketing", 22 | "description": ["Bewertung von Marketingaktionen"] 23 | }, 24 | "personalization": { 25 | "title": "Personalisierung", 26 | "description": ["Speicherung Ihrer Präferenzen aus früheren Besuchen", 27 | "Sammeln von Benutzer-Feedback zur Verbesserung unserer Website", 28 | "Erfassung Ihrer Interessen, um maßgeschneiderte Inhalte und Angebote anzubieten"] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cookie-consent-content/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Privacy Settings", 3 | "body": "We use cookies and similar technologies that are necessary to operate the website. Additional cookies are only used with your consent. You are free to give, deny, or withdraw your consent at any time by using the \"cookie settings\" link at the bottom of each page. You can consent to our use of cookies by clicking \"Agree\". For more information about what information is collected and how it is shared with our partners, please read our --privacy-policy--.", 4 | "privacyPolicy": "Data Protection Statement", 5 | "legalNotice": "Legal Notice", 6 | "mySettings": "My Settings", 7 | "buttonNotAgree": "I do not agree", 8 | "buttonAgree": "Agree", 9 | "buttonSaveSelection": "Save selection", 10 | "buttonAgreeAll": "Agree to all", 11 | "categories": { 12 | "necessary": { 13 | "title": "Necessary", 14 | "description": ["Required to run the website"] 15 | }, 16 | "statistics": { 17 | "title": "Statistics", 18 | "description": ["Monitoring website usage and optimizing the user experience"] 19 | }, 20 | "marketing": { 21 | "title": "Marketing", 22 | "description": ["Evaluation of marketing actions"] 23 | }, 24 | "personalization": { 25 | "title": "Personalization", 26 | "description": ["Storage of your preferences from previous visits", 27 | "Collecting user feedback to improve our website", 28 | "Recording of your interests in order to provide customised content and offers"] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cookie-consent-content/oc.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Paramètres de confidencialitat", 3 | "body": "Utilizam de cookies e de tecnologias similaras que fan mestièr pel foncionament del site web. De cookies addicionals son sonque utilizats amb vòstre acòrd. Sètz liure de donar, refusar o tirar vòstre acòrd a tot moment en utilizant lo ligam «Paramètres de cookies» enbàs de cada pagina. Podètz consentir a nòstra utilizacion dels cookies en clicant «Acceptar». Per mai d'informacions tocant quina informacion es collectada e partejada amb nòstre socis, vejatz nòstra --privacy-policy--.", 4 | "privacyPolicy": "declaracion de proteccion de las donadas", 5 | "legalNotice": "Mencions legalas", 6 | "mySettings": "Mos paramètres", 7 | "buttonNotAgree": "Soi pas d'acòrd", 8 | "buttonAgree": "Acceptar", 9 | "buttonSaveSelection": "Enregistrar la seleccion", 10 | "buttonAgreeAll": "Tot acceptar", 11 | "categories": { 12 | "necessary": { 13 | "title": "Necessaris", 14 | "description": ["Requerits pel foncionament del site"] 15 | }, 16 | "statistics": { 17 | "title": "Estatisticas", 18 | "description": ["Per susvelhar l'utilizacion del site e melhorar l'experiéncia dels utilizaires"] 19 | }, 20 | "marketing": { 21 | "title": "Marketing", 22 | "description": ["Estudi de las accions de marketing"] 23 | }, 24 | "personalization": { 25 | "title": "Personalizacion", 26 | "description": ["Gardar las preferéncias de visitas precedentas", 27 | "Reculhir los comentaris dels utilizaire per melhorar nòstre site web", 28 | "Enregistrar vòstres interesses per vos fornir de contenguts e publicitats personalizats<"] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cookie-consent-content/sk.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Nastavenia ochrany osobných údajov", 3 | "body": "Používame cookies a podobné technológie, ktoré sú nevyhnutné na prevádzku webovej stránky. Ďalšie súbory cookie sa používajú iba s vaším súhlasom. Svoj súhlas môžete kedykoľvek udeliť, odmietnuť alebo odvolať pomocou odkazu \"nastavenia súborov cookie\" v spodnej časti každej stránky. Kliknutím na \"Súhlasím\" môžete súhlasiť s naším používaním súborov cookie. Ďalšie informácie o tom, aké informácie sa zhromažďujú a ako sa zdieľajú s našimi partnermi, nájdete v našich --privacy-policy--.", 4 | "privacyPolicy": "Vyhlásenie o ochrane údajov", 5 | "legalNotice": "Právne upozornenie", 6 | "mySettings": "Moje nastavenia", 7 | "buttonNotAgree": "Nesúhlasím", 8 | "buttonAgree": "Súhlasím", 9 | "buttonSaveSelection": "Uložiť výber", 10 | "buttonAgreeAll": "Súhlas so všetkým", 11 | "categories": { 12 | "necessary": { 13 | "title": "Nevyhnutné", 14 | "description": ["Vyžaduje sa na spustenie webovej stránky"] 15 | }, 16 | "statistics": { 17 | "title": "Štatistiky", 18 | "description": ["Monitorovanie používania webových stránok a optimalizácia používateľskej skúsenosti"] 19 | }, 20 | "marketing": { 21 | "title": "Marketing", 22 | "description": ["Hodnotenie marketingových akcií"] 23 | }, 24 | "personalization": { 25 | "title": "Personalizácia", 26 | "description": ["Ukladanie vašich preferencií z predchádzajúcich návštev", 27 | "Zhromažďovanie spätnej väzby od používateľov na zlepšenie našej webovej stránky", 28 | "Zaznamenávanie vašich záujmov s cieľom poskytnúť prispôsobený obsah a ponuky"] 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /examples/cookie-banner-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | bootstrap-cookie-banner demo page 10 | 11 | 12 |
13 |

bootstrap-cookie-consent-settings demo page

14 |

This is a modal dialog (cookie banner) and framework to handle the German and EU law (as written by EuGH, 15 | 1.10.2019 – C-673/17) about cookies in a website. This banner requires Bootstrap.

16 |

Usage

17 |

Construct

18 |

Initialize the cookie consent framework with the constructor

19 |

var cookieSettings = new BootstrapCookieConsent()

20 |

Show the Dialog

21 |

On a new visit the dialog is shown automatically. For reconfiguration 22 | show the Dialog again with cookieSettings.showDialog(). 23 |

24 |

25 | Cookie Settings 26 |

27 |

Read the settings

28 |

Read all cookie settings with cookieSettings.getSettings()

29 |
30 |
31 |

32 |         
33 |
34 |

Read a specific cookie setting with cookieSettings.getSettings('statistics')

35 |
36 |
37 |

38 |         
39 |
40 |

The code of this banner

41 |
42 |
43 |
var cookieSettings = new BootstrapCookieConsentSettings({
44 |     contentURL: "../cookie-consent-content",
45 |     privacyPolicyUrl: "privacy-policy.html",
46 |     legalNoticeUrl: "legal-notice.html",
47 |     postSelectionCallback: function () {
48 |         location.reload() // reload after selection
49 |     }
50 | })
51 |
52 |
53 |

More documentation

54 |

Find more documentation and 55 | the sourcecode on GitHub

56 |
57 | 58 | 61 | 62 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/legal-notice.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bootstrap-cookie-banner demo page 9 | 10 | 11 |
12 |

bootstrap-cookie-consent-settings

13 |

This is the legal notice dummy page.

14 |

You have to set the legal notice URL in your content files.

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/privacy-policy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bootstrap-cookie-banner demo page 9 | 10 | 11 |
12 |

bootstrap-cookie-consent-settings

13 |

This is the privacy policy dummy page.

14 |

You have to set the privacy policy URL in your content files.

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaack/bootstrap-cookie-consent-settings/1de8f7518a4728c794a1cc949c5b5494b08fc894/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | bootstrap-cookie-consent-settings demo page 9 | 10 | 11 |
12 |

bootstrap-cookie-consent-settings demo page

13 |

This is a modal dialog (cookie banner) and framework to handle the German and EU law (as written by EuGH, 14 | 1.10.2019 – C-673/17) about cookies in a website. This banner requires Bootstrap.

15 |

We also have another, smaller cookie banner, without dependencies, which 16 | does not offer the user an advanced configuration. You can find it in GitHub as 17 | cookie-consent-js.

18 |

Example

19 | 22 |

Further Information

23 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap-cookie-consent-settings", 3 | "version": "4.1.6", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bootstrap-cookie-consent-settings", 9 | "version": "4.1.6", 10 | "license": "MIT" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap-cookie-consent-settings", 3 | "version": "4.1.6", 4 | "description": "Settings dialog in Bootstrap 5 and framework to handle the EU law (may 2020) about cookies in a website", 5 | "browser": "./src/bootstrap-cookie-banner.js", 6 | "scripts": { 7 | "test": "echo \"No test specified\"" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/shaack/bootstrap-cookie-banner.git" 12 | }, 13 | "keywords": [ 14 | "bootstrap", 15 | "cookie", 16 | "consent", 17 | "eu", 18 | "gdpr", 19 | "dsgvo" 20 | ], 21 | "author": "Stefan Haack (shaack.com)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/shaack/bootstrap-cookie-banner/issues" 25 | }, 26 | "homepage": "https://shaack.com", 27 | "main": "src/bootstrap-cookie-consent-settings.js" 28 | } 29 | -------------------------------------------------------------------------------- /php/Shaack/BootstrapCookieConsentSettings.php: -------------------------------------------------------------------------------- 1 | cookieName = $cookieName; 16 | $this->cookieStorageDays = $cookieStorageDays; 17 | } 18 | 19 | /** 20 | * Read the whole consent cookie into an array. 21 | * @return array 22 | */ 23 | public function getSettings() : array { 24 | parse_str(@$_COOKIE[$this->cookieName], $array); 25 | return array_map(function($value) { 26 | return $value === "true"; 27 | }, $array); 28 | } 29 | 30 | /** 31 | * Write a value to the consent cookie. 32 | * @param string $optionName 33 | * @return bool 34 | */ 35 | public function getSetting(string $optionName) : bool { 36 | return !!$this->getSettings()[$optionName]; 37 | } 38 | 39 | /** 40 | * Write an array of values to the consent cookie. 41 | * @param array $settings 42 | * @return void 43 | */ 44 | public function setSettings(array $settings) : void { 45 | $settings["necessary"] = true; 46 | $encoded = http_build_query(array_map(function($value) { 47 | return $value ? "true" : "false"; 48 | },$settings), "", "&"); 49 | setrawcookie($this->cookieName, $encoded, time() + (86400 * $this->cookieStorageDays), "/"); 50 | } 51 | 52 | /** 53 | * Read a value from the consent cookie. 54 | * @param string $optionName 55 | * @param bool $value 56 | * @return void 57 | */ 58 | public function setSetting(string $optionName, bool $value) : void { 59 | $settings = $this->getSettings(); 60 | $settings[$optionName] = $value; 61 | $this->setSettings($settings); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/bootstrap-cookie-consent-settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Author and copyright: Stefan Haack (https://shaack.com) 3 | * Repository: https://github.com/shaack/bootstrap-cookie-consent-settings 4 | * License: MIT, see file 'LICENSE' 5 | */ 6 | 7 | "use strict" 8 | 9 | function BootstrapCookieConsentSettings(props) { 10 | 11 | const self = this 12 | let detailedSettingsShown = false 13 | this.props = { 14 | privacyPolicyUrl: undefined, // the URL of your privacy policy page 15 | legalNoticeUrl: undefined, // the URL of you legal notice page (Impressum) 16 | contentURL: "/cookie-consent-content", // this folder must contain the language-files in the needed languages (`[lang].js`) 17 | buttonAgreeClass: "btn btn-primary", // the "Agree to all" buttons class 18 | buttonDontAgreeClass: "btn btn-link text-decoration-none", // the "I do not agree" buttons class 19 | buttonSaveClass: "btn btn-secondary", // the "Save selection" buttons class 20 | autoShowModal: true, // disable autoShowModal on the privacy policy and legal notice pages, to make these pages readable 21 | alsoUseLocalStorage: true, // if true, the settings are stored in localStorage, too 22 | postSelectionCallback: undefined, // callback function, called after the user has saved the settings 23 | lang: navigator.language, // the language, in which the modal is shown 24 | defaultLang: "en", // default language, if `lang` is not available as translation in `cookie-consent-content` 25 | categories: ["necessary", "statistics", "marketing", "personalization"], // the categories for selection, must be contained in the language files 26 | cookieName: "cookie-consent-settings", // the name of the cookie in which the configuration is stored as JSON 27 | cookieStorageDays: 365, // the duration the cookie configuration is stored on the client 28 | modalId: "bootstrapCookieConsentSettingsModal" // the id of the modal dialog element 29 | } 30 | if (!props.privacyPolicyUrl) { 31 | console.error("please set `privacyPolicyUrl` in the props of BootstrapCookieConsentSettings") 32 | } 33 | if (!props.legalNoticeUrl) { 34 | console.error("please set `legalNoticeUrl` in the props of BootstrapCookieConsentSettings") 35 | } 36 | for (const property in props) { 37 | // noinspection JSUnfilteredForInLoop 38 | this.props[property] = props[property] 39 | } 40 | this.lang = this.props.lang 41 | if (this.lang.indexOf("-") !== -1) { 42 | this.lang = this.lang.split("-")[0] 43 | } 44 | 45 | // read the cookie, and if its content does not fit the categories, remove it 46 | const cookieContent = getCookie(this.props.cookieName) 47 | if (cookieContent) { 48 | try { 49 | for (const category of this.props.categories) { 50 | if (cookieContent[category] === undefined) { 51 | console.log("cookie settings changed, removing settings cookie") 52 | removeCookie(this.props.cookieName) 53 | break 54 | } 55 | } 56 | } catch (e) { 57 | // problems with the cookie, remove it 58 | console.warn("cookie settings changed, removing settings cookie", e) 59 | removeCookie(this.props.cookieName) 60 | } 61 | } 62 | 63 | /** 64 | * Read the language file and render the modal 65 | */ 66 | fetchContent(self.lang, (result) => { 67 | self.content = JSON.parse(result) 68 | renderModal() 69 | }) 70 | 71 | function renderModal() { 72 | const _t = self.content 73 | const linkPrivacyPolicy = '' + _t.privacyPolicy + '' 74 | const linkLegalNotice = '' + _t.legalNotice + '' 75 | if (self.content[self.lang] === undefined) { 76 | self.lang = self.props.defaultLang 77 | } 78 | self.content.body = self.content.body.replace(/--privacy-policy--/, linkPrivacyPolicy) 79 | let optionsHtml = "" 80 | for (const category of self.props.categories) { 81 | const categoryContent = self.content.categories[category] 82 | if (!categoryContent) { 83 | console.error("no content for category", category, "found in language file", self.lang) 84 | } 85 | let descriptionList = "" 86 | for (const descriptionElement of categoryContent.description) { 87 | descriptionList += `
  • ${descriptionElement}
  • ` 88 | } 89 | optionsHtml += `
    90 |
    91 | 92 | 93 |
    94 | 97 |
    ` 98 | } 99 | self.modalContent = ` 100 | ` 132 | if (!getCookie(self.props.cookieName) && self.props.autoShowModal) { 133 | showDialog() 134 | } 135 | } 136 | 137 | function showDialog() { 138 | documentReady(function () { 139 | self.modalElement = document.getElementById(self.props.modalId) 140 | if (!self.modalElement) { 141 | self.modalElement = document.createElement("div") 142 | self.modalElement.id = self.props.modalId 143 | self.modalElement.setAttribute("class", "modal fade") 144 | self.modalElement.setAttribute("tabindex", "-1") 145 | self.modalElement.setAttribute("role", "dialog") 146 | self.modalElement.setAttribute("aria-labelledby", self.props.modalId) 147 | self.modalElement.innerHTML = self.modalContent 148 | document.body.append(self.modalElement) 149 | if (self.props.postSelectionCallback) { 150 | self.modalElement.addEventListener("hidden.bs.modal", function () { 151 | self.props.postSelectionCallback() 152 | }) 153 | } 154 | self.modal = new bootstrap.Modal(self.modalElement, { 155 | backdrop: "static", 156 | keyboard: false 157 | }) 158 | self.modal.show() 159 | self.buttonDoNotAgree = self.modalElement.querySelector("#bccs-buttonDoNotAgree") 160 | self.buttonAgree = self.modalElement.querySelector("#bccs-buttonAgree") 161 | self.buttonSave = self.modalElement.querySelector("#bccs-buttonSave") 162 | self.buttonAgreeAll = self.modalElement.querySelector("#bccs-buttonAgreeAll") 163 | updateButtons() 164 | updateOptionsFromCookie() 165 | self.modalElement.querySelector("#bccs-options").addEventListener("hide.bs.collapse", function () { 166 | detailedSettingsShown = false 167 | updateButtons() 168 | }) 169 | self.modalElement.querySelector("#bccs-options").addEventListener("show.bs.collapse", function () { 170 | detailedSettingsShown = true 171 | updateButtons() 172 | }) 173 | self.buttonDoNotAgree.addEventListener("click", function () { 174 | doNotAgree() 175 | }) 176 | self.buttonAgree.addEventListener("click", function () { 177 | agreeAll() 178 | }) 179 | self.buttonSave.addEventListener("click", function () { 180 | saveSettings() 181 | }) 182 | self.buttonAgreeAll.addEventListener("click", function () { 183 | agreeAll() 184 | }) 185 | } else { 186 | self.modal.show() 187 | } 188 | }.bind(this)) 189 | } 190 | 191 | function updateOptionsFromCookie() { 192 | const settings = self.getSettings() 193 | if (settings) { 194 | for (let setting in settings) { 195 | const checkboxElement = self.modalElement.querySelector("#bccs-checkbox-" + setting) 196 | try { 197 | checkboxElement.checked = settings[setting] === "true" 198 | } catch (e) { 199 | console.warn("error updating settings", setting, "from cookie", e) 200 | } 201 | } 202 | } 203 | const checkboxNecessary = self.modalElement.querySelector("#bccs-checkbox-necessary") 204 | checkboxNecessary.checked = true 205 | checkboxNecessary.disabled = true 206 | } 207 | 208 | function updateButtons() { 209 | if (detailedSettingsShown) { 210 | self.buttonDoNotAgree.style.display = "none" 211 | self.buttonAgree.style.display = "none" 212 | self.buttonSave.style.removeProperty("display") 213 | self.buttonAgreeAll.style.removeProperty("display") 214 | } else { 215 | self.buttonDoNotAgree.style.removeProperty("display") 216 | self.buttonAgree.style.removeProperty("display") 217 | self.buttonSave.style.display = "none" 218 | self.buttonAgreeAll.style.display = "none" 219 | } 220 | } 221 | 222 | function gatherOptions(setAllTo = undefined) { 223 | const options = {} 224 | for (const category of self.props.categories) { 225 | if (setAllTo === undefined) { 226 | const checkbox = self.modalElement.querySelector("#bccs-checkbox-" + category) 227 | if (!checkbox) { 228 | console.error("checkbox not found for category", category) 229 | } 230 | options[category] = checkbox.checked 231 | } else { 232 | options[category] = setAllTo 233 | } 234 | } 235 | options["necessary"] = true // necessary is necessary 236 | return options 237 | } 238 | 239 | function agreeAll() { 240 | setCookie(self.props.cookieName, gatherOptions(true), self.props.cookieStorageDays) 241 | self.modal.hide() 242 | } 243 | 244 | function doNotAgree() { 245 | setCookie(self.props.cookieName, gatherOptions(false), self.props.cookieStorageDays) 246 | self.modal.hide() 247 | } 248 | 249 | function saveSettings() { 250 | setCookie(self.props.cookieName, gatherOptions(), self.props.cookieStorageDays) 251 | self.modal.hide() 252 | } 253 | 254 | function fetchContent(lang, callback) { 255 | const request = new XMLHttpRequest() 256 | request.overrideMimeType("application/json") 257 | const url = self.props.contentURL + '/' + lang + '.json' 258 | request.open('GET', url, true) 259 | request.onreadystatechange = function () { 260 | if (request.readyState === 4 && request.status === 200) { 261 | if (request.status === 200) { 262 | callback(request.responseText) 263 | } else { 264 | console.error(url, request.status) 265 | } 266 | } 267 | } 268 | request.onloadend = function () { 269 | if (request.status === 404 && lang !== self.props.defaultLang) { 270 | console.warn("language " + lang + " not found trying defaultLang " + self.props.defaultLang) 271 | fetchContent(self.props.defaultLang, callback) 272 | } 273 | } 274 | request.send(null) 275 | } 276 | 277 | function setCookie(name, object, days) { 278 | let expires = "" 279 | if (days) { 280 | const date = new Date() 281 | date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)) 282 | expires = "; expires=" + date.toUTCString() 283 | } 284 | const value = new URLSearchParams(object).toString() 285 | document.cookie = name + "=" + (value || "") + expires + "; Path=/; SameSite=Strict;" 286 | // store value also in localStorage 287 | localStorage.setItem(name, value) 288 | } 289 | 290 | function getCookie(name) { 291 | const nameEQ = name + "=" 292 | const ca = document.cookie.split(';') 293 | for (let i = 0; i < ca.length; i++) { 294 | let c = ca[i] 295 | while (c.charAt(0) === ' ') { 296 | c = c.substring(1, c.length) 297 | } 298 | if (c.indexOf(nameEQ) === 0) { 299 | const urlSearchParams = new URLSearchParams(c.substring(nameEQ.length, c.length)) 300 | const result = {} 301 | for (const [key, value] of urlSearchParams) { 302 | result[key] = value 303 | } 304 | return result 305 | } 306 | } 307 | // if cookie not found, try localStorage 308 | const value = localStorage.getItem(name) 309 | if (value) { 310 | const urlSearchParams = new URLSearchParams(value) 311 | const result = {} 312 | for (const [key, value] of urlSearchParams) { 313 | result[key] = value 314 | } 315 | setCookie(name, result, self.props.cookieStorageDays) 316 | return result 317 | } 318 | return null 319 | } 320 | 321 | function removeCookie(name) { 322 | document.cookie = name + '=; Path=/; SameSite=Strict; Expires=Thu, 01 Jan 1970 00:00:01 GMT;' 323 | } 324 | 325 | function documentReady(callback) { 326 | if (document.readyState !== 'loading') { 327 | callback() 328 | } else { 329 | document.addEventListener('DOMContentLoaded', callback) 330 | } 331 | } 332 | 333 | // API 334 | this.showDialog = function () { 335 | showDialog() 336 | } 337 | this.getSettings = function (optionName) { 338 | const cookieContent = getCookie(self.props.cookieName) 339 | if (cookieContent) { 340 | if (optionName === undefined) { 341 | return cookieContent 342 | } else { 343 | if (cookieContent) { 344 | return cookieContent[optionName] 345 | } else { 346 | return false 347 | } 348 | } 349 | } else { 350 | return undefined 351 | } 352 | } 353 | this.setSetting = function (name, value) { 354 | let settings = self.getSettings() || {} 355 | for (const category of this.props.categories) { 356 | if(settings[category] === undefined) { 357 | settings[category] = true 358 | } 359 | } 360 | settings[name] = value 361 | setCookie(self.props.cookieName, settings, self.props.cookieStorageDays) 362 | } 363 | } 364 | --------------------------------------------------------------------------------