Nur notwendige Cookies') && str_contains($body, 'data-analytics-consent="granted">Zustimmen')); $assert('Einwilligungsdialog ist eindeutig benannt', str_contains($body, '')); $consentCss = file_get_contents(APP_ROOT . '/assets/css/privacy-settings.css') ?: ''; $assert('Nur notwendige Cookies ist innen weiß gestaltet', str_contains($consentCss, '[data-analytics-consent="denied"]') && str_contains($consentCss, 'background: #ffffff;')); $assert('Matomo selbst ist im HTML nicht vorab eingebunden', !str_contains($body, 'https://analytics.example.test') && !str_contains($body, '"siteId"')); $assert('Cookie-Einstellungen bleiben nach der Entscheidung erreichbar', str_contains($body, 'analytics-consent-settings')); $assert('Browser nutzt den lokalen First-Party-Endpunkt', str_contains($body, '"trackingEndpoint":"nutzungsanalyse.php"') && str_contains($body, 'assets/js/privacy-settings.js')); $javascript = file_get_contents(APP_ROOT . '/assets/js/privacy-settings.js') ?: ''; $assert('Tracker sendet nur bei erteilter Einwilligung', str_contains($javascript, "readDecision() !== 'granted'")); $assert('Browser sendet nur an den lokalen Endpunkt', str_contains($javascript, 'window.fetch(configuration.trackingEndpoint') && !str_contains($javascript, 'matomo.js') && !str_contains($javascript, 'matomo.php')); $assert('Query und Fragment werden aus Analyse-URLs entfernt', str_contains($javascript, 'url.origin + url.pathname')); $assert('Keine Kaffeeliste-Benutzerkennung wird gelesen', !str_contains($javascript, 'userId') && !str_contains($javascript, 'sessionStorage') && !str_contains($javascript, 'localStorage')); $assert('Widerruf stoppt Tracking und löscht Analyse-Cookies', str_contains($javascript, 'removeAnalysisCookies()') && str_contains($javascript, "name !== visitorCookieName")); $relay = file_get_contents(APP_ROOT . '/app/analytics-relay.php') ?: ''; $assert('Relay übernimmt keine Client-IP', !str_contains($relay, 'REMOTE_ADDR') && !str_contains($relay, "'cip'")); $assert('Relay bindet Ziel und Site-ID an die Serverkonfiguration', str_contains($relay, "['idsite'] = \$configuration['siteId']") && str_contains($relay, "\$configuration['matomoUrl'] . 'matomo.php'")); $publicTemplates = [ 'abo-bestellen.php', 'agb.php', 'avv.php', 'datenschutz.php', 'email-verifizieren.php', 'impressum.php', 'kuendigen.php', 'landing.php', 'login.php', 'passwort-vergessen.php', 'passwort-zuruecksetzen.php', 'preise.php', 'register.php', 'widerruf.php', 'widerrufen.php', 'zwei-faktor.php', ]; foreach ($publicTemplates as $template) { $contents = file_get_contents(APP_ROOT . '/' . $template) ?: ''; $assert("{$template} bindet die Einwilligung vollständig ein", str_contains($contents, 'app_analytics_head_html') && str_contains($contents, 'app_analytics_body_html')); } $assert('App-Layout bindet Head und Body zentral ein', str_contains(file_get_contents(APP_ROOT . '/header.php') ?: '', 'app_analytics_head_html') && str_contains(file_get_contents(APP_ROOT . '/footer.php') ?: '', 'app_analytics_body_html')); $privacy = app_legal_document_text('privacy_notice'); $assert('Aktuelle Datenschutzerklärung beschreibt Matomo und Widerruf', str_contains($privacy, 'Matomo') && str_contains($privacy, 'Cookie-Einstellungen') && str_contains($privacy, 'kaffeeliste_analytics_visitor') && str_contains($privacy, 'nicht an Matomo weitergegeben') && str_contains($privacy, 'Verifizierungs-, Passwort-Reset- und Zugriffs-Tokens')); foreach ($previousEnvironment as $name => $value) { $value === false ? putenv($name) : putenv($name . '=' . $value); } if ($failures > 0) { echo "\n{$failures} Analyse-/Consent-Prüfung(en) fehlgeschlagen.\n"; exit(1); } echo "\nAnalyse und Einwilligung: alle Prüfungen bestanden.\n";