From 39a68748ea1cca7c87beca417ea112a2299efd41 Mon Sep 17 00:00:00 2001 From: Niklas Droste Date: Wed, 1 Sep 2021 10:51:24 +0200 Subject: [PATCH 1/3] WIP --- .../NodeTypes.Content.CookieSettings.yaml | 19 ++++++++ Configuration/Settings.KaufmannDigital.yaml | 24 +++++++++++ Configuration/Settings.Neos.yaml | 21 +++++++++ Configuration/Settings.yaml | 43 ------------------- .../Fusion/Content/CookieSettings.fusion | 3 +- .../Fusion/Helper/JavaScriptSettings.fusion | 11 ++++- .../de/NodeTypes/Content/CookieSettings.xlf | 19 ++++++++ .../en/NodeTypes/Content/CookieSettings.xlf | 16 +++++++ Resources/Public/JavaScript/Initialize.js | 10 ++++- Resources/Public/JavaScript/Main.js | 12 +++++- 10 files changed, 128 insertions(+), 50 deletions(-) create mode 100644 Configuration/Settings.KaufmannDigital.yaml create mode 100644 Configuration/Settings.Neos.yaml delete mode 100644 Configuration/Settings.yaml diff --git a/Configuration/NodeTypes.Content.CookieSettings.yaml b/Configuration/NodeTypes.Content.CookieSettings.yaml index 633ca7d..4c4268c 100644 --- a/Configuration/NodeTypes.Content.CookieSettings.yaml +++ b/Configuration/NodeTypes.Content.CookieSettings.yaml @@ -123,3 +123,22 @@ label: i18n inspector: group: cookieSettings + cookieTtl: + type: integer + defaultValue: 31536000 + ui: + label: i18n + help: + message: i18n + inspector: + group: cookieSettings + closeButtonEnabled: + type: boolean + defaultValue: false + ui: + label: i18n + reloadIfChanged: true + help: + message: i18n + inspector: + group: cookieSettings diff --git a/Configuration/Settings.KaufmannDigital.yaml b/Configuration/Settings.KaufmannDigital.yaml new file mode 100644 index 0000000..75825da --- /dev/null +++ b/Configuration/Settings.KaufmannDigital.yaml @@ -0,0 +1,24 @@ +KaufmannDigital: + GDPR: + CookieConsent: + siteCSSFilepath: null + customCSSFilepath: null + consentLogEnabled: false + consentDimensions: [] + # See README.md + # - language + # - country + cookieName: 'KD_GDPR_CC' + cookieDomainName: null + siteStyles: [] + # "rootNode name 1": + # siteCSSFilepath: + # customCSSFilepath: + # "rootNode name 2": + # siteCSSFilepath: + # customCSSFilepath: + backend: + includeGeneratedJs: true + excludeDocumentNodeTypes: [] + # See README.md + # - 'Vendor.Package:Document.Imprint' diff --git a/Configuration/Settings.Neos.yaml b/Configuration/Settings.Neos.yaml new file mode 100644 index 0000000..138ed25 --- /dev/null +++ b/Configuration/Settings.Neos.yaml @@ -0,0 +1,21 @@ +Neos: + Neos: + nodeTypes: + groups: + cookies: + position: 300 + label: 'Cookies' + collapsed: true + fusion: + autoInclude: + KaufmannDigital.GDPR.CookieConsent: true + userInterface: + translation: + autoInclude: + 'KaufmannDigital.GDPR.CookieConsent': + - 'NodeTypes/*' + Flow: + mvc: + routes: + KaufmannDigital.GDPR.CookieConsent: + position: 'before Neos.Neos' diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml deleted file mode 100644 index 701cedb..0000000 --- a/Configuration/Settings.yaml +++ /dev/null @@ -1,43 +0,0 @@ -Neos: - Neos: - nodeTypes: - groups: - cookies: - position: 300 - label: 'Cookies' - collapsed: true - fusion: - autoInclude: - KaufmannDigital.GDPR.CookieConsent: true - userInterface: - translation: - autoInclude: - 'KaufmannDigital.GDPR.CookieConsent': - - 'NodeTypes/*' - Flow: - mvc: - routes: - KaufmannDigital.GDPR.CookieConsent: - position: 'before Neos.Neos' - -KaufmannDigital: - GDPR: - CookieConsent: - siteCSSFilepath: null - customCSSFilepath: null - consentLogEnabled: false - consentDimensions: [] - cookieName: 'KD_GDPR_CC' - cookieDomainName: null -# See README.md -# - language -# - country - siteStyles: [] -# "rootNode name 1": -# siteCSSFilepath: -# customCSSFilepath: -# "rootNode name 2": -# siteCSSFilepath: -# customCSSFilepath: - backend: - includeGeneratedJs: true diff --git a/Resources/Private/Fusion/Content/CookieSettings.fusion b/Resources/Private/Fusion/Content/CookieSettings.fusion index 56a9f35..6d2eb7e 100644 --- a/Resources/Private/Fusion/Content/CookieSettings.fusion +++ b/Resources/Private/Fusion/Content/CookieSettings.fusion @@ -17,6 +17,7 @@ prototype(KaufmannDigital.GDPR.CookieConsent:Content.CookieSettings) < prototype } hasNecessaryGroup = ${q(node).find('[instanceof KaufmannDigital.GDPR.CookieConsent:Content.NecessarySettingGroup]').count() > 0} + closeButtonEnabled = ${q(node).property('closeButtonEnabled')} labels = Neos.Fusion:RawArray { acceptAll = ${q(node).property('acceptAllButtonLabel')} @@ -57,7 +58,7 @@ prototype(KaufmannDigital.GDPR.CookieConsent:Content.CookieSettings) < prototype
- +
diff --git a/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion b/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion index 3f13754..f8df807 100644 --- a/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion +++ b/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion @@ -1,7 +1,10 @@ prototype(KaufmannDigital.GDPR.CookieConsent:Helper.JavaScriptSettings) < prototype(Neos.Fusion:Tag) { tagName = 'script' - @context.versionDate = ${q(site).find('[instanceof KaufmannDigital.GDPR.CookieConsent:Content.CookieSettings]').first().property("versionDate")} + cookieSettingsNode = ${q(site).find('[instanceof KaufmannDigital.GDPR.CookieConsent:Content.CookieSettings]').get(0)} + @context.versionDate = ${q(this.cookieSettingsNode).property('versionDate')} + @context.cookieTtl = ${q(this.cookieSettingsNode).property('cookieTtl')} + @context.apiUrl = Neos.Fusion:UriBuilder { package = 'KaufmannDigital.GDPR.CookieConsent' controller = 'Api' @@ -18,13 +21,17 @@ prototype(KaufmannDigital.GDPR.CookieConsent:Helper.JavaScriptSettings) < protot @context.cookieName = ${Configuration.setting('KaufmannDigital.GDPR.CookieConsent.cookieName')} @context.cookieDomainName = ${Configuration.setting('KaufmannDigital.GDPR.CookieConsent.cookieDomainName')} + @context.nodeTypeDisabled = ${Array.indexOf(Configuration.setting('KaufmannDigital.GDPR.CookieConsent.excludeDocumentNodeTypes'), documentNode.nodeType.name) >= 0} + content = ${" var KD_GDPR_CC = { apiUrl: '" + apiUrl + "', cookieName: '" + cookieName +"', cookieDomainName: '" + cookieDomainName +"', versionTimestamp: " + versionDate.timestamp * 1000 + ", - dimensionsIdentifier: '" + (dimensionsIdentifier != '' ? dimensionsIdentifier : 'default') + "' + cookieTtl: " + (cookieTtl ? (cookieTtl * 1000) : 0) + ", + dimensionsIdentifier: '" + (dimensionsIdentifier != '' ? dimensionsIdentifier : 'default') + "', + nodeTypeDisabled: " + (nodeTypeDisabled ? 'true' : 'false') +" }; "} @if.condition = ${node.context.inBackend == false} diff --git a/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf b/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf index 4394f81..c517906 100644 --- a/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf +++ b/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf @@ -69,6 +69,25 @@ Version date Versions-Datum + + + TTL (Decision time in seconds) + TTL (Speicherdauer der Entscheidung in Sekunden) + + + + Number of seconds until cookie settings are displayed and confirmed again. Example values are: 86400 (1 day), 604800 (1 week), 2592000 (30 days), 31536000 (1 year) + Anzahl der Sekunden, bis die Cookie-Einstellungen erneut angezeigt und bestätigt werden. Beispielwerte sind: 86400 (1 Tag), 604800 (1 Woche), 2592000 (30 Tage), 31536000 (1 Jahr) + + + + Show close-button + Zeige X (zum schließen) + + + + Zeigt ein "X" in der oberen rechten Ecke, falls aktiviert. Ein klick auf dieses X schließt den Banner ohne Entscheidung. Er erscheint beim nächsten Seitenaufruf erneut. + diff --git a/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf b/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf index 66feb92..5a39f34 100644 --- a/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf +++ b/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf @@ -54,6 +54,22 @@ Version date + + + TTL (Decision time in seconds) + + + + Number of seconds until cookie settings are displayed and confirmed again. Example values are: 86400 (1 day), 604800 (1 week), 2592000 (30 days), 31536000 (1 year) + + + + Show close-button + + + + Shows an "X" on the right top of the banner, if enabled. A click on the X hides the banner, but without making a decision. It re-appears on the next pageload. + diff --git a/Resources/Public/JavaScript/Initialize.js b/Resources/Public/JavaScript/Initialize.js index f099f43..7bda007 100644 --- a/Resources/Public/JavaScript/Initialize.js +++ b/Resources/Public/JavaScript/Initialize.js @@ -18,7 +18,7 @@ function loadCookiebannerHtml() { xhr.send(); } -if (document.cookie.indexOf(KD_GDPR_CC.cookieName) >= 0) { +if (KD_GDPR_CC.nodeTypeDisabled === false && document.cookie.indexOf(KD_GDPR_CC.cookieName) >= 0) { /*Cookie set*/ window.dataLayer = window.dataLayer || []; var cookieObject = JSON.parse( @@ -42,13 +42,19 @@ if (document.cookie.indexOf(KD_GDPR_CC.cookieName) >= 0) { loadCookiebannerHtml(); } + if (!cookieObject.expireDates || !cookieObject.expireDates[KD_GDPR_CC.dimensionsIdentifier]) { + loadCookiebannerHtml(); + } else if (new Date(cookieObject.expireDates[KD_GDPR_CC.dimensionsIdentifier]) < new Date()) { + loadCookiebannerHtml(); + } + window.dataLayer.push({ event: 'KD_GDPR_CC_consent', KD_GDPR_CC: { consents: cookieObject.consents, }, }); -} else if (document.getElementsByClassName('gdpr-cookieconsent-settings').length === 0 && window.neos === undefined) { +} else if (KD_GDPR_CC.nodeTypeDisabled === false && document.getElementsByClassName('gdpr-cookieconsent-settings').length === 0 && window.neos === undefined) { /*No Cookie set, not in backend & not on cookie page*/ loadCookiebannerHtml(); } diff --git a/Resources/Public/JavaScript/Main.js b/Resources/Public/JavaScript/Main.js index b85212d..e51d061 100644 --- a/Resources/Public/JavaScript/Main.js +++ b/Resources/Public/JavaScript/Main.js @@ -190,7 +190,11 @@ function dispatchEventsForCookies(inputs) { function saveConsentToCookie(inputs, userId) { var cookie = decodeCookie(); var currentDate = new Date(); - var expireDate = new Date(currentDate.getFullYear() + 1, currentDate.getMonth(), currentDate.getDate()); + if (KD_GDPR_CC.cookieTtl && KD_GDPR_CC.cookieTtl > 0) { + expireDate = new Date(currentDate.getTime() + KD_GDPR_CC.cookieTtl); + } else { + var expireDate = new Date(currentDate.getFullYear() + 1, currentDate.getMonth(), currentDate.getDate()); + } var consents = cookie && cookie.consents ? cookie.consents : {}; consents[KD_GDPR_CC.dimensionsIdentifier] = []; @@ -201,15 +205,19 @@ function saveConsentToCookie(inputs, userId) { var consentDates = cookie && cookie.consentDates ? cookie.consentDates : {}; consentDates[KD_GDPR_CC.dimensionsIdentifier] = currentDate.toUTCString(); + var expireDates = cookie && cookie.expireDates ? cookie.expireDates : {}; + expireDates[KD_GDPR_CC.dimensionsIdentifier] = expireDate.toUTCString(); + var cookieData = { userId: userId, consents: consents, consentDates: consentDates, + expireDates: expireDates, consentDate: currentDate.toUTCString(), expireDate: expireDate.toUTCString() }; - var cookieParams = encodeURI(JSON.stringify(cookieData)) + "; expires=" + expireDate.toUTCString() + "; path=/; " + (KD_GDPR_CC.cookieDomainName ? ('domain=' + KD_GDPR_CC.cookieDomainName + ';') : '') +" Secure;"; + var cookieParams = encodeURI(JSON.stringify(cookieData)) + "; expires=" + new Date(currentDate.getTime() + 315360000000).toUTCString() + "; path=/; " + (KD_GDPR_CC.cookieDomainName ? ('domain=' + KD_GDPR_CC.cookieDomainName + ';') : '') +" Secure;"; document.cookie = KD_GDPR_CC.cookieName + "=" + cookieParams; window.dataLayer = window.dataLayer || []; From 028648085341564134434bf09d47c89909d05981 Mon Sep 17 00:00:00 2001 From: Niklas Droste Date: Thu, 2 Sep 2021 17:21:57 +0200 Subject: [PATCH 2/3] Feature: Re-open banner after time --- Classes/Controller/JavaScriptController.php | 27 ++++++++++++++---- .../NodeTypes.Content.CookieSettings.yaml | 2 +- Documentation/Images/Decision_TTL.png | Bin 0 -> 22998 bytes README.md | 5 ++++ .../Fusion/Helper/JavaScriptSettings.fusion | 4 +-- .../de/NodeTypes/Content/CookieSettings.xlf | 4 +-- .../en/NodeTypes/Content/CookieSettings.xlf | 4 +-- Resources/Public/JavaScript/Initialize.js | 13 +++++---- Resources/Public/JavaScript/Main.js | 13 +++++---- 9 files changed, 47 insertions(+), 25 deletions(-) create mode 100644 Documentation/Images/Decision_TTL.png diff --git a/Classes/Controller/JavaScriptController.php b/Classes/Controller/JavaScriptController.php index 353bed4..c2022d5 100644 --- a/Classes/Controller/JavaScriptController.php +++ b/Classes/Controller/JavaScriptController.php @@ -57,15 +57,18 @@ public function initializeRenderJavaScriptAction() $this->response->setComponentParameter(SetHeaderComponent::class, 'Cache-Control', 'max-age=0, private, must-revalidate'); } + public function renderJavaScriptAction(array $dimensions = []) { - try { $filteredDimensions = - array_filter($dimensions, function($key) { - return in_array($key, $this->consentDimensions); - }, - ARRAY_FILTER_USE_KEY); + array_filter( + $dimensions, + function ($key) { + return in_array($key, $this->consentDimensions); + }, + ARRAY_FILTER_USE_KEY + ); $dimensionIdentifier = implode( '_', @@ -78,12 +81,24 @@ public function renderJavaScriptAction(array $dimensions = []) $cacheIdentifier = 'kd_gdpr_cc_' . sha1(json_encode($consents) . $dimensionIdentifier . $siteNode->getIdentifier()); + $q = new FlowQuery([$siteNode]); + + $consentDate = new \DateTime(isset($cookie['consentDates'][$dimensionIdentifier]) ? $cookie['consentDates'][$dimensionIdentifier] : $cookie['consentDate']); + $cookieSettings = $q->find('[instanceof KaufmannDigital.GDPR.CookieConsent:Content.CookieSettings]')->get(0); + $decisionTtl = $cookieSettings->getProperty('decisionTtl') ?? 0; + $expireDate = clone $consentDate; + $expireDate->add(\DateInterval::createFromDateString($decisionTtl . ' seconds')); + if ($expireDate < new \DateTime('now')) { + $this->response->setContentType('text/javascript'); + $this->response->setContent(''); + return; + } + if ($this->cache->has($cacheIdentifier)) { $this->redirect('downloadGeneratedJavaScript', null, null, ['hash' => $cacheIdentifier]); return; } - $q = new FlowQuery([$siteNode]); $cookieNodes = $q->find('[instanceof KaufmannDigital.GDPR.CookieConsent:Content.Cookie][javaScriptCode != ""]')->sort('priority', 'DESC')->get(); $javaScript = ''; diff --git a/Configuration/NodeTypes.Content.CookieSettings.yaml b/Configuration/NodeTypes.Content.CookieSettings.yaml index 4c4268c..562025f 100644 --- a/Configuration/NodeTypes.Content.CookieSettings.yaml +++ b/Configuration/NodeTypes.Content.CookieSettings.yaml @@ -123,7 +123,7 @@ label: i18n inspector: group: cookieSettings - cookieTtl: + decisionTtl: type: integer defaultValue: 31536000 ui: diff --git a/Documentation/Images/Decision_TTL.png b/Documentation/Images/Decision_TTL.png new file mode 100644 index 0000000000000000000000000000000000000000..3d55aa257a42fc94d276db0b97ad6fb2a6bbaeaa GIT binary patch literal 22998 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 8479032..9dc0ae2 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,11 @@ To do this, you only need to edit the version date. You can find it in the inspe After the date has been changed, the banner will be shown again to all visitors, who have submitted the cookie banner before this date. Old settings are used as presets. +#### Invalidating user-decisions after time +Sometimes it is necessary or advantageous to remind the user of his decision and ask him to confirm it again. For this purpose, a TTL for the decision can be set in the backend: +![decision-time setting](Documentation/Images/Decision_TTL.png) +The unit is seconds. After the set time has expired, the banner appears again with the default settings of the last decision, so the user can easily accept the old decision with just one click. +The value `0` (default) disables the repeated display of the banner. ### Styling #### Custom Banner-Styles The banner comes with a few basic-styles for positioning, which are getting included inline. To add your custom styles, just put a CSS-Files somewhere in your Resources-Folder and include it using Settings.yaml: diff --git a/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion b/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion index f8df807..07f8882 100644 --- a/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion +++ b/Resources/Private/Fusion/Helper/JavaScriptSettings.fusion @@ -3,7 +3,7 @@ prototype(KaufmannDigital.GDPR.CookieConsent:Helper.JavaScriptSettings) < protot cookieSettingsNode = ${q(site).find('[instanceof KaufmannDigital.GDPR.CookieConsent:Content.CookieSettings]').get(0)} @context.versionDate = ${q(this.cookieSettingsNode).property('versionDate')} - @context.cookieTtl = ${q(this.cookieSettingsNode).property('cookieTtl')} + @context.decisionTtl = ${q(this.cookieSettingsNode).property('decisionTtl')} @context.apiUrl = Neos.Fusion:UriBuilder { package = 'KaufmannDigital.GDPR.CookieConsent' @@ -29,7 +29,7 @@ prototype(KaufmannDigital.GDPR.CookieConsent:Helper.JavaScriptSettings) < protot cookieName: '" + cookieName +"', cookieDomainName: '" + cookieDomainName +"', versionTimestamp: " + versionDate.timestamp * 1000 + ", - cookieTtl: " + (cookieTtl ? (cookieTtl * 1000) : 0) + ", + decisionTtl: " + (decisionTtl ? (decisionTtl * 1000) : 0) + ", dimensionsIdentifier: '" + (dimensionsIdentifier != '' ? dimensionsIdentifier : 'default') + "', nodeTypeDisabled: " + (nodeTypeDisabled ? 'true' : 'false') +" }; diff --git a/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf b/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf index c517906..973cbc7 100644 --- a/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf +++ b/Resources/Private/Translations/de/NodeTypes/Content/CookieSettings.xlf @@ -70,12 +70,12 @@ Versions-Datum - + TTL (Decision time in seconds) TTL (Speicherdauer der Entscheidung in Sekunden) - + Number of seconds until cookie settings are displayed and confirmed again. Example values are: 86400 (1 day), 604800 (1 week), 2592000 (30 days), 31536000 (1 year) Anzahl der Sekunden, bis die Cookie-Einstellungen erneut angezeigt und bestätigt werden. Beispielwerte sind: 86400 (1 Tag), 604800 (1 Woche), 2592000 (30 Tage), 31536000 (1 Jahr) diff --git a/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf b/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf index 5a39f34..33575c3 100644 --- a/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf +++ b/Resources/Private/Translations/en/NodeTypes/Content/CookieSettings.xlf @@ -55,11 +55,11 @@ Version date - + TTL (Decision time in seconds) - + Number of seconds until cookie settings are displayed and confirmed again. Example values are: 86400 (1 day), 604800 (1 week), 2592000 (30 days), 31536000 (1 year) diff --git a/Resources/Public/JavaScript/Initialize.js b/Resources/Public/JavaScript/Initialize.js index 7bda007..1822ec2 100644 --- a/Resources/Public/JavaScript/Initialize.js +++ b/Resources/Public/JavaScript/Initialize.js @@ -1,4 +1,4 @@ -function loadCookiebannerHtml() { +function loadCookiebannerHtml(openSettings = false) { if (document.body.classList.contains('neos-backend')) return; var xhr = new XMLHttpRequest(); xhr.addEventListener('load', function() { @@ -10,7 +10,7 @@ function loadCookiebannerHtml() { eval(scriptTags[n].innerHTML); } if (typeof initializeCookieConsent === 'function') { - initializeCookieConsent(); + initializeCookieConsent(openSettings); } }); @@ -42,12 +42,13 @@ if (KD_GDPR_CC.nodeTypeDisabled === false && document.cookie.indexOf(KD_GDPR_CC. loadCookiebannerHtml(); } - if (!cookieObject.expireDates || !cookieObject.expireDates[KD_GDPR_CC.dimensionsIdentifier]) { - loadCookiebannerHtml(); - } else if (new Date(cookieObject.expireDates[KD_GDPR_CC.dimensionsIdentifier]) < new Date()) { - loadCookiebannerHtml(); + //Re-Open Cookie-Consent, if TTL is expired + var decisionExpiry = cookieConsentDate.getTime() + KD_GDPR_CC.decisionTtl; + if (KD_GDPR_CC.decisionTtl > 0 && decisionExpiry < new Date()) { + loadCookiebannerHtml(true); } + window.dataLayer.push({ event: 'KD_GDPR_CC_consent', KD_GDPR_CC: { diff --git a/Resources/Public/JavaScript/Main.js b/Resources/Public/JavaScript/Main.js index e51d061..b07a872 100644 --- a/Resources/Public/JavaScript/Main.js +++ b/Resources/Public/JavaScript/Main.js @@ -18,7 +18,7 @@ }); })([Element.prototype, CharacterData.prototype, DocumentType.prototype]); -function initializeCookieConsent() { +function initializeCookieConsent(openSettings = false) { var kd_gdpr_cc_userid; var cookieSettingsContainer = document.querySelector('.gdpr-cookieconsent-settings'); @@ -56,6 +56,10 @@ function initializeCookieConsent() { btnAcceptAll.style.display = 'none'; btnSaveSettings.style.display = 'block'; }); + if (openSettings) { + let clickEvent = new MouseEvent('click'); + btnIndividualSettingsEnable.dispatchEvent(clickEvent); + } btnIndividualSettingsDisable.addEventListener('click', function() { individualSettingsContainer.style.display = 'none'; @@ -190,8 +194,8 @@ function dispatchEventsForCookies(inputs) { function saveConsentToCookie(inputs, userId) { var cookie = decodeCookie(); var currentDate = new Date(); - if (KD_GDPR_CC.cookieTtl && KD_GDPR_CC.cookieTtl > 0) { - expireDate = new Date(currentDate.getTime() + KD_GDPR_CC.cookieTtl); + if (KD_GDPR_CC.decisionTtl && KD_GDPR_CC.decisionTtl > 0) { + expireDate = new Date(currentDate.getTime() + KD_GDPR_CC.decisionTtl); } else { var expireDate = new Date(currentDate.getFullYear() + 1, currentDate.getMonth(), currentDate.getDate()); } @@ -205,14 +209,11 @@ function saveConsentToCookie(inputs, userId) { var consentDates = cookie && cookie.consentDates ? cookie.consentDates : {}; consentDates[KD_GDPR_CC.dimensionsIdentifier] = currentDate.toUTCString(); - var expireDates = cookie && cookie.expireDates ? cookie.expireDates : {}; - expireDates[KD_GDPR_CC.dimensionsIdentifier] = expireDate.toUTCString(); var cookieData = { userId: userId, consents: consents, consentDates: consentDates, - expireDates: expireDates, consentDate: currentDate.toUTCString(), expireDate: expireDate.toUTCString() }; From 3a9aa728448714f6b925fe61a026e783029fd651 Mon Sep 17 00:00:00 2001 From: Niklas Droste Date: Thu, 2 Sep 2021 17:24:42 +0200 Subject: [PATCH 3/3] Task: Set defaultValue to 0 (disabled) --- Configuration/NodeTypes.Content.CookieSettings.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/NodeTypes.Content.CookieSettings.yaml b/Configuration/NodeTypes.Content.CookieSettings.yaml index 562025f..fe57a75 100644 --- a/Configuration/NodeTypes.Content.CookieSettings.yaml +++ b/Configuration/NodeTypes.Content.CookieSettings.yaml @@ -125,7 +125,7 @@ group: cookieSettings decisionTtl: type: integer - defaultValue: 31536000 + defaultValue: 0 ui: label: i18n help: