diff --git a/hifi-src/src/context/CookieConsentContext.jsx b/hifi-src/src/context/CookieConsentContext.jsx
index fced880..0c11dbf 100644
--- a/hifi-src/src/context/CookieConsentContext.jsx
+++ b/hifi-src/src/context/CookieConsentContext.jsx
@@ -2,7 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
const STORAGE_KEY = 'hifiplanet-cookie-consent';
-const defaultConsent = { necessary: true, external: false };
+const defaultConsent = { necessary: true, external: false, analytics: false };
const CookieConsentContext = createContext({
consent: defaultConsent,
@@ -25,7 +25,7 @@ export function CookieConsentProvider({ children }) {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
- setConsent({ necessary: true, external: !!parsed.external });
+ setConsent({ necessary: true, external: !!parsed.external, analytics: !!parsed.analytics });
setDecided(true);
}
} catch {
@@ -40,9 +40,12 @@ export function CookieConsentProvider({ children }) {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ ...next, decidedAt: new Date().toISOString() }));
}, []);
- const acceptAll = useCallback(() => persist({ necessary: true, external: true }), [persist]);
- const rejectNonEssential = useCallback(() => persist({ necessary: true, external: false }), [persist]);
- const saveCustom = useCallback((partial) => persist({ necessary: true, external: !!partial.external }), [persist]);
+ const acceptAll = useCallback(() => persist({ necessary: true, external: true, analytics: true }), [persist]);
+ const rejectNonEssential = useCallback(() => persist({ necessary: true, external: false, analytics: false }), [persist]);
+ const saveCustom = useCallback(
+ (partial) => persist({ necessary: true, external: !!partial.external, analytics: !!partial.analytics }),
+ [persist]
+ );
const openSettings = useCallback(() => setSettingsOpen(true), []);
const closeSettings = useCallback(() => setSettingsOpen(false), []);
diff --git a/hifi-src/src/context/SiteSettingsContext.jsx b/hifi-src/src/context/SiteSettingsContext.jsx
index 4558411..7030f70 100644
--- a/hifi-src/src/context/SiteSettingsContext.jsx
+++ b/hifi-src/src/context/SiteSettingsContext.jsx
@@ -7,6 +7,7 @@ const SiteSettingsContext = createContext({
whatsapp: null,
contact_email: 'info@hifi-planet-amorbach.de',
hero_image_path: null,
+ ga_measurement_id: null,
});
export const SiteSettingsProvider = SiteSettingsContext.Provider;
diff --git a/hifi-src/src/i18n/de.js b/hifi-src/src/i18n/de.js
index 8e650d1..d6b8ab3 100644
--- a/hifi-src/src/i18n/de.js
+++ b/hifi-src/src/i18n/de.js
@@ -36,7 +36,7 @@ export default {
cookieSettings: 'Cookie-Einstellungen',
},
cookieConsent: {
- bannerText: 'Wir verwenden nur technisch notwendige Cookies. Für Google Maps und YouTube-Videos benötigen wir zusätzlich deine Zustimmung, da dabei Daten an Google übertragen werden.',
+ bannerText: 'Wir verwenden nur technisch notwendige Cookies. Für Google Maps, YouTube-Videos und Statistik-Cookies benötigen wir zusätzlich deine Zustimmung, da dabei Daten an Google übertragen werden.',
moreInfo: 'Mehr dazu in unserer',
privacyPolicy: 'Datenschutzerklärung',
settings: 'Einstellungen',
@@ -48,6 +48,8 @@ export default {
necessaryDesc: 'Für den Betrieb der Website erforderlich (z. B. Login-Session, Theme- und Cookie-Einstellung). Kann nicht deaktiviert werden.',
externalTitle: 'Externe Medien',
externalDesc: 'Google Maps und YouTube-Videos. Beim Laden werden Daten (u. a. deine IP-Adresse) an Google übertragen.',
+ analyticsTitle: 'Statistik',
+ analyticsDesc: 'Google Analytics hilft uns zu verstehen, wie die Website genutzt wird. Dabei werden Daten an Google übertragen.',
cancel: 'Abbrechen',
saveSelection: 'Auswahl speichern',
},
diff --git a/hifi-src/src/i18n/en.js b/hifi-src/src/i18n/en.js
index 83ad0f2..eda24b0 100644
--- a/hifi-src/src/i18n/en.js
+++ b/hifi-src/src/i18n/en.js
@@ -36,7 +36,7 @@ export default {
cookieSettings: 'Cookie settings',
},
cookieConsent: {
- bannerText: 'We only use technically necessary cookies. For Google Maps and YouTube videos we need your additional consent, since data is transferred to Google in that case.',
+ bannerText: 'We only use technically necessary cookies. For Google Maps, YouTube videos and analytics cookies we need your additional consent, since data is transferred to Google in that case.',
moreInfo: 'More details in our',
privacyPolicy: 'privacy policy',
settings: 'Settings',
@@ -48,6 +48,8 @@ export default {
necessaryDesc: 'Required to operate the website (e.g. login session, theme and cookie preference). Cannot be disabled.',
externalTitle: 'External media',
externalDesc: 'Google Maps and YouTube videos. Loading them transfers data (including your IP address) to Google.',
+ analyticsTitle: 'Analytics',
+ analyticsDesc: 'Google Analytics helps us understand how the website is used. This transfers data to Google.',
cancel: 'Cancel',
saveSelection: 'Save selection',
},
diff --git a/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx b/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx
index 98f7334..d0cb6ea 100644
--- a/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx
+++ b/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import { api } from '../../../api/client.js';
import ImageUploadField from '../../../components/ImageUploadField.jsx';
-const emptyForm = { phone: '', whatsapp: '', contact_email: '', hero_image_path: '' };
+const emptyForm = { phone: '', whatsapp: '', contact_email: '', hero_image_path: '', ga_measurement_id: '' };
export default function WebsiteSettings() {
const [form, setForm] = useState(emptyForm);
@@ -20,6 +20,7 @@ export default function WebsiteSettings() {
whatsapp: res.whatsapp || '',
contact_email: res.contact_email || '',
hero_image_path: res.hero_image_path || '',
+ ga_measurement_id: res.ga_measurement_id || '',
})
)
.finally(() => setLoading(false));
@@ -103,6 +104,23 @@ export default function WebsiteSettings() {
+
+
Google Analytics
+
+ Wird erst geladen, nachdem ein Besucher der Statistik-Kategorie im Cookie-Banner zugestimmt hat. Leer
+ lassen, um Google Analytics zu deaktivieren.
+
Für diese technisch notwendigen Cookies ist gemäß § 25 Abs. 2 Nr. 2 TTDSG keine Einwilligung
erforderlich. Rechtsgrundlage ist unser berechtigtes Interesse am technischen Betrieb der Website (Art.
- 6 Abs. 1 lit. f DSGVO). Für alle darüber hinausgehenden Inhalte – aktuell die eingebundenen Google
- Maps- und YouTube-Elemente – fragen wir Sie beim ersten Besuch aktiv nach Ihrer Einwilligung. Sie
- können Ihre Auswahl jederzeit über den Link „Cookie-Einstellungen" im Footer ändern.
+ 6 Abs. 1 lit. f DSGVO). Für alle darüber hinausgehenden Inhalte und Dienste – aktuell die eingebundenen
+ Google Maps- und YouTube-Elemente sowie Google Analytics – fragen wir Sie beim ersten Besuch aktiv nach
+ Ihrer Einwilligung. Sie können Ihre Auswahl jederzeit über den Link „Cookie-Einstellungen" im Footer
+ ändern.
@@ -145,7 +146,31 @@ export default function Datenschutz() {
-
8. Empfänger der Daten
+
8. Google Analytics
+
+ Sofern Sie der Statistik-Kategorie im Cookie-Banner zugestimmt haben, setzen wir „Google Analytics" ein,
+ einen Webanalysedienst der Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irland. Google
+ Analytics verwendet Cookies und ähnliche Technologien, die eine Analyse der Benutzung dieser Website
+ durch Sie ermöglichen. Die dabei erzeugten Informationen (u. a. Ihre IP-Adresse, die vor der Verarbeitung
+ durch die Aktivierung der IP-Anonymisierung gekürzt wird) werden an einen Server von Google übertragen
+ und dort gespeichert; eine Verarbeitung kann dabei auch auf Servern außerhalb der EU/des EWR erfolgen.
+ Die Verarbeitung erfolgt ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO),
+ die Sie jederzeit über die Cookie-Einstellungen mit Wirkung für die Zukunft widerrufen können. Weitere
+ Informationen finden Sie in der{' '}
+
+ Datenschutzerklärung von Google
+
+ .
+
+
+
+
+
9. Empfänger der Daten
Ihre Daten werden grundsätzlich nur innerhalb unseres Unternehmens verarbeitet. Eine Weitergabe an
Dritte erfolgt nur, soweit dies zur Bearbeitung Ihrer Anfrage erforderlich ist (z. B. an unseren
@@ -155,7 +180,7 @@ export default function Datenschutz() {
-
9. Speicherdauer
+
10. Speicherdauer
Sofern in dieser Erklärung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre
personenbezogenen Daten bei uns, bis der Zweck für die Datenspeicherung entfällt. Gesetzliche
@@ -165,7 +190,7 @@ export default function Datenschutz() {
-
10. SSL-/TLS-Verschlüsselung
+
11. SSL-/TLS-Verschlüsselung
Diese Seite nutzt aus Sicherheitsgründen eine SSL-/TLS-Verschlüsselung zur Übertragung Ihrer Daten. Eine
verschlüsselte Verbindung erkennen Sie an dem Schloss-Symbol in der Adresszeile Ihres Browsers.
@@ -173,7 +198,7 @@ export default function Datenschutz() {
-
11. Änderung dieser Datenschutzerklärung
+
12. Änderung dieser Datenschutzerklärung
Wir passen diese Datenschutzerklärung an, sobald sich die von uns eingesetzten Datenverarbeitungen
ändern. Es gilt jeweils die zum Zeitpunkt Ihres Besuchs aktuelle, auf dieser Seite veröffentlichte
diff --git a/hifi/api/database/migration_analytics.sql b/hifi/api/database/migration_analytics.sql
new file mode 100644
index 0000000..3ce9664
--- /dev/null
+++ b/hifi/api/database/migration_analytics.sql
@@ -0,0 +1,6 @@
+-- Google Analytics Measurement ID admin-konfigurierbar machen. Additiv, nichts wird geloescht.
+-- Hinweis: Auf der Live-Seite reicht stattdessen ein Klick auf "Datenbankstruktur aktualisieren"
+-- unter Admin-Panel -> Einstellungen -> Datenbank.
+
+ALTER TABLE app_settings
+ ADD COLUMN IF NOT EXISTS ga_measurement_id VARCHAR(20) NULL;
diff --git a/hifi/api/database/schema.sql b/hifi/api/database/schema.sql
index 1d254af..c55da29 100644
--- a/hifi/api/database/schema.sql
+++ b/hifi/api/database/schema.sql
@@ -24,6 +24,7 @@ CREATE TABLE app_settings (
whatsapp VARCHAR(50) NULL,
contact_email VARCHAR(150) NULL,
hero_image_path VARCHAR(255) NULL,
+ ga_measurement_id VARCHAR(20) NULL,
mail_host VARCHAR(255) NULL,
mail_port INT NULL,
mail_username VARCHAR(255) NULL,
diff --git a/hifi/api/src/Controllers/SiteSettingsController.php b/hifi/api/src/Controllers/SiteSettingsController.php
index c90fc6f..961377d 100644
--- a/hifi/api/src/Controllers/SiteSettingsController.php
+++ b/hifi/api/src/Controllers/SiteSettingsController.php
@@ -14,13 +14,14 @@ class SiteSettingsController
'whatsapp' => null,
'contact_email' => 'info@hifi-planet-amorbach.de',
'hero_image_path' => null,
+ 'ga_measurement_id' => null,
];
public static function show(): void
{
try {
$stmt = Database::connection()->query(
- 'SELECT phone, whatsapp, contact_email, hero_image_path FROM app_settings WHERE id = 1'
+ 'SELECT phone, whatsapp, contact_email, hero_image_path, ga_measurement_id FROM app_settings WHERE id = 1'
);
$row = $stmt->fetch();
} catch (\Throwable $e) {
@@ -43,13 +44,14 @@ class SiteSettingsController
$db->exec('INSERT IGNORE INTO app_settings (id) VALUES (1)');
$stmt = $db->prepare(
- 'UPDATE app_settings SET phone = ?, whatsapp = ?, contact_email = ?, hero_image_path = ? WHERE id = 1'
+ 'UPDATE app_settings SET phone = ?, whatsapp = ?, contact_email = ?, hero_image_path = ?, ga_measurement_id = ? WHERE id = 1'
);
$stmt->execute([
trim($body['phone'] ?? '') ?: null,
trim($body['whatsapp'] ?? '') ?: null,
trim($body['contact_email'] ?? '') ?: null,
trim($body['hero_image_path'] ?? '') ?: null,
+ trim($body['ga_measurement_id'] ?? '') ?: null,
]);
self::show();
diff --git a/hifi/api/src/Support/Schema.php b/hifi/api/src/Support/Schema.php
index c24ecae..b8499f9 100644
--- a/hifi/api/src/Support/Schema.php
+++ b/hifi/api/src/Support/Schema.php
@@ -35,6 +35,7 @@ class Schema
whatsapp VARCHAR(50) NULL,
contact_email VARCHAR(150) NULL,
hero_image_path VARCHAR(255) NULL,
+ ga_measurement_id VARCHAR(20) NULL,
mail_host VARCHAR(255) NULL,
mail_port INT NULL,
mail_username VARCHAR(255) NULL,
@@ -242,6 +243,7 @@ class Schema
'whatsapp' => 'VARCHAR(50) NULL',
'contact_email' => 'VARCHAR(150) NULL',
'hero_image_path' => 'VARCHAR(255) NULL',
+ 'ga_measurement_id' => 'VARCHAR(20) NULL',
'mail_host' => 'VARCHAR(255) NULL',
'mail_port' => 'INT NULL',
'mail_username' => 'VARCHAR(255) NULL',