From dccd9d09e5d4d19d3a4fa3811ae8966736d48502 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Sat, 2 May 2026 15:50:54 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20cookie=20banner=20compliance=20hardenin?= =?UTF-8?q?g=20=E2=80=94=205=20legal=20requirements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Impressum link mandatory in banner (§5 TMG) 2. Pre-ticked prevention: only "required" categories pre-enabled (Planet49) 3. Cookie-Settings reopen link (§7(3) DSGVO — revocation as easy as consent) 4. Script-Blocking: data-cookie-category + type="text/plain" pattern Scripts only execute AFTER user consents to that category 5. Buttons already equal size (flex:1) — verified correct Co-Authored-By: Claude Opus 4.6 (1M context) --- .../generator/cookie-banner-embed.ts | 70 +++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/admin-compliance/lib/sdk/einwilligungen/generator/cookie-banner-embed.ts b/admin-compliance/lib/sdk/einwilligungen/generator/cookie-banner-embed.ts index 6b553d7..b2fdcdf 100644 --- a/admin-compliance/lib/sdk/einwilligungen/generator/cookie-banner-embed.ts +++ b/admin-compliance/lib/sdk/einwilligungen/generator/cookie-banner-embed.ts @@ -245,13 +245,16 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str const categoriesHTML = config.categories .map((cat) => { const isRequired = cat.isRequired + // COMPLIANCE: Only "required" categories may be pre-enabled (EuGH Planet49) + // Non-required categories must NEVER be defaultEnabled + const isEnabled = isRequired ? true : false return ` @@ -286,10 +289,22 @@ function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): str - - ${config.texts.privacyPolicyLink.de} - + + + + + Cookie-Einstellungen + `.trim() } @@ -397,6 +412,31 @@ function generateJS(config: CookieBannerConfig): string { overlay?.classList.remove('active'); } + // Script-Blocking: activate scripts with data-cookie-category ONLY after consent + function activateConsentedScripts() { + const consent = getConsent(); + if (!consent) return; + + // Find all blocked scripts (type="text/plain" with data-cookie-category) + document.querySelectorAll('script[data-cookie-category][type="text/plain"]').forEach(script => { + const category = script.getAttribute('data-cookie-category'); + if (consent[category] === true) { + // Replace type to activate the script + const newScript = document.createElement('script'); + if (script.src) newScript.src = script.src; + else newScript.textContent = script.textContent; + newScript.type = 'text/javascript'; + script.parentNode.replaceChild(newScript, script); + } + }); + + // Also fire custom event for programmatic listeners + window.dispatchEvent(new CustomEvent('cookieConsentActivated', { detail: consent })); + } + + // Run script activation after consent is saved + window.addEventListener('cookieConsentUpdated', activateConsentedScripts); + window.CookieConsent = { getConsent, saveConsent, @@ -405,14 +445,32 @@ function generateJS(config: CookieBannerConfig): string { document.getElementById('cookieBanner')?.classList.add('active'); document.getElementById('cookieBannerOverlay')?.classList.add('active'); }, - hide: closeBanner + hide: closeBanner, + activateScripts: activateConsentedScripts, }; if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initBanner); + document.addEventListener('DOMContentLoaded', () => { + initBanner(); + activateConsentedScripts(); + }); } else { initBanner(); + activateConsentedScripts(); } })(); + +/* + * USAGE: Script-Blocking + * + * Instead of: + * + * + * Use: + * + * + * The script will only execute AFTER the user consents to "statistics". + */ `.trim() }