diff --git a/app/analytics.php b/app/analytics.php
index f0b4237..088741f 100644
--- a/app/analytics.php
+++ b/app/analytics.php
@@ -108,6 +108,16 @@ function app_analytics_head_html(): string
return '' . "\n";
}
+function app_analytics_footer_settings_button_html(): string
+{
+ if (app_matomo_configuration() === null) {
+ return '';
+ }
+
+ return '';
+}
+
function app_analytics_body_html(): string
{
$configuration = app_matomo_configuration();
@@ -154,7 +164,7 @@ function app_analytics_body_html(): string
-
+
HTML
. "\n\n"
. '' . "\n";
diff --git a/app/legal.php b/app/legal.php
index 3599d85..8ceb7ee 100644
--- a/app/legal.php
+++ b/app/legal.php
@@ -35,6 +35,7 @@ function app_public_legal_footer(): string
. 'Vertrag widerrufen'
. 'Preise'
. 'Verträge hier kündigen'
+ . app_analytics_footer_settings_button_html()
. '';
}
diff --git a/assets/css/privacy-settings.css b/assets/css/privacy-settings.css
index df99dd6..d94b4c9 100644
--- a/assets/css/privacy-settings.css
+++ b/assets/css/privacy-settings.css
@@ -103,6 +103,41 @@
padding: 0.55rem 0.7rem;
}
+.footer-cookie-settings {
+ appearance: none;
+ background: transparent;
+ border: 0;
+ border-bottom: dotted 1px;
+ border-radius: 0;
+ box-shadow: none;
+ color: inherit !important;
+ cursor: pointer;
+ font: inherit;
+ font-weight: inherit;
+ height: auto;
+ letter-spacing: inherit;
+ line-height: inherit;
+ margin: 0;
+ padding: 0;
+ text-transform: none;
+}
+
+.footer-cookie-settings:hover {
+ background: transparent;
+ border-bottom-color: transparent;
+ color: #38761d !important;
+}
+
+.footer-cookie-settings:focus-visible {
+ outline: solid 3px #f0b429;
+ outline-offset: 3px;
+}
+
+.public-footer-links .footer-cookie-settings {
+ color: #3d4449 !important;
+ margin: 0 0.5rem;
+}
+
@media screen and (max-width: 736px) {
.analytics-consent {
padding: 1rem;
diff --git a/assets/js/privacy-settings.js b/assets/js/privacy-settings.js
index b8e4ca8..3f3727e 100644
--- a/assets/js/privacy-settings.js
+++ b/assets/js/privacy-settings.js
@@ -3,9 +3,10 @@
var configurationElement = document.getElementById('analytics-configuration');
var banner = document.getElementById('analytics-consent');
- var settingsButton = document.getElementById('analytics-consent-settings');
+ var floatingSettingsButton = document.getElementById('analytics-consent-settings');
+ var settingsButtons = document.querySelectorAll('[data-analytics-settings]');
- if (!configurationElement || !banner || !settingsButton) {
+ if (!configurationElement || !banner || !floatingSettingsButton) {
return;
}
@@ -148,7 +149,7 @@
function closeBanner() {
banner.hidden = true;
- settingsButton.hidden = false;
+ floatingSettingsButton.hidden = false;
}
function choose(decision) {
@@ -170,21 +171,23 @@
});
});
- settingsButton.addEventListener('click', function () {
- banner.hidden = false;
- settingsButton.hidden = true;
- var firstButton = banner.querySelector('[data-analytics-consent="denied"]');
- if (firstButton) {
- firstButton.focus();
- }
+ settingsButtons.forEach(function (button) {
+ button.addEventListener('click', function () {
+ banner.hidden = false;
+ floatingSettingsButton.hidden = true;
+ var firstButton = banner.querySelector('[data-analytics-consent="denied"]');
+ if (firstButton) {
+ firstButton.focus();
+ }
+ });
});
var initialDecision = readDecision();
if (initialDecision === 'granted') {
- settingsButton.hidden = false;
+ floatingSettingsButton.hidden = false;
sendPageView();
} else if (initialDecision === 'denied') {
- settingsButton.hidden = false;
+ floatingSettingsButton.hidden = false;
removeAnalysisCookies();
} else {
banner.hidden = false;
diff --git a/email-verifizieren.php b/email-verifizieren.php
index b2580a7..777bb3e 100644
--- a/email-verifizieren.php
+++ b/email-verifizieren.php
@@ -68,6 +68,7 @@ $result = $token !== ''
Vertrag widerrufen
Preise
Verträge hier kündigen
+
diff --git a/footer.php b/footer.php
index 025ac70..24f77ed 100644
--- a/footer.php
+++ b/footer.php
@@ -133,7 +133,7 @@ if ($saasNavUser !== null) {
diff --git a/landing.php b/landing.php
index 8b074b6..019ad8b 100644
--- a/landing.php
+++ b/landing.php
@@ -292,6 +292,7 @@ $jsonFlags = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
Vertrag widerrufen
Preise
Verträge hier kündigen
+
diff --git a/login.php b/login.php
index d9045f0..851271c 100644
--- a/login.php
+++ b/login.php
@@ -136,6 +136,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Vertrag widerrufen
Preise
Verträge hier kündigen
+
diff --git a/passwort-vergessen.php b/passwort-vergessen.php
index 02d719a..09d11d2 100644
--- a/passwort-vergessen.php
+++ b/passwort-vergessen.php
@@ -116,6 +116,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Vertrag widerrufen
Preise
Verträge hier kündigen
+
diff --git a/passwort-zuruecksetzen.php b/passwort-zuruecksetzen.php
index d5889ec..dc1c93d 100644
--- a/passwort-zuruecksetzen.php
+++ b/passwort-zuruecksetzen.php
@@ -111,6 +111,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Vertrag widerrufen
Preise
Verträge hier kündigen
+
diff --git a/register.php b/register.php
index 75cfb27..0ef24c8 100644
--- a/register.php
+++ b/register.php
@@ -211,6 +211,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Vertrag widerrufen
Preise
Verträge hier kündigen
+
diff --git a/scripts/check-analytics-consent.php b/scripts/check-analytics-consent.php
index 1dc2b22..3ea8d73 100644
--- a/scripts/check-analytics-consent.php
+++ b/scripts/check-analytics-consent.php
@@ -74,6 +74,9 @@ $assert('Matomo selbst ist im HTML nicht vorab eingebunden',
&& !str_contains($body, '"siteId"'));
$assert('Cookie-Einstellungen bleiben nach der Entscheidung erreichbar',
str_contains($body, 'analytics-consent-settings'));
+$assert('Footer-Schalter wird bei aktiver Analyse ausgegeben',
+ str_contains(app_analytics_footer_settings_button_html(), 'data-analytics-settings')
+ && str_contains(app_public_legal_footer(), 'Cookie-Einstellungen'));
$assert('Browser nutzt den lokalen First-Party-Endpunkt',
str_contains($body, '"trackingEndpoint":"nutzungsanalyse.php"')
&& str_contains($body, 'assets/js/privacy-settings.js'));
@@ -94,6 +97,9 @@ $assert('Keine Kaffeeliste-Benutzerkennung wird gelesen',
$assert('Widerruf stoppt Tracking und löscht Analyse-Cookies',
str_contains($javascript, 'removeAnalysisCookies()')
&& str_contains($javascript, "name !== visitorCookieName"));
+$assert('Alle Cookie-Einstellungs-Schalter öffnen denselben Dialog',
+ str_contains($javascript, "querySelectorAll('[data-analytics-settings]')")
+ && str_contains($javascript, 'settingsButtons.forEach'));
$relay = file_get_contents(APP_ROOT . '/app/analytics-relay.php') ?: '';
$assert('Relay übernimmt keine Client-IP',
@@ -130,6 +136,25 @@ foreach ($publicTemplates as $template) {
$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'));
+$appFooter = file_get_contents(APP_ROOT . '/footer.php') ?: '';
+$assert('App-Footer enthält Cookie-Einstellungen ohne Copyright- und Designlink',
+ str_contains($appFooter, 'app_analytics_footer_settings_button_html')
+ && !str_contains($appFooter, '© Clemens Creutzburg')
+ && !str_contains($appFooter, 'html5up.net'));
+
+foreach ([
+ 'email-verifizieren.php',
+ 'landing.php',
+ 'login.php',
+ 'passwort-vergessen.php',
+ 'passwort-zuruecksetzen.php',
+ 'register.php',
+ 'zwei-faktor.php',
+] as $manualFooterTemplate) {
+ $contents = file_get_contents(APP_ROOT . '/' . $manualFooterTemplate) ?: '';
+ $assert("{$manualFooterTemplate} bietet Cookie-Einstellungen im Footer an",
+ str_contains($contents, 'app_analytics_footer_settings_button_html'));
+}
$privacy = app_legal_document_text('privacy_notice');
$assert('Aktuelle Datenschutzerklärung beschreibt Matomo und Widerruf',
diff --git a/zwei-faktor.php b/zwei-faktor.php
index 1ec5d20..6f2a1cb 100644
--- a/zwei-faktor.php
+++ b/zwei-faktor.php
@@ -112,6 +112,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
Datenschutz
AVV
Widerrufsbelehrung
+