├── vite.config.js
├── dist
├── .vite
│ └── manifest.json
└── assets
│ ├── cookie-banner-DGJAdqxF.css
│ └── cookie-banner-DSt9AXzp.js
├── translations
├── en.json
└── de.json
├── composer.json
├── snippets
├── cookie-modal-option.php
└── cookie-modal.php
├── LICENSE.md
├── index.php
├── CookieBanner.php
├── src
├── cookie-banner.scss
└── cookie-banner.js
└── README.md
/vite.config.js:
--------------------------------------------------------------------------------
1 | export default () => ({
2 | build: {
3 | manifest: true,
4 | emptyOutDir: true,
5 | rollupOptions: {input: 'src/cookie-banner.js'},
6 | },
7 | });
8 |
--------------------------------------------------------------------------------
/dist/.vite/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "src/cookie-banner.js": {
3 | "file": "assets/cookie-banner-DSt9AXzp.js",
4 | "name": "cookie-banner",
5 | "src": "src/cookie-banner.js",
6 | "isEntry": true,
7 | "css": [
8 | "assets/cookie-banner-DGJAdqxF.css"
9 | ]
10 | }
11 | }
--------------------------------------------------------------------------------
/translations/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "michnhokn.cookie-banner.title": "Cookie settings",
3 | "michnhokn.cookie-banner.text": "We use cookies to provide you with the best possible experience. They also allow us to analyze user behavior in order to constantly improve the website for you. (link: privacy-policy text: Privacy Policy)",
4 | "michnhokn.cookie-banner.essentialText": "Essential",
5 | "michnhokn.cookie-banner.denyAll": "Reject All",
6 | "michnhokn.cookie-banner.acceptAll": "Accept All",
7 | "michnhokn.cookie-banner.save": "Save"
8 | }
9 |
--------------------------------------------------------------------------------
/translations/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "michnhokn.cookie-banner.title": "Cookie Einstellungen",
3 | "michnhokn.cookie-banner.text": "Wir nutzen Cookies um Dir die bestmögliche Erfahrung zu bieten. Außerdem können wir damit das Verhalten der Benutzer analysieren um die Webseite stetig für Dich zu verbessern. (link: datenschutz text: Datenschutz)",
4 | "michnhokn.cookie-banner.essentialText": "Essenziell",
5 | "michnhokn.cookie-banner.denyAll": "Alle ablehnen",
6 | "michnhokn.cookie-banner.acceptAll": "Alle annehmen",
7 | "michnhokn.cookie-banner.save": "Speichern"
8 | }
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "michnhokn/kirby-cookie-banner",
3 | "description": "Add a cookie modal to your Kirby3 website",
4 | "type": "kirby-plugin",
5 | "version": "1.2.0",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Michael Engel",
10 | "email": "support@michaelengel.dev"
11 | }
12 | ],
13 | "keywords": [
14 | "kirby",
15 | "kirby-plugin",
16 | "cookies",
17 | "cookie-modal",
18 | "cookie-banner"
19 | ],
20 | "config": {
21 | "optimize-autoloader": true
22 | },
23 | "require": {
24 | "getkirby/composer-installer": "^1.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/snippets/cookie-modal-option.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Michael Engel
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 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | __DIR__ . '/CookieBanner.php']);
10 |
11 | try {
12 | $germanTranslations = Json::read(__DIR__ . "/translations/de.json");
13 | $englishTranslations = Json::read(__DIR__ . "/translations/en.json");
14 |
15 | // support for use without option "languages => true"
16 | $cookieBannerTranslations = [];
17 | foreach ($germanTranslations as $key => $translation) {
18 | $cookieBannerTranslations[Str::replace($key, 'michnhokn.cookie-banner.', '')] = $translation;
19 | }
20 | } catch (Exception $exception) {
21 | }
22 |
23 | App::plugin('michnhokn/cookie-banner', [
24 | 'snippets' => [
25 | 'cookie-modal' => __DIR__ . '/snippets/cookie-modal.php',
26 | 'cookie-modal-option' => __DIR__ . '/snippets/cookie-modal-option.php',
27 | ],
28 | 'translations' => [
29 | 'de' => $germanTranslations ?? [],
30 | 'en' => $englishTranslations ?? []
31 | ],
32 | 'options' => [
33 | 'features' => [],
34 | 'translations' => $cookieBannerTranslations ?? []
35 | ],
36 | 'assets' => [
37 | 'cookie-banner.js' => CookieBanner::realAsset('js'),
38 | 'cookie-banner.css' => CookieBanner::realAsset('css'),
39 | ]
40 | ]);
41 |
--------------------------------------------------------------------------------
/snippets/cookie-modal.php:
--------------------------------------------------------------------------------
1 |
4 | = css(CookieBanner::asset('cookie-banner.css')) ?>
5 | = js(CookieBanner::asset('cookie-banner.js'), ['defer' => true]) ?>
6 |
7 |
9 |
10 |
= CookieBanner::translate('title') ?>
11 |
= kti(CookieBanner::translate('text')) ?>
12 |
13 | true,
15 | 'checked' => true,
16 | 'key' => 'essential',
17 | 'title' => CookieBanner::translate('essentialText')
18 | ]) ?>
19 | $title): ?>
20 | false,
22 | 'key' => $key,
23 | 'title' => t($title, $title)
24 | ]) ?>
25 |
26 |
27 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/dist/assets/cookie-banner-DGJAdqxF.css:
--------------------------------------------------------------------------------
1 | .cookie-modal{position:fixed;z-index:1000;width:100%;height:100%;background-color:#0000004d;left:0;top:0;pointer-events:none}.cookie-modal--hidden{display:none}.cookie-modal__content{max-width:clamp(400px,70dvw,800px);padding:16px;box-shadow:0 10px 30px #0003;background-color:#fff;margin:16vh auto 0;pointer-events:auto;border-radius:8px}@media (min-width: 400px){.cookie-modal__content{margin:22vh auto 0;padding:32px}}.cookie-modal__title{font-size:1.4rem;font-weight:700;margin-bottom:10px;margin-top:0}.cookie-modal__text{margin-bottom:20px;line-height:1.4}.cookie-modal__text a{text-decoration:underline}.cookie-modal__options{margin-bottom:64px;display:grid;flex-direction:row;gap:16px;grid-template-columns:1fr}@media (min-width: 400px){.cookie-modal__options{grid-template-columns:repeat(2,1fr)}}@media (min-width: 900px){.cookie-modal__options{grid-template-columns:repeat(3,1fr)}}.cookie-modal__option{width:100%;display:inline-flex;flex-direction:row;align-items:center;justify-content:flex-start;position:relative;cursor:pointer}.cookie-modal__option.disabled{opacity:.7}.cookie-modal__checkbox{appearance:none;-webkit-appearance:none;-moz-appearance:none;display:block;width:20px;height:20px;border:2px solid #000000;margin:0 6px 0 0;flex-shrink:0;border-radius:4px}.cookie-modal__checkbox:checked{background-color:#000}.cookie-modal__checkbox:checked:focus-visible{outline:none;background-color:#81a2be}.cookie-modal__checkbox:focus-visible{outline:none;border-color:#81a2be}.cookie-modal__check{position:absolute;left:0;z-index:1;display:flex;justify-content:center;align-items:center;width:20px;height:20px;flex-shrink:0}.cookie-modal__check svg{stroke:#fff}.cookie-modal__label{line-height:22px}.cookie-modal__buttons{display:flex;flex-direction:row;align-items:flex-start;justify-content:flex-start}.cookie-modal__button{display:block;margin-right:8px;padding:6px 20px;white-space:nowrap;border:2px solid #000000;text-decoration:none;color:#000;border-radius:4px}@media (min-width: 400px){.cookie-modal__button{margin-right:10px;padding:10px 24px}}.cookie-modal__button.primary{background-color:#000;color:#fff}.cookie-modal__button.primary:focus-visible,.cookie-modal__button.primary:hover{background-color:#81a2be}.cookie-modal__button:focus-visible,.cookie-modal__button:hover{outline:none;border-color:#81a2be}.cookie-modal__button.hide{display:none}
2 |
--------------------------------------------------------------------------------
/CookieBanner.php:
--------------------------------------------------------------------------------
1 | response()->usesCookie(self::COOKIE_NAME);
18 | $cookie = $_COOKIE[self::COOKIE_NAME] ?? null;
19 | if (!is_string($cookie) or empty($cookie)) {
20 | return null;
21 | }
22 | return $cookie;
23 | }
24 |
25 | public static function isFeatureAllowed(string $featureName): bool
26 | {
27 | return in_array($featureName, self::allowedFeatures());
28 | }
29 |
30 | public static function allowedFeatures(): array
31 | {
32 | if (($cookie = self::getCookie()) === null) {
33 | return [];
34 | }
35 |
36 | return array_values(
37 | array_unique(
38 | array_filter(
39 | array_map('trim', explode(',', $cookie))
40 | )
41 | )
42 | );
43 | }
44 |
45 | public static function availableFeatures(array $additionalFeatures = []): array
46 | {
47 | $configFeatures = option('michnhokn.cookie-banner.features', []);
48 | return A::merge($configFeatures, $additionalFeatures);
49 | }
50 |
51 | public static function clear(): void
52 | {
53 | Cookie::remove(self::COOKIE_NAME);
54 | }
55 |
56 | public static function translate(string $key): string
57 | {
58 | if (option('languages') === true) {
59 | return t("michnhokn.cookie-banner.$key");
60 | }
61 | return option("michnhokn.cookie-banner.translations.$key");
62 | }
63 |
64 | public static function realAsset(string $type = 'css'): ?string
65 | {
66 | $manifest = Json::read(__DIR__ . "/dist/.vite/manifest.json");
67 |
68 | if (empty($manifest)) {
69 | throw new NotFoundException(['fallback' => 'manifest.json file missing in kirby-cookie-banner plugin']);
70 | }
71 |
72 | if ($type === 'css') {
73 | return __DIR__ . "/dist/" . $manifest['src/cookie-banner.js']['css'][0];
74 | }
75 |
76 | if ($type === 'js') {
77 | return __DIR__ . "/dist/" . $manifest['src/cookie-banner.js']['file'];
78 | }
79 |
80 | return null;
81 | }
82 |
83 | public static function asset(string $name): string
84 | {
85 | /** @var Plugin $plugin */
86 | $plugin = kirby()->plugins()['michnhokn/cookie-banner'];
87 | return $plugin->asset($name)->url();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/cookie-banner.scss:
--------------------------------------------------------------------------------
1 | .cookie-modal {
2 | position: fixed;
3 | z-index: 1000;
4 | width: 100%;
5 | height: 100%;
6 | background-color: rgba(0, 0, 0, .3);
7 | left: 0;
8 | top: 0;
9 | pointer-events: none;
10 |
11 | &--hidden {
12 | display: none;
13 | }
14 |
15 | &__content {
16 | max-width: clamp(400px, 70dvw, 800px);
17 | padding: 16px;
18 | box-shadow: 0 10px 30px rgba(0, 0, 0, .2);
19 | background-color: #FFFFFF;
20 | margin: 16vh auto 0 auto;
21 | pointer-events: auto;
22 | border-radius: 8px;
23 |
24 | @media (min-width: 400px) {
25 | margin: 22vh auto 0 auto;
26 | padding: 32px;
27 | }
28 | }
29 |
30 | &__title {
31 | font-size: 1.4rem;
32 | font-weight: bold;
33 | margin-bottom: 10px;
34 | margin-top: 0;
35 | }
36 |
37 | &__text {
38 | margin-bottom: 20px;
39 | line-height: 1.4;
40 |
41 | a {
42 | text-decoration: underline;
43 | }
44 | }
45 |
46 | &__options {
47 | margin-bottom: 64px;
48 | display: grid;
49 | flex-direction: row;
50 | gap: 16px;
51 | grid-template-columns: 1fr;
52 |
53 | @media (min-width: 400px) {
54 | grid-template-columns: repeat(2, 1fr);
55 | }
56 |
57 | @media (min-width: 900px) {
58 | grid-template-columns: repeat(3, 1fr);
59 | }
60 | }
61 |
62 | &__option {
63 | width: 100%;
64 | display: inline-flex;
65 | flex-direction: row;
66 | align-items: center;
67 | justify-content: flex-start;
68 | position: relative;
69 | cursor: pointer;
70 |
71 | &.disabled {
72 | opacity: .7;
73 | }
74 | }
75 |
76 | &__checkbox {
77 | appearance: none;
78 | -webkit-appearance: none;
79 | -moz-appearance: none;
80 | display: block;
81 | width: 20px;
82 | height: 20px;
83 | border: 2px solid #000000;
84 | margin: 0 6px 0 0;
85 | flex-shrink: 0;
86 | border-radius: 4px;
87 |
88 | &:checked {
89 | background-color: #000000;
90 |
91 | &:focus-visible {
92 | outline: none;
93 | background-color: #81a2be;
94 | }
95 | }
96 |
97 | &:focus-visible {
98 | outline: none;
99 | border-color: #81a2be;
100 | }
101 | }
102 |
103 | &__check {
104 | position: absolute;
105 | left: 0;
106 | z-index: 1;
107 | display: flex;
108 | justify-content: center;
109 | align-items: center;
110 | width: 20px;
111 | height: 20px;
112 | flex-shrink: 0;
113 |
114 | svg {
115 | stroke: #FFFFFF;
116 | }
117 | }
118 |
119 | &__label {
120 | line-height: 22px;
121 | }
122 |
123 | &__buttons {
124 | display: flex;
125 | flex-direction: row;
126 | align-items: flex-start;
127 | justify-content: flex-start;
128 | }
129 |
130 | &__button {
131 | display: block;
132 | margin-right: 8px;
133 | padding: 6px 20px;
134 | white-space: nowrap;
135 | border: 2px solid #000000;
136 | text-decoration: none;
137 | color: #000000;
138 | border-radius: 4px;
139 |
140 | @media (min-width: 400px) {
141 | margin-right: 10px;
142 | padding: 10px 24px;
143 | }
144 |
145 | &.primary {
146 | background-color: #000000;
147 | color: #FFFFFF;
148 |
149 | &:focus-visible, &:hover {
150 | background-color: #81a2be;
151 | }
152 | }
153 |
154 | &:focus-visible, &:hover {
155 | outline: none;
156 | border-color: #81a2be;
157 | }
158 |
159 | &.hide {
160 | display: none;
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/dist/assets/cookie-banner-DSt9AXzp.js:
--------------------------------------------------------------------------------
1 | /*! js-cookie v3.0.5 | MIT */function E(o){for(var e=1;e"u")){s=E({},e,s),typeof s.expires=="number"&&(s.expires=new Date(Date.now()+s.expires*864e5)),s.expires&&(s.expires=s.expires.toUTCString()),i=encodeURIComponent(i).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var r="";for(var c in s)s[c]&&(r+="; "+c,s[c]!==!0&&(r+="="+s[c].split(";")[0]));return document.cookie=i+"="+o.write(a,i)+r}}function n(i){if(!(typeof document>"u"||arguments.length&&!i)){for(var a=document.cookie?document.cookie.split("; "):[],s={},r=0;rthis.registerHooks())}initCookieModal(){const e=this;return new Promise(t=>{e.loadMaximumFeatures(),e.loadCustomFeatures(),e.CUSTOM_FEATURES.length===0&&this.SHOW_ON_FIRST?e.openCookieModal():e.CUSTOM_FEATURES.length!==0&&T("cookies:loaded",e.CUSTOM_FEATURES),t()})}registerHooks(){const e=this;Array.prototype.forEach.call(e.$FEATURES,t=>{t.addEventListener("change",n=>e.updateCustomFeatures())}),e.$ACCEPT_BUTTON.addEventListener("click",t=>e.save(e.MAXIMUM_FEATURES)),e.$DENY_BUTTON.addEventListener("click",t=>e.save(e.MINUMUM_FEATURES)),e.$SAVE_BUTTON.addEventListener("click",t=>e.save(e.CUSTOM_FEATURES)),d("body").addEventListener("cookies:update",t=>{e.loadCustomFeatures(),e.openCookieModal()})}loadMaximumFeatures(){const e=this;Array.prototype.forEach.call(e.$FEATURES,t=>{const n=t.dataset.cookieId.toLowerCase();e.MAXIMUM_FEATURES.push(n)})}loadCustomFeatures(){const e=this;if(u.get("cookie_status")){e.CUSTOM_FEATURES=u.get("cookie_status").split(",");const t=Array.prototype.filter.call(e.$FEATURES,n=>{const i=n.dataset.cookieId.toLowerCase();return e.CUSTOM_FEATURES.indexOf(i)>-1});Array.prototype.forEach.call(t,n=>{n.setAttribute("checked",!0)}),e.updateButtons()}}updateCustomFeatures(){const e=this;e.CUSTOM_FEATURES=[];const t=Array.prototype.filter.call(e.$FEATURES,n=>n.checked);Array.prototype.forEach.call(t,n=>{const i=n.dataset.cookieId.toLowerCase();e.CUSTOM_FEATURES.push(i)}),e.updateButtons()}save(e){const t=this;event.preventDefault(),e.length||(e=t.MINUMUM_FEATURES),T("cookies:saved",e),t.setCookie(e),t.CUSTOM_FEATURES=e,t.closeCookieModal()}updateButtons(){let e=this;e.CUSTOM_FEATURES.length>1?(e.$ACCEPT_BUTTON.classList.add("hide"),e.$DENY_BUTTON.classList.add("hide"),e.$SAVE_BUTTON.classList.remove("hide")):(e.$ACCEPT_BUTTON.classList.remove("hide"),e.$DENY_BUTTON.classList.remove("hide"),e.$SAVE_BUTTON.classList.add("hide"))}setCookie(e){u.set("cookie_status",e.join(","),{expires:365,sameSite:"lax"})}closeCookieModal(){const e=this;e.$COOKIE_MODAL.classList.add("cookie-modal--hidden"),e.MODAL_OPEN=!1}openCookieModal(){const e=this;e.$COOKIE_MODAL.classList.remove("cookie-modal--hidden"),e.MODAL_OPEN=!0}}document.addEventListener("DOMContentLoaded",()=>{window.cookieBanner=new p});
2 |
--------------------------------------------------------------------------------
/src/cookie-banner.js:
--------------------------------------------------------------------------------
1 | import './cookie-banner.scss';
2 | import Cookies from 'js-cookie';
3 |
4 | function element(selector) {
5 | return document.querySelector(selector);
6 | }
7 |
8 | function allElements(selector) {
9 | return document.querySelectorAll(selector);
10 | }
11 |
12 | function triggerEvent(eventName, data = {}) {
13 | let customEvent = null;
14 | if (window.CustomEvent && typeof window.CustomEvent === 'function') {
15 | customEvent = new CustomEvent(eventName, {detail: data});
16 | } else {
17 | customEvent = document.createEvent('CustomEvent');
18 | customEvent.initCustomEvent(eventName, true, true, data);
19 | }
20 | document.querySelector('body').dispatchEvent(customEvent);
21 | }
22 |
23 | class CookieBanner {
24 | constructor() {
25 | this.$COOKIE_MODAL = element('#cookie-modal');
26 | this.$FEATURES = allElements('.cookie-modal__checkbox');
27 | this.$ACCEPT_BUTTON = element('#cookie-accept');
28 | this.$DENY_BUTTON = element('#cookie-deny');
29 | this.$SAVE_BUTTON = element('#cookie-save');
30 |
31 | this.MODAL_OPEN = false;
32 | this.MINUMUM_FEATURES = ['essential'];
33 | this.MAXIMUM_FEATURES = [];
34 | this.CUSTOM_FEATURES = [];
35 | this.SHOW_ON_FIRST = this.$COOKIE_MODAL.dataset.showOnFirst === 'true';
36 |
37 | this.initCookieModal().then(_ => this.registerHooks());
38 | }
39 |
40 | initCookieModal() {
41 | const _this = this;
42 | return new Promise(resolve => {
43 | _this.loadMaximumFeatures();
44 | _this.loadCustomFeatures();
45 | if (_this.CUSTOM_FEATURES.length === 0 && this.SHOW_ON_FIRST) {
46 | _this.openCookieModal();
47 | } else if (_this.CUSTOM_FEATURES.length !== 0) {
48 | triggerEvent('cookies:loaded', _this.CUSTOM_FEATURES);
49 | }
50 | resolve();
51 | });
52 | }
53 |
54 | registerHooks() {
55 | const _this = this;
56 | Array.prototype.forEach.call(_this.$FEATURES, feature => {
57 | feature.addEventListener('change', _ => _this.updateCustomFeatures());
58 | });
59 | _this.$ACCEPT_BUTTON.addEventListener(
60 | 'click',
61 | _ => _this.save(_this.MAXIMUM_FEATURES),
62 | );
63 | _this.$DENY_BUTTON.addEventListener(
64 | 'click',
65 | _ => _this.save(_this.MINUMUM_FEATURES),
66 | );
67 | _this.$SAVE_BUTTON.addEventListener(
68 | 'click',
69 | _ => _this.save(_this.CUSTOM_FEATURES),
70 | );
71 | element('body').addEventListener('cookies:update', _ => {
72 | _this.loadCustomFeatures();
73 | _this.openCookieModal();
74 | });
75 | }
76 |
77 | loadMaximumFeatures() {
78 | const _this = this;
79 | Array.prototype.forEach.call(_this.$FEATURES, feature => {
80 | const featureKey = feature.dataset.cookieId.toLowerCase();
81 | _this.MAXIMUM_FEATURES.push(featureKey);
82 | });
83 | }
84 |
85 | loadCustomFeatures() {
86 | const _this = this;
87 | if (Cookies.get('cookie_status')) {
88 | _this.CUSTOM_FEATURES = Cookies.get('cookie_status').split(',');
89 | const activeFeatures = Array.prototype.filter.call(_this.$FEATURES,
90 | feature => {
91 | const featureKey = feature.dataset.cookieId.toLowerCase();
92 | return _this.CUSTOM_FEATURES.indexOf(featureKey) > -1;
93 | });
94 | Array.prototype.forEach.call(activeFeatures, feature => {
95 | feature.setAttribute('checked', true);
96 | });
97 | _this.updateButtons();
98 | }
99 | }
100 |
101 | updateCustomFeatures() {
102 | const _this = this;
103 | _this.CUSTOM_FEATURES = [];
104 | const checkedFeatures = Array.prototype.filter.call(
105 | _this.$FEATURES,
106 | feature => feature.checked,
107 | );
108 | Array.prototype.forEach.call(checkedFeatures, feature => {
109 | const featureKey = feature.dataset.cookieId.toLowerCase();
110 | _this.CUSTOM_FEATURES.push(featureKey);
111 | });
112 | _this.updateButtons();
113 | }
114 |
115 | save(features) {
116 | const _this = this;
117 | event.preventDefault();
118 | if (!features.length) {
119 | features = _this.MINUMUM_FEATURES;
120 | }
121 | triggerEvent('cookies:saved', features);
122 | _this.setCookie(features);
123 | _this.CUSTOM_FEATURES = features;
124 | _this.closeCookieModal();
125 | }
126 |
127 | updateButtons() {
128 | let _this = this;
129 | if (_this.CUSTOM_FEATURES.length > 1) {
130 | _this.$ACCEPT_BUTTON.classList.add('hide');
131 | _this.$DENY_BUTTON.classList.add('hide');
132 | _this.$SAVE_BUTTON.classList.remove('hide');
133 | } else {
134 | _this.$ACCEPT_BUTTON.classList.remove('hide');
135 | _this.$DENY_BUTTON.classList.remove('hide');
136 | _this.$SAVE_BUTTON.classList.add('hide');
137 | }
138 | }
139 |
140 | setCookie(features) {
141 | Cookies.set('cookie_status', features.join(','),
142 | {expires: 365, sameSite: 'lax'});
143 | }
144 |
145 | closeCookieModal() {
146 | const _this = this;
147 | _this.$COOKIE_MODAL.classList.add('cookie-modal--hidden');
148 | _this.MODAL_OPEN = false;
149 | }
150 |
151 | openCookieModal() {
152 | const _this = this;
153 | _this.$COOKIE_MODAL.classList.remove('cookie-modal--hidden');
154 | _this.MODAL_OPEN = true;
155 | }
156 | }
157 |
158 | document.addEventListener('DOMContentLoaded', () => {
159 | window.cookieBanner = new CookieBanner();
160 | });
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!WARNING]
2 | > Heads up! This plugin is no longer supported or maintained. Please find a suitable alternative here: https://plugins.getkirby.com/zephir/cookieconsent
3 |
4 | 
5 |
6 | # Kirby Cookie Banner
7 |
8 | Integrate a user-friendly cookie banner into your Kirby website with ease. This simple solution allows for effortless
9 | incorporation directly into your Kirby page.
10 |
11 | ## Key Features
12 |
13 | * 🚀 **Customizable Cookie Consent Modal:** Design the modal to perfectly match your website's look and feel.
14 | * 🌐 **Multilingual Support (including German & English):** Deliver a seamless user experience by displaying the cookie
15 | banner in different languages.
16 | * ⏱️ **Real-time Cookie Consent Detection:** Keep track of user preferences and adapt accordingly.
17 | * ✅ **Easy Feature Management with a Helper Class:** Simplify checking for allowed cookies and features within your
18 | Kirby
19 | code.
20 | * ✨ **Effortless Style Integration:** Apply your custom CSS styles with minimal hassle.
21 |
22 | ## Installation
23 |
24 | ### Composer
25 |
26 | ```
27 | composer require michnhokn/kirby3-cookie-banner
28 | ```
29 |
30 | ### Download
31 |
32 | Download and copy this repository to `/site/plugins/kirby-cookie-banner`
33 |
34 | ## Usage
35 |
36 | ### Configuration
37 |
38 | The cookie banner includes a pre-configured "Essential Feature" with customizable text using language variables. You can
39 | easily add more features by editing your `/site/config/config.php` file. By default, leverage language variables for
40 | feature values, but if you're not using Kirby's multi-language functionality, simply adjust the text directly in the
41 | configuration.
42 |
43 | ```php
44 | [
49 | // add your features and the language variables
50 | 'features' => [
51 | 'analytics' => 'custom.cookie-modal.analytics',
52 | 'mapbox' => 'custom.cookie-modal.mapbox',
53 | ...
54 | ]
55 | // optional - adjust the texts if you are not using Kirby's multi-language functionality
56 | 'translations' => [
57 | 'title' => 'Your custom title',
58 | 'text' => 'This is your custom Text',
59 | 'essentialText' => 'Essenziell',
60 | 'denyAll' => 'Alle ablehnen',
61 | 'acceptAll' => 'Alle annehmen',
62 | 'save' => 'Speichern',
63 | ]
64 | ]
65 | ];
66 | ```
67 |
68 | ### Snippet
69 |
70 | ```` php
71 | false,
74 | // Displays the consent modal on initial load. Default: true
75 | 'showOnFirst' => false,
76 | // [WIP] Reloads the whole page instead of loading only the blocked scripts. Default: true
77 | // 'reload' => false,
78 | // [WIP] Can be used to set a new consent version to force a new display for the end user. Default: null
79 | // 'version' => 1,
80 | // Adds additional features. I recommend using the config. Default: null
81 | 'features' => [
82 | 'analytics' => 'custom.cookie-modal.analytics',
83 | 'mapbox' => 'custom.cookie-modal.mapbox',
84 | ...
85 | ]
86 | ]) ?>
87 | ````
88 |
89 | ### Track User Cookie Preferences
90 |
91 | The cookie banner triggers a cookies:saved event on the tag whenever a user confirms their settings. This allows
92 | you to easily capture this event and react accordingly. Here's an example of how you might intercept it:
93 |
94 | ```javascript
95 | document.querySelector('body').addEventListener('cookies:saved', ({detail}) => {
96 | console.log('Saved cookie features:', detail);
97 | })
98 |
99 | // Example output (array)
100 | // Saved cookie features: ['essential', 'analytics', 'mapbox']
101 | ```
102 |
103 | ### Open the cookie modal
104 |
105 | Want to give users the option to revisit cookie settings? Simply add a link that calls the openCookieModal() method of
106 | the cookie banner. For instance, clicking this link would reopen the modal:
107 |
108 | ```html
109 | Edit Cookie Settings
110 | ```
111 |
112 | ### Helper class
113 |
114 | The `Michnhokn\CookieBanner` class offers a set of handy methods to control your cookie features.
115 |
116 | This simplifies tasks like:
117 |
118 | * Checking if a specific feature is allowed by the user.
119 | * Performing actions based on allowed features.
120 |
121 | ```php
122 | // returns a list of features accepted by the visitor
123 | \Michnhokn\CookieBanner::allowedFeatures(): array
124 |
125 | // check if one feature is accepted by the visitor
126 | \Michnhokn\CookieBanner::isFeatureAllowed('youtube'): bool
127 |
128 | // returns all configured plus additional features
129 | \Michnhokn\CookieBanner::availableFeatures(additionalFeatures: ['recaptcha']): array
130 |
131 | // clear the cookie for a visitor
132 | \Michnhokn\CookieBanner::clear(): void
133 | ```
134 |
135 | ### Translate the modal
136 |
137 | Customize your cookie banner for a global audience! Simply provide the following variables through a language file to
138 | translate the modal content. This ensures a seamless user experience in any language.
139 |
140 | ```php
141 | // site/languages/es.php
142 | 'es',
146 | 'default' => true,
147 | 'direction' => 'ltr',
148 | 'locale' => 'es_ES',
149 | 'name' => 'Spanish',
150 | 'translations' => [
151 | 'michnhokn.cookie-banner.title' => 'Configuración de las galletas',
152 | 'michnhokn.cookie-banner.text' => 'Utilizamos cookies para proporcionarle la mejor experiencia posible. También nos permiten analizar el comportamiento de los usuarios para mejorar constantemente el sitio web para usted. (link: politica-de-privacidadtext: Política de privacidad)',
153 | 'michnhokn.cookie-banner.essentialText' => 'Esencial',
154 | 'michnhokn.cookie-banner.denyAll' => 'Rechazar todo',
155 | 'michnhokn.cookie-banner.acceptAll' => 'Acepta todo',
156 | 'michnhokn.cookie-banner.save' => 'Guardar la configuración',
157 |
158 | // custom features translation
159 | 'custom.cookie-modal.analytics' => 'Analítica'
160 | ]
161 | ];
162 | ```
163 |
164 | ## Support the project
165 |
166 | > [!NOTE]
167 | > This plugin is provided free of charge & published under the permissive MIT License. If you use it in a commercial
168 | > project, please consider to [buy me a beer 🍺](https://buymeacoff.ee/michnhokn)
169 |
170 | ## Compatability
171 | Starting with version `1.1.0` of this plugin, it is only compatible with Kirby 4.
172 | If you need to install this plugin for a Kirby 3 instance, please use version `1.0.9`.
173 |
174 | ## License
175 |
176 | [MIT](./LICENSE) License © 2020-PRESENT [Michael Engel](https://github.com/michnhokn)
177 |
--------------------------------------------------------------------------------