From 4608b0a28722291dd0faeb21c4a36e17f1427333 Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Wed, 11 Feb 2026 15:48:09 +0100 Subject: [PATCH 1/4] reload fixes (#457) * security fixes and theme preview * securit fix replace * Update CHANGELOG.md * 5.3.3 * namespace guide entfernt * fixed reload * Bump version to 5.3.4 and fix various issues Updated version to 5.3.4 and fixed multiple issues including JSON parsing errors, framework template loading, and security vulnerabilities. * 5.3.6 * alle laden inline via session erlauben * Fix iOS Safari touch events (thx @alexwenz) and update changelog * Address review comments: add missing translations and secure host usage --- CHANGELOG.md | 9 +++++ assets/consent_inline.js | 56 +++++++++++++++++++++----- assets/consent_manager_frontend.js | 3 +- assets/consent_manager_frontend.min.js | 2 +- inline.md | 11 +++++ lang/de_de.lang | 4 ++ lang/en_gb.lang | 5 +++ lang/sv_se.lang | 5 +++ lib/Frontend.php | 8 ++-- lib/InlineConsent.php | 7 +++- package.yml | 2 +- pages/config.php | 6 +++ 12 files changed, 101 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 555b7fc0..13d490f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # REDAXO consent_manager - Changelog +## Version 5.4.0 - 11.02.2026 + +- **Feature:** Inline-Consent kann nun optional auf "Session-Scope" beschränkt werden. Zustimmungen gelten dann nur, solange der Browser-Tab offen ist (via `sessionStorage`). Konfigurierbar unter Einstellungen. +- **Fix:** Reload-Loop behoben: Das Öffnen der Details aus einem Inline-Element führte unter Umständen zu einem sofortigen Neuladen der Seite. +- **Fix:** iOS Safari Touch-Event Handling verbessert: Button musste unter Umständen doppelt getippt werden; nun reagiert er sofort (Danke @alexwenz). +- **System:** Build-Skript aktualisiert für bessere Minifizierung. + + + ## Version 5.3.4 - 29.01.2026 - **Fix:** JSON Parsing Fehler im Frontend behoben (`double-escaping` von HTML-Attributen entfernt), was zu Fehlern beim Laden der Cookie-Gruppen führte (`safeJSONParse failed`). diff --git a/assets/consent_inline.js b/assets/consent_inline.js index 3182c1fd..17f46d36 100644 --- a/assets/consent_inline.js +++ b/assets/consent_inline.js @@ -47,12 +47,12 @@ if (typeof window.consentManagerInline !== 'undefined') { }); }); - // Cookie-Änderungen überwachen - var lastCookieValue = self.getCookie('consentmanager'); + // Cookie/Storage-Änderungen überwachen + var lastStorageValue = self.getStorageValue(); setInterval(function() { - var currentCookieValue = self.getCookie('consentmanager'); - if (currentCookieValue !== lastCookieValue) { - lastCookieValue = currentCookieValue; + var currentStorageValue = self.getStorageValue(); + if (currentStorageValue !== lastStorageValue) { + lastStorageValue = currentStorageValue; self.updateAllPlaceholders(); } }, 1000); @@ -83,8 +83,8 @@ if (typeof window.consentManagerInline !== 'undefined') { }); } - // Event-Handler für Buttons mit spezifischer Priorität - document.addEventListener('click', function(e) { + // Helper function for button click handling (unified for click and touchstart) + var handleButtonClick = function(e) { // Eindeutig nur "Einmal laden" Button - Lädt NUR diesen einen Container if (e.target.matches('.consent-inline-once') && !e.target.matches('.consent-inline-allow-all')) { e.preventDefault(); @@ -115,7 +115,18 @@ if (typeof window.consentManagerInline !== 'undefined') { self.showDetails(serviceKey); return; } - }); + }; + + // Event-Handler für Buttons mit spezifischer Priorität + document.addEventListener('click', handleButtonClick); + + // iOS Safari Fix: touchend statt touchstart verwenden + // touchend verhindert Hover-State und triggert sofort + document.addEventListener('touchend', function(e) { + if (e.target.matches('.consent-inline-once, .consent-inline-allow-all, .consent-inline-details')) { + handleButtonClick(e); + } + }, { passive: false }); // Fallback: Regelmäßige Prüfung setInterval(function() { @@ -382,7 +393,7 @@ if (typeof window.consentManagerInline !== 'undefined') { } catch (e) { // ignore and fallback to default } - var cookieValue = this.getCookie('consentmanager'); + var cookieValue = this.getStorageValue(); if (!cookieValue) { return { @@ -508,6 +519,13 @@ if (typeof window.consentManagerInline !== 'undefined') { }, setCookieData: function(data) { + if (this.isSessionScope()) { + try { + sessionStorage.setItem('consentmanager', JSON.stringify(data)); + } catch(e) { /* ignore */ } + return; + } + // Vor dem Setzen: alte / invalide Cookies entfernen var shouldClear = false; try { @@ -544,6 +562,26 @@ if (typeof window.consentManagerInline !== 'undefined') { '; path=/; SameSite=Lax'; }, + isSessionScope: function() { + try { + return (typeof window.consentManagerInlineOptions !== 'undefined' && + window.consentManagerInlineOptions.sessionScope === true); + } catch(e) { + return false; + } + }, + + getStorageValue: function() { + if (this.isSessionScope()) { + try { + return sessionStorage.getItem('consentmanager'); + } catch(e) { + return null; + } + } + return this.getCookie('consentmanager'); + }, + getCookie: function(name) { var value = '; ' + document.cookie; var parts = value.split('; ' + name + '='); diff --git a/assets/consent_manager_frontend.js b/assets/consent_manager_frontend.js index abba2539..1f1abb61 100644 --- a/assets/consent_manager_frontend.js +++ b/assets/consent_manager_frontend.js @@ -291,7 +291,8 @@ function safeJSONParse(input, fallback) { if (actions.indexOf('reload') !== -1) { // Warte auf Box-Close und reload dann var checkClose = setInterval(function() { - if (!document.querySelector('.consent_manager-box')) { + var box = document.getElementById('consent_manager-background'); + if (!box || box.classList.contains('consent_manager-hidden')) { clearInterval(checkClose); location.reload(); } diff --git a/assets/consent_manager_frontend.min.js b/assets/consent_manager_frontend.min.js index 31dba73c..f7dcd871 100644 --- a/assets/consent_manager_frontend.min.js +++ b/assets/consent_manager_frontend.min.js @@ -1 +1 @@ -const cmCookieSameSite = consent_manager_parameters.cookieSameSite || 'Lax';const cmCookieSecure = consent_manager_parameters.cookieSecure || false;const cmCookieName = consent_manager_parameters.cookieName || 'consentmanager';const cmCookieAPI = Cookies.withAttributes({expires: cmCookieExpires,path: '/',sameSite: cmCookieSameSite,secure: cmCookieSecure});if(window.consentManagerDebugConfig && window.consentManagerDebugConfig.debug_enabled){console.log('Consent Manager: Script loaded');}function debugLog(message,data){if(window.consentManagerDebugConfig && window.consentManagerDebugConfig.debug_enabled){if(data !== undefined){console.log('Consent Manager: ' + message,data);}else{console.log('Consent Manager: ' + message);}}}function safeJSONParse(input,fallback){try{if(typeof input === 'string')return JSON.parse(input);if(typeof input === 'object' && input !== null)return input;}catch(e){console.warn('consent_manager: safeJSONParse failed for input',input,e);}return fallback;}(function(){'use strict';var show = 0,cookieData ={},consents = [],addonVersion = -1,cachelogid = -1,cookieVersion = -1,cookieCachelogid = -1,consent_managerBox;consent_manager_parameters.no_cookie_set = false;var autoInjectOptions = window.consentManagerOptions ||{};var reloadOnConsent = autoInjectOptions.reloadOnConsent || false;var showDelay = parseInt(autoInjectOptions.showDelay,10)|| 0;var autoFocus = autoInjectOptions.autoFocus !== false;if(typeof cmCookieAPI.get(cmCookieName)=== 'undefined'){cmCookieAPI.set(cmCookieName + '_test','test');if(typeof cmCookieAPI.get(cmCookieName + '_test')=== 'undefined'){show = 0;consent_manager_parameters.no_cookie_set = true;console.warn('Addon consent_manager: Es konnte kein Cookie für die Domain ' + consent_manager_parameters.domain + ' gesetzt werden!');}else{cmCookieAPI.remove(cmCookieName + '_test');show = 1;}}else{cookieData = safeJSONParse(cmCookieAPI.get(cmCookieName),{});if(cookieData.hasOwnProperty('version')){consents = cookieData.consents;cookieVersion = parseInt(cookieData.version);cookieCachelogid = parseInt(cookieData.cachelogid);}}if(consent_manager_box_template === ''){console.warn('Addon consent_manager: Keine Cookie-Gruppen / Cookies ausgewählt bzw. keine Domain zugewiesen!(' + location.hostname + ')');return;}consent_managerBox = new DOMParser().parseFromString(consent_manager_box_template,'text/html');consent_managerBox = consent_managerBox.getElementById('consent_manager-background');document.querySelectorAll('body')[0].appendChild(consent_managerBox);addonVersion = parseInt(consent_manager_parameters.version);cachelogid = parseInt(consent_manager_parameters.cachelogid);if(isNaN(cookieVersion)|| cookieVersion !== addonVersion || cachelogid !== cookieCachelogid){show = 1;consents = [];deleteCookies();}debugLog('Startup: Triggering scripts for enabled consents',consents);consents.forEach(function(uid){debugLog('Startup: Processing consent UID',uid);var scriptElement = consent_managerBox.querySelector('[data-uid="script-' + uid + '"]');var unselectElement = consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]');debugLog('Startup: Elements found',{scriptElement: !!scriptElement,unselectElement: !!unselectElement});addScript(scriptElement);removeScript(unselectElement);});if(consents.length > 0 && typeof window.GoogleConsentModeV2 !== 'undefined' && typeof window.GoogleConsentModeV2.setConsent === 'function'){var googleConsentFlags = mapConsentsToGoogleFlags(consents);debugLog('Auto-mapping Google Consent Mode flags',consents,googleConsentFlags);window.GoogleConsentModeV2.setConsent(googleConsentFlags);}else{debugLog('Auto-mapping skipped',{consents: consents,hasGCM: typeof window.GoogleConsentModeV2 !== 'undefined',hasSetConsent: typeof window.GoogleConsentModeV2?.setConsent === 'function'});}consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);var consentsSet = new Set(consents);cookieUids.forEach(function(uid){if(!consentsSet.has(uid)){removeScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));addScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));}});});if(consent_manager_parameters.initially_hidden || consent_manager_parameters.no_cookie_set){show = 0;}var dontShowElements = document.querySelectorAll('[data-consent-action]');var hasDontShow = false;for(var i = 0;i < dontShowElements.length;i++){var actions =(dontShowElements[i].getAttribute('data-consent-action')|| '').split(',').map(function(a){return a.trim();});if(actions.indexOf('dontshow')!== -1){hasDontShow = true;debugLog('Found element with dontshow flag,suppressing automatic box display');break;}}if(show && !hasDontShow){if(showDelay > 0){setTimeout(function(){showBox();},showDelay * 1000);}else{showBox();}}consent_managerBox.querySelectorAll('.consent_manager-close').forEach(function(el){el.addEventListener('click',function(){if(el.classList.contains('consent_manager-save-selection')){saveConsent('selection');}else if(el.classList.contains('consent_manager-accept-all')){saveConsent('all');}else if(el.classList.contains('consent_manager-accept-none')){saveConsent('none');}else if(el.classList.contains('consent_manager-close')){if(!document.getElementById('consent_manager-detail').classList.contains('consent_manager-hidden')){document.getElementById('consent_manager-detail').classList.toggle('consent_manager-hidden');}}if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'auto';}document.getElementById('consent_manager-background').classList.add('consent_manager-hidden');document.getElementById('consent_manager-background').setAttribute('aria-hidden','true');document.dispatchEvent(new CustomEvent('consent_manager-close'));return false;});});var toggleDetailsBtn = document.getElementById('consent_manager-toggle-details');if(toggleDetailsBtn){var detailElement = document.getElementById('consent_manager-detail');var toggleDetails = function(){detailElement.classList.toggle('consent_manager-hidden');var isExpanded = !detailElement.classList.contains('consent_manager-hidden');toggleDetailsBtn.setAttribute('aria-expanded',isExpanded);};toggleDetailsBtn.addEventListener('click',function(){toggleDetails();return false;});toggleDetailsBtn.addEventListener('keydown',function(event){if(event.key === 'Enter' || event.key === ' '){event.preventDefault();toggleDetails();return false;}});}document.addEventListener('keydown',function(event){var consentBox = document.getElementById('consent_manager-background');if(!consentBox || consentBox.classList.contains('consent_manager-hidden')){return;}if(event.key === 'Escape' || event.key === 'Esc'){event.preventDefault();event.stopPropagation();if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'auto';}consentBox.classList.add('consent_manager-hidden');consentBox.setAttribute('aria-hidden','true');document.dispatchEvent(new CustomEvent('consent_manager-close'));return;}if(event.key === 'Tab'){var wrapper = document.getElementById('consent_manager-wrapper');if(!wrapper || !wrapper.contains(document.activeElement)){return;}var focusableElements = wrapper.querySelectorAll('button:not([disabled]),input:not([disabled]),a[href],[tabindex]:not([tabindex="-1"])');var focusableArray = Array.from(focusableElements);var firstFocusable = focusableArray[0];var lastFocusable = focusableArray[focusableArray.length - 1];if(event.shiftKey){if(document.activeElement === firstFocusable){event.preventDefault();event.stopPropagation();lastFocusable.focus();}}else{if(document.activeElement === lastFocusable){event.preventDefault();event.stopPropagation();firstFocusable.focus();}}}},true);document.addEventListener('click',function(e){var target = e.target;while(target && target !== document){if(target.classList &&(target.classList.contains('consent_manager-show-box')|| target.classList.contains('consent_manager-show-box-reload'))){e.preventDefault();showBox();return false;}var consentAction = target.getAttribute('data-consent-action');if(target.tagName === 'A' && consentAction){var actions = consentAction.split(',').map(function(a){return a.trim();});if(actions.indexOf('settings')!== -1){e.preventDefault();consent_manager_showBox();if(actions.indexOf('reload')!== -1){var checkClose = setInterval(function(){if(!document.querySelector('.consent_manager-box')){clearInterval(checkClose);location.reload();}},100);}return false;}}target = target.parentElement;}});function saveConsent(toSave){debugLog('saveConsent: Start',toSave);if(!consent_managerBox){console.warn('Consent Manager: saveConsent called but consent_managerBox not initialized');return;}consents = [];var consentsSet = new Set();var hasOptionalConsent = false;cookieData ={consents: [],version: addonVersion,consentid: consent_manager_parameters.consentid,cachelogid: consent_manager_parameters.cachelogid};if(toSave !== 'none'){consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);if(el.checked || toSave === 'all'){if(!el.disabled){hasOptionalConsent = true;}debugLog('saveConsent: Consent erteilt für',cookieUids);cookieUids.forEach(function(uid){consents.push(uid);consentsSet.add(uid);debugLog('saveConsent: Führe Script aus für UID',uid);var scriptElement = consent_managerBox.querySelector('[data-uid="script-' + uid + '"]');var unselectElement = consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]');debugLog('saveConsent: Elements gefunden',{scriptElement: !!scriptElement,unselectElement: !!unselectElement,hasDataScript: scriptElement ? !!scriptElement.getAttribute('data-script'): false});addScript(scriptElement);removeScript(unselectElement);});}else{debugLog('saveConsent: Consent verweigert für',cookieUids);cookieUids.forEach(function(uid){removeScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));addScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));});}});}else{debugLog('saveConsent: Keine Consents(none)');consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);if(el.disabled){cookieUids.forEach(function(uid){consents.push(uid);addScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));removeScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));});}else{el.checked = false;cookieUids.forEach(function(uid){removeScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));addScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));});}});}cookieData.consents = consents;debugLog('saveConsent: Finale Consents',consents);try{deleteCookies();}catch(e){console.warn('Consent Manager: deleteCookies()failed before setting cookie',e);}if(!hasOptionalConsent){debugLog('saveConsent: Minimal consent only - setting short cookie lifetime(14 days)');cmCookieAPI.set(cmCookieName,JSON.stringify(cookieData),{expires: 14});}else{cmCookieAPI.set(cmCookieName,JSON.stringify(cookieData));}if(typeof window.GoogleConsentModeV2 !== 'undefined' && typeof window.GoogleConsentModeV2.setConsent === 'function'){var googleConsentFlags = mapConsentsToGoogleFlags(consents);debugLog('Mapping consents to Google flags',consents,googleConsentFlags);window.GoogleConsentModeV2.setConsent(googleConsentFlags);}else{debugLog('Google Consent Mode not available for mapping');}if(typeof cmCookieAPI.get(cmCookieName)=== 'undefined'){consent_manager_parameters.no_cookie_set = true;console.warn('Addon consent_manager: Es konnte kein Cookie für die Domain ' + document.domain + ' gesetzt werden!');}else{var url = consent_manager_parameters.fe_controller + '?rex-api-call=consent_manager&buster=' + new Date().getTime();var params = 'domain=' + encodeURIComponent(document.domain)+ '&consentid=' + encodeURIComponent(consent_manager_parameters.consentid)+ '&buster=' + new Date().getTime();fetch(url,{method: 'POST',headers:{'Content-Type': 'application/x-www-form-urlencoded','Cache-Control': 'no-cache,no-store,max-age=0'},body: params}).catch(function(error){console.error('Addon consent_manager: Fehler beim speichern des Consent!',error);debugLog('Consent logging failed',error);});}if(document.querySelectorAll('.consent_manager-show-box-reload').length || consent_manager_parameters.forcereload === 1 || reloadOnConsent){location.reload();}else{document.dispatchEvent(new CustomEvent('consent_manager-saved',{detail: JSON.stringify(consents)}));}}function deleteCookies(){var domain = consent_manager_parameters.domain;var hostname = window.location.hostname;var cookieNamesToDelete = ['consent_manager','consent_manager_test','consentmanager','consentmanager_test',cmCookieName,cmCookieName + '_test'];cookieNamesToDelete.forEach(function(name){Cookies.remove(name);Cookies.remove(name,{'path': '/'});Cookies.remove(name,{'domain': domain});Cookies.remove(name,{'domain': domain,'path': '/'});Cookies.remove(name,{'domain':('.' + domain)});Cookies.remove(name,{'domain':('.' + domain),'path': '/'});if(hostname !== domain){Cookies.remove(name,{'domain': hostname});Cookies.remove(name,{'domain': hostname,'path': '/'});Cookies.remove(name,{'domain':('.' + hostname)});Cookies.remove(name,{'domain':('.' + hostname),'path': '/'});}});}function addScript(el){if(!el){debugLog('addScript: Element ist null/undefined');return;}var uid = el.getAttribute('data-uid')|| 'unknown';debugLog('addScript: Processing element',{uid: uid,element: el});if(!el.children.length){var encodedScript = el.getAttribute('data-script');debugLog('addScript: Encoded script data',{length: encodedScript ? encodedScript.length : 0,preview: encodedScript ? encodedScript.substring(0,50)+ '...' : 'empty'});if(!encodedScript){debugLog('addScript: Kein data-script Attribut gefunden');return;}var scriptContent = '';try{scriptContent = window.atob(encodedScript);debugLog('addScript: Script erfolgreich dekodiert',{length: scriptContent.length,preview: scriptContent.substring(0,100)});}catch(e){console.error('addScript: Fehler beim Base64-Dekodieren',e);debugLog('addScript: Base64-Dekodierung fehlgeschlagen',e);return;}if(!scriptContent){debugLog('addScript: Script-Inhalt ist leer nach Dekodierung');return;}if(scriptContent.includes('\ufffd')|| scriptContent.includes('�')){console.error('addScript: Script enthält ungültige Zeichen(fehlerhaftes Encoding)',{uid: uid,preview: scriptContent.substring(0,100)});debugLog('addScript: Ungültiges Encoding erkannt,überspringe Script');return;}var scriptDom = null;try{scriptDom = new DOMParser().parseFromString(scriptContent,'text/html');debugLog('addScript: DOM geparst,Scripts gefunden:',scriptDom.scripts.length);}catch(e){console.error('addScript: Fehler beim Parsen des HTML',e);debugLog('addScript: HTML-Parsing fehlgeschlagen',e);return;}if(!scriptDom || !scriptDom.scripts || scriptDom.scripts.length === 0){debugLog('addScript: Keine Scripts im geparsten DOM gefunden');return;}var nonce = consent_manager_parameters.cspNonce || null;for(var i = 0;i < scriptDom.scripts.length;i++){var originalScript = scriptDom.scripts[i];var scriptNode = document.createElement('script');debugLog('addScript: Processing script #' +(i + 1),{hasSrc: !!originalScript.src,src: originalScript.src || 'inline',hasContent: !!originalScript.textContent,contentLength: originalScript.textContent ? originalScript.textContent.length : 0,attributes: originalScript.attributes.length});if(nonce){scriptNode.setAttribute('nonce',nonce);debugLog('addScript: Nonce gesetzt',nonce);}for(var j = 0;j < originalScript.attributes.length;j++){var attr = originalScript.attributes[j];if(attr.name !== 'nonce'){scriptNode.setAttribute(attr.name,attr.value);debugLog('addScript: Attribut kopiert',{name: attr.name,value: attr.value});}}if(originalScript.src){var scriptSrc = originalScript.src;var existingScript = document.querySelector('script[src="' + scriptSrc + '"]');if(existingScript){debugLog('addScript: Script bereits geladen,überspringe',scriptSrc);console.warn('Consent Manager: Script bereits geladen - Duplikat verhindert: ' + scriptSrc);continue;}scriptNode.src = scriptSrc;debugLog('addScript: External script wird geladen',scriptSrc);}else{var inlineContent = originalScript.textContent;if(inlineContent){scriptNode.textContent = inlineContent;debugLog('addScript: Inline script gesetzt',{contentLength: inlineContent.length,preview: inlineContent.substring(0,100)});}else{debugLog('addScript: Inline script ist leer');continue;}}try{document.body.appendChild(scriptNode);debugLog('addScript: Script erfolgreich zum DOM hinzugefügt');}catch(e){var errorInfo ={error: e.message,uid: uid,hasSrc: !!scriptNode.src,hasContent: !!scriptNode.textContent,src: scriptNode.src || 'inline'};if(e.message &&(e.message.includes('Content Security Policy')|| e.message.includes('CSP'))){errorInfo.hint = 'CSP violation - check Content-Security-Policy header';}else if(scriptNode.src && e.message && e.message.includes('CORS')){errorInfo.hint = 'CORS error - external script may be blocked';}console.error('addScript: Fehler beim Hinzufügen des Scripts',errorInfo);debugLog('addScript: Fehler beim appendChild',e);}}}else{debugLog('addScript: Element hat bereits children,wird übersprungen');}}function removeScript(el){if(!el){return;}el.innerHTML = '';}function showBox(){if(!consent_managerBox){console.warn('Consent Manager: showBox called but consent_managerBox not initialized');return;}var consentsSet = new Set(consents);consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);var check = cookieUids.every(function(uid){return consentsSet.has(uid);});if(check){el.checked = true;}});if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'hidden';}var consentBg = document.getElementById('consent_manager-background');consentBg.classList.remove('consent_manager-hidden');consentBg.setAttribute('aria-hidden','false');var toggleBtn = document.getElementById('consent_manager-toggle-details');if(toggleBtn){toggleBtn.setAttribute('aria-expanded','false');}var dialogWrapper = document.getElementById('consent_manager-wrapper');if(dialogWrapper && autoFocus){setTimeout(function(){dialogWrapper.focus();},100);}document.dispatchEvent(new CustomEvent('consent_manager-show'));}})();function mapConsentsToGoogleFlags(consents){var flags ={'ad_storage': false,'ad_user_data': false,'ad_personalization': false,'analytics_storage': false,'personalization_storage': false,'functionality_storage': false,'security_storage': false};consents.forEach(function(uid){var lowerUid = uid.toLowerCase();debugLog('Mapping UID',uid,lowerUid);if(lowerUid === 'analytics'){flags['analytics_storage'] = true;debugLog('Mapped analytics to analytics_storage');}if(lowerUid === 'marketing'){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;debugLog('Mapped marketing to ad_*');}if(lowerUid === 'functional'){flags['functionality_storage'] = true;debugLog('Mapped functional to functionality_storage');}if(lowerUid === 'preferences'){flags['personalization_storage'] = true;debugLog('Mapped preferences to personalization_storage');}if(lowerUid === 'necessary'){flags['security_storage'] = true;debugLog('Mapped necessary to security_storage');}if(lowerUid.includes('google-analytics')|| lowerUid.includes('analytics')|| lowerUid.includes('ga')){flags['analytics_storage'] = true;}if(lowerUid.includes('google-tag-manager')|| lowerUid.includes('gtm')|| lowerUid.includes('tag-manager')){flags['analytics_storage'] = true;flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('google-ads')|| lowerUid.includes('adwords')|| lowerUid.includes('google-adwords')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('facebook-pixel')|| lowerUid.includes('facebook')|| lowerUid.includes('meta-pixel')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('youtube')|| lowerUid.includes('yt')){flags['ad_storage'] = true;flags['personalization_storage'] = true;}if(lowerUid.includes('google-maps')|| lowerUid.includes('maps')|| lowerUid.includes('gmaps')){flags['functionality_storage'] = true;flags['personalization_storage'] = true;}if(lowerUid.includes('matomo')|| lowerUid.includes('piwik')){flags['analytics_storage'] = true;}if(lowerUid.includes('hotjar')){flags['analytics_storage'] = true;}if(lowerUid.includes('microsoft-clarity')|| lowerUid.includes('clarity')){flags['analytics_storage'] = true;}if(lowerUid.includes('linkedin')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('tiktok')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('pinterest')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('booking')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('hubspot')){flags['analytics_storage'] = true;flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('whatsapp')){flags['functionality_storage'] = true;}});debugLog('Final mapped flags',flags);return flags;}function consent_manager_showBox(){var consentBox = document.getElementById('consent_manager-background');if(!consentBox){console.warn('Consent Manager: Consent box element not found in DOM');return;}var consents = [];var cookieValue = cmCookieAPI.get(cmCookieName);if(typeof cookieValue !== 'undefined'){var cookieData = safeJSONParse(cookieValue,{});if(cookieData.hasOwnProperty('version')){consents = cookieData.consents;}}var consentsSet = new Set(consents);consentBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);var check = cookieUids.every(function(uid){return consentsSet.has(uid);});if(check){el.checked = true;}});if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'hidden';}consentBox.classList.remove('consent_manager-hidden');consentBox.setAttribute('aria-hidden','false');var dialogWrapper = document.getElementById('consent_manager-wrapper');if(dialogWrapper){setTimeout(function(){dialogWrapper.focus();},100);}}function consent_manager_hasconsent(id){var cookieValue = cmCookieAPI.get(cmCookieName);if(typeof cookieValue === 'undefined'){return false;}var cookieData = safeJSONParse(cookieValue,{consents: []});return cookieData.consents.indexOf(id)!== -1;} \ No newline at end of file +var cmCookieSameSite = consent_manager_parameters.cookieSameSite || 'Lax';var cmCookieSecure = consent_manager_parameters.cookieSecure || false;var cmCookieName = consent_manager_parameters.cookieName || 'consentmanager';var cmCookieAPI = Cookies.withAttributes({expires: cmCookieExpires,path: '/',sameSite: cmCookieSameSite,secure: cmCookieSecure});if(window.consentManagerDebugConfig && window.consentManagerDebugConfig.debug_enabled){console.log('Consent Manager: Script loaded');}function debugLog(message,data){if(window.consentManagerDebugConfig && window.consentManagerDebugConfig.debug_enabled){if(data !== undefined){console.log('Consent Manager: ' + message,data);}else{console.log('Consent Manager: ' + message);}}}function safeJSONParse(input,fallback){try{if(typeof input === 'string')return JSON.parse(input);if(typeof input === 'object' && input !== null)return input;}catch(e){console.warn('consent_manager: safeJSONParse failed for input',input,e);}return fallback;}(function(){'use strict';var show = 0,cookieData ={},consents = [],addonVersion = -1,cachelogid = -1,cookieVersion = -1,cookieCachelogid = -1,consent_managerBox;consent_manager_parameters.no_cookie_set = false;var autoInjectOptions = window.consentManagerOptions ||{};var reloadOnConsent = autoInjectOptions.reloadOnConsent || false;var showDelay = parseInt(autoInjectOptions.showDelay,10)|| 0;var autoFocus = autoInjectOptions.autoFocus !== false;if(typeof cmCookieAPI.get(cmCookieName)=== 'undefined'){cmCookieAPI.set(cmCookieName + '_test','test');if(typeof cmCookieAPI.get(cmCookieName + '_test')=== 'undefined'){show = 0;consent_manager_parameters.no_cookie_set = true;console.warn('Addon consent_manager: Es konnte kein Cookie für die Domain ' + consent_manager_parameters.domain + ' gesetzt werden!');}else{cmCookieAPI.remove(cmCookieName + '_test');show = 1;}}else{cookieData = safeJSONParse(cmCookieAPI.get(cmCookieName),{});if(cookieData.hasOwnProperty('version')){consents = cookieData.consents;cookieVersion = parseInt(cookieData.version);cookieCachelogid = parseInt(cookieData.cachelogid);}}if(consent_manager_box_template === ''){console.warn('Addon consent_manager: Keine Cookie-Gruppen / Cookies ausgewählt bzw. keine Domain zugewiesen!(' + location.hostname + ')');return;}consent_managerBox = new DOMParser().parseFromString(consent_manager_box_template,'text/html');consent_managerBox = consent_managerBox.getElementById('consent_manager-background');document.querySelectorAll('body')[0].appendChild(consent_managerBox);addonVersion = parseInt(consent_manager_parameters.version);cachelogid = parseInt(consent_manager_parameters.cachelogid);if(isNaN(cookieVersion)|| cookieVersion !== addonVersion || cachelogid !== cookieCachelogid){show = 1;consents = [];deleteCookies();}debugLog('Startup: Triggering scripts for enabled consents',consents);consents.forEach(function(uid){debugLog('Startup: Processing consent UID',uid);var scriptElement = consent_managerBox.querySelector('[data-uid="script-' + uid + '"]');var unselectElement = consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]');debugLog('Startup: Elements found',{scriptElement: !!scriptElement,unselectElement: !!unselectElement});addScript(scriptElement);removeScript(unselectElement);});if(consents.length > 0 && typeof window.GoogleConsentModeV2 !== 'undefined' && typeof window.GoogleConsentModeV2.setConsent === 'function'){var googleConsentFlags = mapConsentsToGoogleFlags(consents);debugLog('Auto-mapping Google Consent Mode flags',consents,googleConsentFlags);window.GoogleConsentModeV2.setConsent(googleConsentFlags);}else{debugLog('Auto-mapping skipped',{consents: consents,hasGCM: typeof window.GoogleConsentModeV2 !== 'undefined',hasSetConsent: typeof window.GoogleConsentModeV2?.setConsent === 'function'});}consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);var consentsSet = new Set(consents);cookieUids.forEach(function(uid){if(!consentsSet.has(uid)){removeScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));addScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));}});});if(consent_manager_parameters.initially_hidden || consent_manager_parameters.no_cookie_set){show = 0;}var dontShowElements = document.querySelectorAll('[data-consent-action]');var hasDontShow = false;for(var i = 0;i < dontShowElements.length;i++){var actions =(dontShowElements[i].getAttribute('data-consent-action')|| '').split(',').map(function(a){return a.trim();});if(actions.indexOf('dontshow')!== -1){hasDontShow = true;debugLog('Found element with dontshow flag,suppressing automatic box display');break;}}if(show && !hasDontShow){if(showDelay > 0){setTimeout(function(){showBox();},showDelay * 1000);}else{showBox();}}consent_managerBox.querySelectorAll('.consent_manager-close').forEach(function(el){el.addEventListener('click',function(){if(el.classList.contains('consent_manager-save-selection')){saveConsent('selection');}else if(el.classList.contains('consent_manager-accept-all')){saveConsent('all');}else if(el.classList.contains('consent_manager-accept-none')){saveConsent('none');}else if(el.classList.contains('consent_manager-close')){if(!document.getElementById('consent_manager-detail').classList.contains('consent_manager-hidden')){document.getElementById('consent_manager-detail').classList.toggle('consent_manager-hidden');}}if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'auto';}document.getElementById('consent_manager-background').classList.add('consent_manager-hidden');document.getElementById('consent_manager-background').setAttribute('aria-hidden','true');document.dispatchEvent(new CustomEvent('consent_manager-close'));return false;});});var toggleDetailsBtn = document.getElementById('consent_manager-toggle-details');if(toggleDetailsBtn){var detailElement = document.getElementById('consent_manager-detail');var toggleDetails = function(){detailElement.classList.toggle('consent_manager-hidden');var isExpanded = !detailElement.classList.contains('consent_manager-hidden');toggleDetailsBtn.setAttribute('aria-expanded',isExpanded);};toggleDetailsBtn.addEventListener('click',function(){toggleDetails();return false;});toggleDetailsBtn.addEventListener('keydown',function(event){if(event.key === 'Enter' || event.key === ' '){event.preventDefault();toggleDetails();return false;}});}document.addEventListener('keydown',function(event){var consentBox = document.getElementById('consent_manager-background');if(!consentBox || consentBox.classList.contains('consent_manager-hidden')){return;}if(event.key === 'Escape' || event.key === 'Esc'){event.preventDefault();event.stopPropagation();if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'auto';}consentBox.classList.add('consent_manager-hidden');consentBox.setAttribute('aria-hidden','true');document.dispatchEvent(new CustomEvent('consent_manager-close'));return;}if(event.key === 'Tab'){var wrapper = document.getElementById('consent_manager-wrapper');if(!wrapper || !wrapper.contains(document.activeElement)){return;}var focusableElements = wrapper.querySelectorAll('button:not([disabled]),input:not([disabled]),a[href],[tabindex]:not([tabindex="-1"])');var focusableArray = Array.from(focusableElements);var firstFocusable = focusableArray[0];var lastFocusable = focusableArray[focusableArray.length - 1];if(event.shiftKey){if(document.activeElement === firstFocusable){event.preventDefault();event.stopPropagation();lastFocusable.focus();}}else{if(document.activeElement === lastFocusable){event.preventDefault();event.stopPropagation();firstFocusable.focus();}}}},true);document.addEventListener('click',function(e){var target = e.target;while(target && target !== document){if(target.classList &&(target.classList.contains('consent_manager-show-box')|| target.classList.contains('consent_manager-show-box-reload'))){e.preventDefault();showBox();return false;}var consentAction = target.getAttribute('data-consent-action');if(target.tagName === 'A' && consentAction){var actions = consentAction.split(',').map(function(a){return a.trim();});if(actions.indexOf('settings')!== -1){e.preventDefault();consent_manager_showBox();if(actions.indexOf('reload')!== -1){var checkClose = setInterval(function(){var box = document.getElementById('consent_manager-background');if(!box || box.classList.contains('consent_manager-hidden')){clearInterval(checkClose);location.reload();}},100);}return false;}}target = target.parentElement;}});function saveConsent(toSave){debugLog('saveConsent: Start',toSave);if(!consent_managerBox){console.warn('Consent Manager: saveConsent called but consent_managerBox not initialized');return;}consents = [];var consentsSet = new Set();var hasOptionalConsent = false;cookieData ={consents: [],version: addonVersion,consentid: consent_manager_parameters.consentid,cachelogid: consent_manager_parameters.cachelogid};if(toSave !== 'none'){consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);if(el.checked || toSave === 'all'){if(!el.disabled){hasOptionalConsent = true;}debugLog('saveConsent: Consent erteilt für',cookieUids);cookieUids.forEach(function(uid){consents.push(uid);consentsSet.add(uid);debugLog('saveConsent: Führe Script aus für UID',uid);var scriptElement = consent_managerBox.querySelector('[data-uid="script-' + uid + '"]');var unselectElement = consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]');debugLog('saveConsent: Elements gefunden',{scriptElement: !!scriptElement,unselectElement: !!unselectElement,hasDataScript: scriptElement ? !!scriptElement.getAttribute('data-script'): false});addScript(scriptElement);removeScript(unselectElement);});}else{debugLog('saveConsent: Consent verweigert für',cookieUids);cookieUids.forEach(function(uid){removeScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));addScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));});}});}else{debugLog('saveConsent: Keine Consents(none)');consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);if(el.disabled){cookieUids.forEach(function(uid){consents.push(uid);addScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));removeScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));});}else{el.checked = false;cookieUids.forEach(function(uid){removeScript(consent_managerBox.querySelector('[data-uid="script-' + uid + '"]'));addScript(consent_managerBox.querySelector('[data-uid="script-unselect-' + uid + '"]'));});}});}cookieData.consents = consents;debugLog('saveConsent: Finale Consents',consents);try{deleteCookies();}catch(e){console.warn('Consent Manager: deleteCookies()failed before setting cookie',e);}if(!hasOptionalConsent){debugLog('saveConsent: Minimal consent only - setting short cookie lifetime(14 days)');cmCookieAPI.set(cmCookieName,JSON.stringify(cookieData),{expires: 14});}else{cmCookieAPI.set(cmCookieName,JSON.stringify(cookieData));}if(typeof window.GoogleConsentModeV2 !== 'undefined' && typeof window.GoogleConsentModeV2.setConsent === 'function'){var googleConsentFlags = mapConsentsToGoogleFlags(consents);debugLog('Mapping consents to Google flags',consents,googleConsentFlags);window.GoogleConsentModeV2.setConsent(googleConsentFlags);}else{debugLog('Google Consent Mode not available for mapping');}if(typeof cmCookieAPI.get(cmCookieName)=== 'undefined'){consent_manager_parameters.no_cookie_set = true;console.warn('Addon consent_manager: Es konnte kein Cookie für die Domain ' + document.domain + ' gesetzt werden!');}else{var url = consent_manager_parameters.fe_controller + '?rex-api-call=consent_manager&buster=' + new Date().getTime();var params = 'domain=' + encodeURIComponent(document.domain)+ '&consentid=' + encodeURIComponent(consent_manager_parameters.consentid)+ '&buster=' + new Date().getTime();fetch(url,{method: 'POST',headers:{'Content-Type': 'application/x-www-form-urlencoded','Cache-Control': 'no-cache,no-store,max-age=0'},body: params}).catch(function(error){console.error('Addon consent_manager: Fehler beim speichern des Consent!',error);debugLog('Consent logging failed',error);});}if(document.querySelectorAll('.consent_manager-show-box-reload').length || consent_manager_parameters.forcereload === 1 || reloadOnConsent){location.reload();}else{document.dispatchEvent(new CustomEvent('consent_manager-saved',{detail: JSON.stringify(consents)}));}}function deleteCookies(){var domain = consent_manager_parameters.domain;var hostname = window.location.hostname;var cookieNamesToDelete = ['consent_manager','consent_manager_test','consentmanager','consentmanager_test',cmCookieName,cmCookieName + '_test'];cookieNamesToDelete.forEach(function(name){Cookies.remove(name);Cookies.remove(name,{'path': '/'});Cookies.remove(name,{'domain': domain});Cookies.remove(name,{'domain': domain,'path': '/'});Cookies.remove(name,{'domain':('.' + domain)});Cookies.remove(name,{'domain':('.' + domain),'path': '/'});if(hostname !== domain){Cookies.remove(name,{'domain': hostname});Cookies.remove(name,{'domain': hostname,'path': '/'});Cookies.remove(name,{'domain':('.' + hostname)});Cookies.remove(name,{'domain':('.' + hostname),'path': '/'});}});}function addScript(el){if(!el){debugLog('addScript: Element ist null/undefined');return;}var uid = el.getAttribute('data-uid')|| 'unknown';debugLog('addScript: Processing element',{uid: uid,element: el});if(!el.children.length){var encodedScript = el.getAttribute('data-script');debugLog('addScript: Encoded script data',{length: encodedScript ? encodedScript.length : 0,preview: encodedScript ? encodedScript.substring(0,50)+ '...' : 'empty'});if(!encodedScript){debugLog('addScript: Kein data-script Attribut gefunden');return;}var scriptContent = '';try{scriptContent = window.atob(encodedScript);debugLog('addScript: Script erfolgreich dekodiert',{length: scriptContent.length,preview: scriptContent.substring(0,100)});}catch(e){console.error('addScript: Fehler beim Base64-Dekodieren',e);debugLog('addScript: Base64-Dekodierung fehlgeschlagen',e);return;}if(!scriptContent){debugLog('addScript: Script-Inhalt ist leer nach Dekodierung');return;}if(scriptContent.includes('\ufffd')|| scriptContent.includes('�')){console.error('addScript: Script enthält ungültige Zeichen(fehlerhaftes Encoding)',{uid: uid,preview: scriptContent.substring(0,100)});debugLog('addScript: Ungültiges Encoding erkannt,überspringe Script');return;}var scriptDom = null;try{scriptDom = new DOMParser().parseFromString(scriptContent,'text/html');debugLog('addScript: DOM geparst,Scripts gefunden:',scriptDom.scripts.length);}catch(e){console.error('addScript: Fehler beim Parsen des HTML',e);debugLog('addScript: HTML-Parsing fehlgeschlagen',e);return;}if(!scriptDom || !scriptDom.scripts || scriptDom.scripts.length === 0){debugLog('addScript: Keine Scripts im geparsten DOM gefunden');return;}var nonce = consent_manager_parameters.cspNonce || null;for(var i = 0;i < scriptDom.scripts.length;i++){var originalScript = scriptDom.scripts[i];var scriptNode = document.createElement('script');debugLog('addScript: Processing script #' +(i + 1),{hasSrc: !!originalScript.src,src: originalScript.src || 'inline',hasContent: !!originalScript.textContent,contentLength: originalScript.textContent ? originalScript.textContent.length : 0,attributes: originalScript.attributes.length});if(nonce){scriptNode.setAttribute('nonce',nonce);debugLog('addScript: Nonce gesetzt',nonce);}for(var j = 0;j < originalScript.attributes.length;j++){var attr = originalScript.attributes[j];if(attr.name !== 'nonce'){scriptNode.setAttribute(attr.name,attr.value);debugLog('addScript: Attribut kopiert',{name: attr.name,value: attr.value});}}if(originalScript.src){var scriptSrc = originalScript.src;var existingScript = document.querySelector('script[src="' + scriptSrc + '"]');if(existingScript){debugLog('addScript: Script bereits geladen,überspringe',scriptSrc);console.warn('Consent Manager: Script bereits geladen - Duplikat verhindert: ' + scriptSrc);continue;}scriptNode.src = scriptSrc;debugLog('addScript: External script wird geladen',scriptSrc);}else{var inlineContent = originalScript.textContent;if(inlineContent){scriptNode.textContent = inlineContent;debugLog('addScript: Inline script gesetzt',{contentLength: inlineContent.length,preview: inlineContent.substring(0,100)});}else{debugLog('addScript: Inline script ist leer');continue;}}try{document.body.appendChild(scriptNode);debugLog('addScript: Script erfolgreich zum DOM hinzugefügt');}catch(e){var errorInfo ={error: e.message,uid: uid,hasSrc: !!scriptNode.src,hasContent: !!scriptNode.textContent,src: scriptNode.src || 'inline'};if(e.message &&(e.message.includes('Content Security Policy')|| e.message.includes('CSP'))){errorInfo.hint = 'CSP violation - check Content-Security-Policy header';}else if(scriptNode.src && e.message && e.message.includes('CORS')){errorInfo.hint = 'CORS error - external script may be blocked';}console.error('addScript: Fehler beim Hinzufügen des Scripts',errorInfo);debugLog('addScript: Fehler beim appendChild',e);}}}else{debugLog('addScript: Element hat bereits children,wird übersprungen');}}function removeScript(el){if(!el){return;}el.innerHTML = '';}function showBox(){if(!consent_managerBox){console.warn('Consent Manager: showBox called but consent_managerBox not initialized');return;}var consentsSet = new Set(consents);consent_managerBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);var check = cookieUids.every(function(uid){return consentsSet.has(uid);});if(check){el.checked = true;}});if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'hidden';}var consentBg = document.getElementById('consent_manager-background');consentBg.classList.remove('consent_manager-hidden');consentBg.setAttribute('aria-hidden','false');var toggleBtn = document.getElementById('consent_manager-toggle-details');if(toggleBtn){toggleBtn.setAttribute('aria-expanded','false');}var dialogWrapper = document.getElementById('consent_manager-wrapper');if(dialogWrapper && autoFocus){setTimeout(function(){dialogWrapper.focus();},100);}document.dispatchEvent(new CustomEvent('consent_manager-show'));}})();function mapConsentsToGoogleFlags(consents){var flags ={'ad_storage': false,'ad_user_data': false,'ad_personalization': false,'analytics_storage': false,'personalization_storage': false,'functionality_storage': false,'security_storage': false};consents.forEach(function(uid){var lowerUid = uid.toLowerCase();debugLog('Mapping UID',uid,lowerUid);if(lowerUid === 'analytics'){flags['analytics_storage'] = true;debugLog('Mapped analytics to analytics_storage');}if(lowerUid === 'marketing'){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;debugLog('Mapped marketing to ad_*');}if(lowerUid === 'functional'){flags['functionality_storage'] = true;debugLog('Mapped functional to functionality_storage');}if(lowerUid === 'preferences'){flags['personalization_storage'] = true;debugLog('Mapped preferences to personalization_storage');}if(lowerUid === 'necessary'){flags['security_storage'] = true;debugLog('Mapped necessary to security_storage');}if(lowerUid.includes('google-analytics')|| lowerUid.includes('analytics')|| lowerUid.includes('ga')){flags['analytics_storage'] = true;}if(lowerUid.includes('google-tag-manager')|| lowerUid.includes('gtm')|| lowerUid.includes('tag-manager')){flags['analytics_storage'] = true;flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('google-ads')|| lowerUid.includes('adwords')|| lowerUid.includes('google-adwords')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('facebook-pixel')|| lowerUid.includes('facebook')|| lowerUid.includes('meta-pixel')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('youtube')|| lowerUid.includes('yt')){flags['ad_storage'] = true;flags['personalization_storage'] = true;}if(lowerUid.includes('google-maps')|| lowerUid.includes('maps')|| lowerUid.includes('gmaps')){flags['functionality_storage'] = true;flags['personalization_storage'] = true;}if(lowerUid.includes('matomo')|| lowerUid.includes('piwik')){flags['analytics_storage'] = true;}if(lowerUid.includes('hotjar')){flags['analytics_storage'] = true;}if(lowerUid.includes('microsoft-clarity')|| lowerUid.includes('clarity')){flags['analytics_storage'] = true;}if(lowerUid.includes('linkedin')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('tiktok')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('pinterest')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('booking')){flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('hubspot')){flags['analytics_storage'] = true;flags['ad_storage'] = true;flags['ad_user_data'] = true;flags['ad_personalization'] = true;}if(lowerUid.includes('whatsapp')){flags['functionality_storage'] = true;}});debugLog('Final mapped flags',flags);return flags;}function consent_manager_showBox(){var consentBox = document.getElementById('consent_manager-background');if(!consentBox){console.warn('Consent Manager: Consent box element not found in DOM');return;}var consents = [];var cookieValue = cmCookieAPI.get(cmCookieName);if(typeof cookieValue !== 'undefined'){var cookieData = safeJSONParse(cookieValue,{});if(cookieData.hasOwnProperty('version')){consents = cookieData.consents;}}var consentsSet = new Set(consents);consentBox.querySelectorAll('[data-cookie-uids]').forEach(function(el){var cookieUids = safeJSONParse(el.getAttribute('data-cookie-uids'),[]);var check = cookieUids.every(function(uid){return consentsSet.has(uid);});if(check){el.checked = true;}});if(consent_manager_parameters.hidebodyscrollbar){document.querySelector('body').style.overflow = 'hidden';}consentBox.classList.remove('consent_manager-hidden');consentBox.setAttribute('aria-hidden','false');var dialogWrapper = document.getElementById('consent_manager-wrapper');if(dialogWrapper){setTimeout(function(){dialogWrapper.focus();},100);}}function consent_manager_hasconsent(id){var cookieValue = cmCookieAPI.get(cmCookieName);if(typeof cookieValue === 'undefined'){return false;}var cookieData = safeJSONParse(cookieValue,{consents: []});return cookieData.consents.indexOf(id)!== -1;} \ No newline at end of file diff --git a/inline.md b/inline.md index 9e235ebc..9272e88a 100644 --- a/inline.md +++ b/inline.md @@ -225,6 +225,17 @@ if (class_exists(InlineConsent::class)) { --- +## 🔧 Backend-Konfiguration: Globale Einstellungen + +### Session-Scope (Nur für die Sitzung merken) + +Unter **Consent Manager → Einstellungen** kann die Option **"Inline-Consent: Zustimmung nur für Session merken"** aktiviert werden. + +- **Deaktiviert (Standard):** Zustimmungen für Inline-Elemente (z.B. "Einmal laden" oder "Alle zulassen") werden als persistentes Cookie gespeichert (Standard 1 Jahr). +- **Aktiviert:** Zustimmungen werden im `sessionStorage` des Browsers gespeichert. Sobald der Tab oder Browser geschlossen wird, verfällt die Zustimmung automatisch. + +Diese Einstellung ist besonders datenschutzfreundlich, da Besucher bei jedem neuen Besuch erneut explizit zustimmen müssen. + ## 🔧 Backend-Konfiguration: Platzhalter pro Service Im Backend unter **Consent Manager → Cookies** können für jeden Service individuelle Platzhalter-Einstellungen vorgenommen werden: diff --git a/lang/de_de.lang b/lang/de_de.lang index ae4f8436..38e8b13e 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -308,6 +308,10 @@ consent_manager_import_standard_update_success = Standard Setup Update erfolgrei consent_manager_import_standard_update_error = Standard Setup Update fehlgeschlagen: {0} # Inline Consent Einstellungen +consent_manager_config_inline_consent_session_scope = Inline-Consent für Session merken +consent_manager_config_inline_consent_session_scope_enable = Ja, Auswahl für die Session speichern +consent_manager_config_inline_consent_session_scope_desc = Wenn aktiviert, wird die Auswahl "Einmal laden" und "Alle erlauben" (im Inline-Kontext) für die Dauer der Browsersitzung gespeichert (sessionStorage). Standardmäßig gilt "Einmal laden" nur bis zum Neuladen der Seite und "Alle erlauben" speichert dauerhaft im Cookie. Mit dieser Option verhält sich "Alle erlauben" wie eine Session-Freigabe. + consent_manager_config_inline_only_mode = Nur Inline-Consent verwenden consent_manager_config_inline_only_mode_desc = Das globale Consent-Popup wird standardmäßig nicht angezeigt. Consent wird nur bei Bedarf über doConsent() abgefragt. diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 40c4d657..0e4b1a6f 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -416,6 +416,11 @@ consent_manager_import_standard_update_error = Standard setup update failed: {0} consent_manager_quickstart_status_final = Completion # Inline Consent Settings +# Inline Consent Settings +consent_manager_config_inline_consent_session_scope = Remember inline consent for session +consent_manager_config_inline_consent_session_scope_enable = Yes, save selection for the session +consent_manager_config_inline_consent_session_scope_desc = If enabled, the selection "Load once" and "Allow all" (in inline context) is stored for the duration of the browser session (sessionStorage). By default, "Load once" applies only until the page is reloaded and "Allow all" saves permanently in the cookie. With this option, "Allow all" behaves like a session approval. + consent_manager_config_inline_only_mode = Use inline consent only consent_manager_config_inline_only_mode_desc = The global consent popup is not displayed by default. Consent is only requested when needed via doConsent(). diff --git a/lang/sv_se.lang b/lang/sv_se.lang index 2b91427d..29a2dcfd 100644 --- a/lang/sv_se.lang +++ b/lang/sv_se.lang @@ -396,6 +396,11 @@ consent_manager_import_standard_update_error = Standard setup-uppdatering missly consent_manager_quickstart_status_final = Slutförande # Inline Consent-inställningar +# Inline Consent Settings +consent_manager_config_inline_consent_session_scope = Kom ihåg inline-samtycke för sessionen +consent_manager_config_inline_consent_session_scope_enable = Ja, spara valet för sessionen +consent_manager_config_inline_consent_session_scope_desc = Om aktiverat sparas valet "Ladda en gång" och "Tillåt alla" (i inline-sammanhang) under webbläsarsessionens varaktighet (sessionStorage). Som standard gäller "Ladda en gång" endast tills sidan laddas om och "Tillåt alla" sparas permanent i cookien. Med detta alternativ beter sig "Tillåt alla" som ett sessionsgodkännande. + consent_manager_config_inline_only_mode = Använd endast inline-consent consent_manager_config_inline_only_mode_desc = Den globala consent-popupen visas inte som standard. Samtycke begärs endast vid behov via doConsent(). diff --git a/lib/Frontend.php b/lib/Frontend.php index ad5dfb53..b4b56718 100644 --- a/lib/Frontend.php +++ b/lib/Frontend.php @@ -345,9 +345,9 @@ public static function getFrontendCss(): string // 1. Prüfen ob Domain-spezifisches Theme existiert $domainTheme = null; $hasDomainConfig = false; - if (is_string(rex_request::server('HTTP_HOST'))) { + if ('' !== Utility::hostname()) { $frontend = new self(0); - $frontend->setDomain(rex_request::server('HTTP_HOST')); + $frontend->setDomain(Utility::hostname()); // Prüfen ob Domain konfiguriert ist if ('' !== $frontend->domainName) { @@ -544,8 +544,8 @@ public static function getCookieList(string $format = 'table', ?string $domainNa if (null === $domainName) { // Aktuelle Domain verwenden - if (is_string(rex_request::server('HTTP_HOST'))) { - $consent->setDomain(rex_request::server('HTTP_HOST')); + if ('' !== Utility::hostname()) { + $consent->setDomain(Utility::hostname()); } } else { // Spezifische Domain verwenden diff --git a/lib/InlineConsent.php b/lib/InlineConsent.php index 4013ed9a..dfc2a27d 100644 --- a/lib/InlineConsent.php +++ b/lib/InlineConsent.php @@ -397,9 +397,14 @@ public static function getJavaScript(): string } self::$jsOutputted = true; + $addon = \rex_addon::get('consent_manager'); + $sessionScope = $addon->getConfig('inline_consent_session_scope') ? 'true' : 'false'; + + $configScript = ''; + // JavaScript-Datei laden $jsPath = rex_url::addonAssets('consent_manager', 'consent_inline.js'); - return ''; + return $configScript . ''; } /** diff --git a/package.yml b/package.yml index acf1fa6d..3b7d02bc 100644 --- a/package.yml +++ b/package.yml @@ -1,5 +1,5 @@ package: consent_manager -version: "5.3.5" +version: "5.4.0" author: "Friends Of REDAXO" supportpage: https://redaxo.org/support/community/#slack diff --git a/pages/config.php b/pages/config.php index db8425f9..4a66ce02 100644 --- a/pages/config.php +++ b/pages/config.php @@ -291,6 +291,12 @@ $field->addOption(rex_i18n::msg('consent_manager_config_inline_only_mode'), 1); $field->setNotice(rex_i18n::msg('consent_manager_config_inline_only_mode_desc')); +// Inline Consent Session Scope +$field = $form->addCheckboxField('inline_consent_session_scope'); +$field->setLabel(rex_i18n::msg('consent_manager_config_inline_consent_session_scope')); +$field->addOption(rex_i18n::msg('consent_manager_config_inline_consent_session_scope_enable'), 1); // Use "1" as value for checked +$field->setNotice(rex_i18n::msg('consent_manager_config_inline_consent_session_scope_desc')); + // Auto-Blocking für manuell eingefügtes HTML $field = $form->addCheckboxField('auto_blocking_enabled'); $field->setLabel(rex_i18n::msg('consent_manager_config_auto_blocking')); From 5ff31e5aef51824a2965169c7909807f14bd93c0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:54:58 +0100 Subject: [PATCH 2/4] fix(security): Add missing CSP nonce attributes to script tags in box_cssjs.php (#460) * Initial plan * fix(security): Add missing nonce attributes to all script tags in box_cssjs.php Co-authored-by: skerbis <791247+skerbis@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: skerbis <791247+skerbis@users.noreply.github.com> --- fragments/ConsentManager/box_cssjs.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fragments/ConsentManager/box_cssjs.php b/fragments/ConsentManager/box_cssjs.php index 2024ec58..0a3bd86f 100644 --- a/fragments/ConsentManager/box_cssjs.php +++ b/fragments/ConsentManager/box_cssjs.php @@ -54,7 +54,7 @@ $googleConsentModeScriptFile = 'google_consent_mode_v2.js'; } $googleConsentModeScriptUrl = $addon->getAssetsUrl($googleConsentModeScriptFile); - $googleConsentModeOutput .= ' ' . PHP_EOL; + $googleConsentModeOutput .= ' ' . PHP_EOL; // Debug-Script laden wenn Debug-Modus aktiviert UND User im Backend eingeloggt if (isset($consent_manager->domainInfo['google_consent_mode_debug']) @@ -65,10 +65,10 @@ // Nur für eingeloggte Backend-Benutzer if (rex_backend_login::hasSession() && null !== rex::getUser()) { $debugScriptUrl = $addon->getAssetsUrl('consent_debug.js'); - $googleConsentModeOutput .= ' ' . PHP_EOL; + $googleConsentModeOutput .= ' ' . PHP_EOL; // Debug-Konfiguration für JavaScript verfügbar machen - $googleConsentModeOutput .= ' ' . PHP_EOL; -$consentparams['outputjs'] .= ' ' . PHP_EOL; +$consentparams['outputjs'] .= ' ' . PHP_EOL; +$consentparams['outputjs'] .= ' ' . PHP_EOL; // Ausgabe Google Consent Mode v2 (vor allem anderen) echo $googleConsentModeOutput; From 1e88f631911464b20644ee60af94f869266e626a Mon Sep 17 00:00:00 2001 From: Thomas Skerbis Date: Tue, 17 Feb 2026 13:55:52 +0100 Subject: [PATCH 3/4] rebuild form 5.3.5 --- CHANGELOG.md | 8 +++----- fragments/ConsentManager/theme_editor.php | 2 +- lang/de_de.lang | 1 + lang/en_gb.lang | 1 + lang/sv_se.lang | 2 ++ package.yml | 2 +- pages/theme.php | 7 +++++++ 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d490f1..be2988cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,11 @@ # REDAXO consent_manager - Changelog -## Version 5.4.0 - 11.02.2026 +## Version 5.5.0 (Entwicklung) - **Feature:** Inline-Consent kann nun optional auf "Session-Scope" beschränkt werden. Zustimmungen gelten dann nur, solange der Browser-Tab offen ist (via `sessionStorage`). Konfigurierbar unter Einstellungen. - **Fix:** Reload-Loop behoben: Das Öffnen der Details aus einem Inline-Element führte unter Umständen zu einem sofortigen Neuladen der Seite. -- **Fix:** iOS Safari Touch-Event Handling verbessert: Button musste unter Umständen doppelt getippt werden; nun reagiert er sofort (Danke @alexwenz). -- **System:** Build-Skript aktualisiert für bessere Minifizierung. - - +- **Fix:** iOS Safari Touch-Event Handling verbessert: Button musste unter Umständen doppelt getippt werden; nun reagiert er sofort. +- **Security:** CSRF/XSS-Schutz: Fehlende CSP-Nonce für Inline-Styles und Scripte ergänzt (`theme_editor.php` und `box_cssjs.php`). ## Version 5.3.4 - 29.01.2026 diff --git a/fragments/ConsentManager/theme_editor.php b/fragments/ConsentManager/theme_editor.php index f7079478..b0702a03 100644 --- a/fragments/ConsentManager/theme_editor.php +++ b/fragments/ConsentManager/theme_editor.php @@ -589,7 +589,7 @@ class="btn "> } -' . PHP_EOL; + $debugScript = '' . PHP_EOL; } catch (Exception $e) { - $debugScript = '' . PHP_EOL; + $debugScript = '' . PHP_EOL; } - $debugScript .= '' . PHP_EOL; + $debugScript .= '' . PHP_EOL; // Debug-Script vor einfügen $content = $ep->getSubject(); diff --git a/fragments/ConsentManager/box_cssjs.php b/fragments/ConsentManager/box_cssjs.php index 0a3bd86f..2d63124f 100644 --- a/fragments/ConsentManager/box_cssjs.php +++ b/fragments/ConsentManager/box_cssjs.php @@ -155,8 +155,8 @@ $jsConfig = [ 'cookieSameSite' => 'Lax', 'cookieSecure' => rex_request::isHttps(), - 'cookieName' => 'consentmanager', - 'cookieLifetime' => 14, // Tage + 'cookieName' => $addon->getConfig('cookie_name', 'consentmanager'), + 'cookieLifetime' => (int) $addon->getConfig('lifespan', 14), // Tage 'domain' => rex_request::server('HTTP_HOST', 'string', ''), 'version' => $consent_manager->version, 'cacheLogId' => $consent_manager->cacheLogId, diff --git a/fragments/ConsentManager/cookiedb.php b/fragments/ConsentManager/cookiedb.php index 177479e5..276e97c6 100644 --- a/fragments/ConsentManager/cookiedb.php +++ b/fragments/ConsentManager/cookiedb.php @@ -21,11 +21,12 @@ if (0 !== count($consent_manager->cookiegroups)) { /** phpstan-ignore-line */ // Cookie Consent + History + $cookieName = rex_addon::get('consent_manager')->getConfig('cookie_name', 'consentmanager'); $cookiedata = []; - if (is_string(rex_request::cookie('consentmanager'))) { - $cookiedata = (array) json_decode(rex_request::cookie('consentmanager'), true); + if (is_string(rex_request::cookie($cookieName))) { + $cookiedata = (array) json_decode(rex_request::cookie($cookieName), true); } - $consent_manager_cookie = null !== rex_request::cookie('consentmanager') ? $cookiedata : null; + $consent_manager_cookie = null !== rex_request::cookie($cookieName) ? $cookiedata : null; if (null !== $consent_manager_cookie && isset($consent_manager_cookie['cachelogid'])) { $db = rex_sql::factory(); $db->setDebug(false); diff --git a/fragments/ConsentManager/inline_placeholder.php b/fragments/ConsentManager/inline_placeholder.php index d295e51a..f6d585f6 100644 --- a/fragments/ConsentManager/inline_placeholder.php +++ b/fragments/ConsentManager/inline_placeholder.php @@ -121,7 +121,7 @@ class="consent-inline-thumbnail" - ', '<\/script>', $content) ?> diff --git a/lib/InlineConsent.php b/lib/InlineConsent.php index dfc2a27d..7ed69eb6 100644 --- a/lib/InlineConsent.php +++ b/lib/InlineConsent.php @@ -400,11 +400,11 @@ public static function getJavaScript(): string $addon = \rex_addon::get('consent_manager'); $sessionScope = $addon->getConfig('inline_consent_session_scope') ? 'true' : 'false'; - $configScript = ''; + $configScript = ''; // JavaScript-Datei laden $jsPath = rex_url::addonAssets('consent_manager', 'consent_inline.js'); - return $configScript . ''; + return $configScript . ''; } /** diff --git a/pages/help.php b/pages/help.php index aebd189e..30d3074f 100644 --- a/pages/help.php +++ b/pages/help.php @@ -166,7 +166,7 @@ $tocHtml .= ''; // JS for Live Search - $tocHtml .= '