diff --git a/admin.php b/admin.php index 1d4621e..c4f5911 100644 --- a/admin.php +++ b/admin.php @@ -2,7 +2,7 @@ session_start(); require __DIR__ . '/helpers.php'; -$ADMIN_KEY = getenv('FLIXCOOKS_ADMIN_KEY') ?: 'REWnqLYwQtZZDgXcNxnt'; +$ADMIN_KEY = getenv('FLIXCOOKS_ADMIN_KEY') ?: 'vFDH.N_tVLEKNdR3fhLs'; $authed = isset($_SESSION['fc_admin']) && $_SESSION['fc_admin'] === true; if (!$authed && isset($_POST['password'])) { @@ -50,11 +50,18 @@ if (!$authed) { } $allRecipes = load_recipes(); +$siteSettings = load_site_settings(); $message = null; +$errors = []; $editing = null; $editIndex = null; $editingEn = []; $editingDe = []; +$view = (isset($_GET['view']) && $_GET['view'] === 'settings') ? 'settings' : 'recipes'; + +if (empty($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(16)); +} if (isset($_GET['edit'])) { $slugEdit = trim($_GET['edit']); @@ -69,7 +76,94 @@ if (isset($_GET['edit'])) { } } -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'delete') { +if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_settings') { + if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['token'] ?? '')) { + $errors[] = 'Invalid form token, please retry.'; + } else { + $imprint = $siteSettings['imprint']; + $privacy = $siteSettings['privacy']; + + $imprint['de']['owner_name'] = trim($_POST['owner_name_de'] ?? $imprint['de']['owner_name']); + $imprint['en']['owner_name'] = trim($_POST['owner_name_en'] ?? $imprint['en']['owner_name']); + $imprint['de']['address'] = trim($_POST['address_de'] ?? $imprint['de']['address']); + $imprint['en']['address'] = trim($_POST['address_en'] ?? $imprint['en']['address']); + $imprint['de']['email'] = trim($_POST['email_de'] ?? $imprint['de']['email']); + $imprint['en']['email'] = trim($_POST['email_en'] ?? $imprint['en']['email']); + $imprint['de']['phone'] = trim($_POST['phone_de'] ?? $imprint['de']['phone']); + $imprint['en']['phone'] = trim($_POST['phone_en'] ?? $imprint['en']['phone']); + $imprint['de']['legal_form'] = trim($_POST['legal_form_de'] ?? $imprint['de']['legal_form']); + $imprint['en']['legal_form'] = trim($_POST['legal_form_en'] ?? $imprint['en']['legal_form']); + $imprint['de']['business_purpose'] = trim($_POST['business_purpose_de'] ?? $imprint['de']['business_purpose']); + $imprint['en']['business_purpose'] = trim($_POST['business_purpose_en'] ?? $imprint['en']['business_purpose']); + $imprint['de']['wko_membership'] = trim($_POST['wko_de'] ?? $imprint['de']['wko_membership']); + $imprint['en']['wko_membership'] = trim($_POST['wko_en'] ?? $imprint['en']['wko_membership']); + $imprint['de']['authority'] = trim($_POST['authority_de'] ?? $imprint['de']['authority']); + $imprint['en']['authority'] = trim($_POST['authority_en'] ?? $imprint['en']['authority']); + $imprint['de']['uid'] = trim($_POST['uid_de'] ?? $imprint['de']['uid']); + $imprint['en']['uid'] = trim($_POST['uid_en'] ?? $imprint['en']['uid']); + $imprint['de']['odr'] = trim($_POST['odr_link'] ?? $imprint['de']['odr']); + $imprint['en']['odr'] = $imprint['de']['odr']; + $imprint['de']['last_updated'] = date('Y-m-d'); + $imprint['en']['last_updated'] = date('Y-m-d'); + + $privacy['de']['controller'] = trim($_POST['controller_de'] ?? $privacy['de']['controller']); + $privacy['en']['controller'] = trim($_POST['controller_en'] ?? $privacy['en']['controller']); + $privacy['de']['address'] = trim($_POST['privacy_address_de'] ?? $privacy['de']['address']); + $privacy['en']['address'] = trim($_POST['privacy_address_en'] ?? $privacy['en']['address']); + $privacy['de']['contact_email'] = trim($_POST['privacy_email_de'] ?? $privacy['de']['contact_email']); + $privacy['en']['contact_email'] = trim($_POST['privacy_email_en'] ?? $privacy['en']['contact_email']); + $privacy['de']['contact_phone'] = trim($_POST['privacy_phone_de'] ?? $privacy['de']['contact_phone']); + $privacy['en']['contact_phone'] = trim($_POST['privacy_phone_en'] ?? $privacy['en']['contact_phone']); + $privacy['de']['hosting_provider'] = trim($_POST['hosting_de'] ?? $privacy['de']['hosting_provider']); + $privacy['en']['hosting_provider'] = trim($_POST['hosting_en'] ?? $privacy['en']['hosting_provider']); + $privacy['de']['cookie_statement'] = trim($_POST['cookie_de'] ?? $privacy['de']['cookie_statement']); + $privacy['en']['cookie_statement'] = trim($_POST['cookie_en'] ?? $privacy['en']['cookie_statement']); + $privacy['de']['purposes'] = trim($_POST['purposes_de'] ?? $privacy['de']['purposes']); + $privacy['en']['purposes'] = trim($_POST['purposes_en'] ?? $privacy['en']['purposes']); + $privacy['de']['log_retention_days'] = (int) ($_POST['log_retention'] ?? $privacy['de']['log_retention_days']); + $privacy['en']['log_retention_days'] = $privacy['de']['log_retention_days']; + $privacy['de']['last_updated'] = date('Y-m-d'); + $privacy['en']['last_updated'] = date('Y-m-d'); + + // Require core fields + $requiredPairs = [ + ['imprint', 'owner_name', 'Owner name'], + ['imprint', 'address', 'Address'], + ['imprint', 'email', 'Email'], + ['imprint', 'phone', 'Phone'], + ['imprint', 'business_purpose', 'Business purpose'], + ['privacy', 'controller', 'Controller'], + ['privacy', 'contact_email', 'Privacy email'], + ['privacy', 'contact_phone', 'Privacy phone'], + ['privacy', 'hosting_provider', 'Hosting provider'], + ['privacy', 'cookie_statement', 'Cookie statement'], + ]; + + foreach ($requiredPairs as [$section, $key, $label]) { + $deVal = ${$section}['de'][$key] ?? ''; + $enVal = ${$section}['en'][$key] ?? ''; + if ($deVal === '' && $enVal !== '') { + ${$section}['de'][$key] = $enVal; + } elseif ($enVal === '' && $deVal !== '') { + ${$section}['en'][$key] = $deVal; + } elseif ($deVal === '' && $enVal === '') { + $errors[] = $label . ' is required (DE or EN).'; + } + } + + if (empty($errors)) { + $siteSettings['imprint'] = $imprint; + $siteSettings['privacy'] = $privacy; + if (save_site_settings($siteSettings)) { + $message = 'Settings saved.'; + } else { + $errors[] = 'Could not save settings. Check permissions on data/site.json.'; + } + } + } +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') { $slugDel = trim($_POST['slug'] ?? ''); $before = count($allRecipes); $allRecipes = array_values(array_filter($allRecipes, fn($r) => ($r['slug'] ?? '') !== $slugDel)); @@ -80,9 +174,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' } $editing = null; $editIndex = null; -} - -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'save') { +} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save') { // helpers $parse_csv = fn($text) => array_values(array_filter(array_map('trim', explode(',', $text ?? '')), 'strlen')); $parse_lines = fn($text) => array_values(array_filter(array_map('trim', preg_split('/\\r?\\n/', $text ?? '')), 'strlen')); @@ -217,6 +309,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' + + + +
+

+

+ + +
+ + + diff --git a/helpers.php b/helpers.php index 2b63d3d..cfe9d06 100644 --- a/helpers.php +++ b/helpers.php @@ -28,6 +28,93 @@ function save_recipes(array $recipes): bool { return (bool) file_put_contents($path, $json); } +/** + * Default settings for imprint and privacy pages. + */ +function default_site_settings(): array { + $today = date('Y-m-d'); + return [ + 'imprint' => [ + 'de' => [ + 'owner_name' => 'FlixCooks (bitte anpassen)', + 'address' => 'Straße Hausnummer, PLZ Ort, Österreich', + 'email' => 'contact@flixcooks.at', + 'phone' => '+43 660 0000000', + 'legal_form' => 'Kleines Impressum; kein Firmenbucheintrag/UID hinterlegt.', + 'business_purpose' => 'Foodblog & Rezeptmarketing.', + 'wko_membership' => '', + 'authority' => '', + 'uid' => '', + 'odr' => 'https://ec.europa.eu/consumers/odr', + 'last_updated' => $today, + ], + 'en' => [ + 'owner_name' => 'FlixCooks (please update)', + 'address' => 'Street number, ZIP City, Austria', + 'email' => 'contact@flixcooks.at', + 'phone' => '+43 660 0000000', + 'legal_form' => 'Small website notice; no commercial register/UID provided.', + 'business_purpose' => 'Food blog & recipe marketing.', + 'wko_membership' => '', + 'authority' => '', + 'uid' => '', + 'odr' => 'https://ec.europa.eu/consumers/odr', + 'last_updated' => $today, + ], + ], + 'privacy' => [ + 'de' => [ + 'controller' => 'FlixCooks (bitte anpassen)', + 'contact_email' => 'contact@flixcooks.at', + 'contact_phone' => '+43 660 0000000', + 'address' => 'Straße Hausnummer, PLZ Ort, Österreich', + 'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Deutschland', + 'log_retention_days' => 30, + 'cookie_statement' => 'Keine Tracking- oder Marketing-Cookies; nur technisch notwendige.', + 'purposes' => 'Betrieb und Bereitstellung der Website, Beantwortung von Kontaktanfragen.', + 'last_updated' => $today, + ], + 'en' => [ + 'controller' => 'FlixCooks (please update)', + 'contact_email' => 'contact@flixcooks.at', + 'contact_phone' => '+43 660 0000000', + 'address' => 'Street number, ZIP City, Austria', + 'hosting_provider' => 'Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany', + 'log_retention_days' => 30, + 'cookie_statement' => 'No tracking or marketing cookies; only technically necessary cookies.', + 'purposes' => 'Operating and providing the website, responding to contact requests.', + 'last_updated' => $today, + ], + ], + ]; +} + +/** + * Load site-wide settings for legal pages, merging with defaults. + */ +function load_site_settings(): array { + $defaults = default_site_settings(); + $path = __DIR__ . '/data/site.json'; + if (!file_exists($path)) { + return $defaults; + } + $raw = file_get_contents($path); + $data = json_decode($raw, true); + if (!is_array($data)) { + return $defaults; + } + return array_replace_recursive($defaults, $data); +} + +/** + * Persist site-wide settings for legal pages. + */ +function save_site_settings(array $settings): bool { + $path = __DIR__ . '/data/site.json'; + $json = json_encode($settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return (bool) file_put_contents($path, $json); +} + function slugify(string $text): string { $text = strtolower($text); $text = preg_replace('~[^a-z0-9]+~', '-', $text); diff --git a/impressum.php b/impressum.php new file mode 100644 index 0000000..e06aad9 --- /dev/null +++ b/impressum.php @@ -0,0 +1,84 @@ + $lang === 'de' ? 'Impressum' : 'Imprint', + 'owner' => $lang === 'de' ? 'Medieninhaber' : 'Media owner', + 'address' => $lang === 'de' ? 'Adresse' : 'Address', + 'contact' => $lang === 'de' ? 'Kontakt' : 'Contact', + 'legal' => $lang === 'de' ? 'Rechtliches' : 'Legal', + 'purpose' => $lang === 'de' ? 'Unternehmensgegenstand / Blattlinie' : 'Business purpose / editorial line', + 'authority' => $lang === 'de' ? 'Aufsichts-/Gewerbebehörde' : 'Supervisory authority', + 'membership' => $lang === 'de' ? 'WKO-Mitgliedschaft' : 'Chamber membership', + 'uid' => $lang === 'de' ? 'UID' : 'VAT ID', + 'odr' => $lang === 'de' ? 'Online-Streitbeilegung (ODR)' : 'Online dispute resolution (ODR)', + 'last_updated' => $lang === 'de' ? 'Stand' : 'Last updated', +]; + +function legal_item(string $label, ?string $value): void { + $value = trim((string) $value); + if ($value === '') { + return; + } + echo ''; +} +?> + + + + + + <?php echo e($t['title']); ?> | FlixCooks + + + + + +
+

+

+ + '; + } + legal_item($t['last_updated'], $imprint['last_updated'] ?? ''); + ?> + +
+ + + diff --git a/partials/footer.php b/partials/footer.php index 5f9e0f4..57f3830 100644 --- a/partials/footer.php +++ b/partials/footer.php @@ -1,9 +1,19 @@ +