[
'label' => 'Freizeitangebote',
'singular' => 'Freizeitangebot',
'main_file' => $dataDir . '/freizeit.json',
'pending_file' => $dataDir . '/freizeit_meldungen.json',
'download_name' => 'freizeit.json',
'prefix' => 'FR',
'default_category' => 'Freizeit & Erlebnis',
'categories' => [
'See & Baden',
'Museum & Kultur',
'Aussicht & Natur',
'Familie & Tiere',
'Sport & Aktiv',
'Freizeit & Erlebnis',
'Sonstiges'
]
],
'badeseen' => [
'label' => 'Badeseen & Bäder',
'singular' => 'Badesee / Bad',
'main_file' => $dataDir . '/badeseen.json',
'pending_file' => $dataDir . '/badeseen_meldungen.json',
'download_name' => 'badeseen.json',
'prefix' => 'BAD',
'default_category' => 'See & Baden',
'categories' => [
'Badesee',
'Freibad',
'Hallenbad',
'Therme',
'Naturbad',
'Strandbad',
'Wasserspielplatz',
'Sonstiges'
]
],
'grillplaetze' => [
'label' => 'Grillplätze',
'singular' => 'Grillplatz',
'main_file' => $dataDir . '/grillplaetze.json',
'pending_file' => $dataDir . '/grillplaetze_meldungen.json',
'download_name' => 'grillplaetze.json',
'prefix' => 'GRILL',
'default_category' => 'Grillplatz',
'categories' => [
'Grillplatz',
'Waldgrillplatz',
'Grillhütte',
'Feuerstelle',
'Picknickplatz',
'Familienplatz',
'Sonstiges'
]
]
];
$regionOptions = [
'Karlsruhe',
'Pforzheim',
'Landkreis Karlsruhe',
'Enzkreis',
'Sonstige angrenzende Region'
];
$areaTypeOptions = [
'Stadt',
'Landkreis',
'Gemeinde',
'Ortsteil',
'Naturraum',
'Sonstiges'
];
/* =========================
HELFER
========================= */
function esc(string $value): string {
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function ensureDir(string $dir): void {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
function defaultData(): array {
return [
'generated_on' => date('Y-m-d'),
'count' => 0,
'items' => []
];
}
function readJsonFile(string $file, bool $listOnly = false): array {
if (!file_exists($file)) {
return $listOnly ? [] : defaultData();
}
$raw = file_get_contents($file);
if ($raw === false || trim($raw) === '') {
return $listOnly ? [] : defaultData();
}
$json = json_decode($raw, true);
if (!is_array($json)) {
return $listOnly ? [] : defaultData();
}
if ($listOnly) {
if (isset($json['items']) && is_array($json['items'])) {
return $json['items'];
}
return array_values($json);
}
if (!isset($json['items']) || !is_array($json['items'])) {
$json = [
'generated_on' => $json['generated_on'] ?? date('Y-m-d'),
'count' => 0,
'items' => []
];
}
$json['items'] = array_values($json['items']);
$json['count'] = count($json['items']);
$json['generated_on'] = $json['generated_on'] ?? date('Y-m-d');
return $json;
}
function writeJsonFile(string $file, array $data): bool {
ensureDir(dirname($file));
$data['generated_on'] = date('Y-m-d');
$data['items'] = array_values($data['items'] ?? []);
$data['count'] = count($data['items']);
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return false;
}
return file_put_contents($file, $json . PHP_EOL, LOCK_EX) !== false;
}
function writePendingFile(string $file, array $items): bool {
ensureDir(dirname($file));
$items = array_values($items);
$json = json_encode($items, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return false;
}
return file_put_contents($file, $json . PHP_EOL, LOCK_EX) !== false;
}
function cleanString($value): string {
return trim((string)($value ?? ''));
}
function cleanFloat($value) {
$value = str_replace(',', '.', trim((string)($value ?? '')));
if ($value === '') {
return '';
}
return is_numeric($value) ? (float)$value : '';
}
function normalizeItem(array $item): array {
return [
'id' => cleanString($item['id'] ?? ''),
'name' => cleanString($item['name'] ?? ''),
'region' => cleanString($item['region'] ?? ''),
'area_type' => cleanString($item['area_type'] ?? ''),
'municipality' => cleanString($item['municipality'] ?? ''),
'category' => cleanString($item['category'] ?? ''),
'lat' => cleanFloat($item['lat'] ?? ''),
'lon' => cleanFloat($item['lon'] ?? ''),
'url' => cleanString($item['url'] ?? ''),
'short_report' => cleanString($item['short_report'] ?? ''),
'address' => cleanString($item['address'] ?? ''),
'source' => cleanString($item['source'] ?? ''),
'status' => cleanString($item['status'] ?? 'published'),
'created_at' => cleanString($item['created_at'] ?? ''),
'updated_at' => date('c')
];
}
function normalizePendingItem(array $item): array {
return [
'id' => cleanString($item['id'] ?? ''),
'name' => cleanString($item['name'] ?? ''),
'region' => cleanString($item['region'] ?? ''),
'area_type' => cleanString($item['area_type'] ?? ''),
'municipality' => cleanString($item['municipality'] ?? ''),
'category' => cleanString($item['category'] ?? ''),
'lat' => cleanFloat($item['lat'] ?? ''),
'lon' => cleanFloat($item['lon'] ?? ''),
'url' => cleanString($item['url'] ?? ''),
'short_report' => cleanString($item['short_report'] ?? ''),
'address' => cleanString($item['address'] ?? ''),
'contact_name' => cleanString($item['contact_name'] ?? ''),
'contact_email' => cleanString($item['contact_email'] ?? ''),
'message' => cleanString($item['message'] ?? ''),
'submitted_at' => cleanString($item['submitted_at'] ?? date('c')),
'status' => cleanString($item['status'] ?? 'neu')
];
}
function suggestNextId(array $items, string $prefix): string {
$max = 0;
foreach ($items as $item) {
$id = (string)($item['id'] ?? '');
if (stripos($id, $prefix) === 0 && preg_match('/(\d+)$/', $id, $m)) {
$max = max($max, (int)$m[1]);
}
}
return $prefix . str_pad((string)($max + 1), 3, '0', STR_PAD_LEFT);
}
function jsonResponse(array $payload, int $status = 200): void {
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function requireLoginAjax(): void {
if (empty($_SESSION['freizeit_admin_logged_in'])) {
jsonResponse([
'ok' => false,
'message' => 'Nicht angemeldet.'
], 401);
}
}
function validSection(string $key, array $sections): bool {
return isset($sections[$key]);
}
/* =========================
LOGIN / LOGOUT
========================= */
if (isset($_POST['login_action']) && $_POST['login_action'] === 'login') {
$user = cleanString($_POST['username'] ?? '');
$pass = (string)($_POST['password'] ?? '');
if (mb_strtolower($user) === mb_strtolower($adminUser) && $pass === $adminPass) {
$_SESSION['freizeit_admin_logged_in'] = true;
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
$loginError = 'Benutzername oder Passwort ist falsch.';
}
if (isset($_GET['logout'])) {
session_destroy();
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
exit;
}
/* =========================
AJAX API
========================= */
if (isset($_POST['ajax_action'])) {
requireLoginAjax();
$action = cleanString($_POST['ajax_action'] ?? '');
$sectionKey = cleanString($_POST['section'] ?? 'freizeit');
if (!validSection($sectionKey, $sections)) {
jsonResponse([
'ok' => false,
'message' => 'Ungültiger Bereich.'
], 400);
}
$section = $sections[$sectionKey];
if ($action === 'load_section') {
$main = readJsonFile($section['main_file'], false);
$pending = readJsonFile($section['pending_file'], true);
jsonResponse([
'ok' => true,
'section' => $sectionKey,
'main' => $main,
'pending' => array_values(array_map('normalizePendingItem', $pending)),
'next_id' => suggestNextId($main['items'], $section['prefix'])
]);
}
if ($action === 'save_main') {
$payload = $_POST['payload'] ?? '';
$decoded = json_decode((string)$payload, true);
if (!is_array($decoded)) {
jsonResponse([
'ok' => false,
'message' => 'JSON konnte nicht gelesen werden.'
], 400);
}
$items = $decoded['items'] ?? [];
if (!is_array($items)) {
$items = [];
}
$cleanItems = [];
foreach ($items as $item) {
if (is_array($item)) {
$cleanItems[] = normalizeItem($item);
}
}
$data = [
'generated_on' => date('Y-m-d'),
'count' => count($cleanItems),
'items' => $cleanItems
];
$ok = writeJsonFile($section['main_file'], $data);
jsonResponse([
'ok' => $ok,
'message' => $ok ? 'Bestand wurde gespeichert.' : 'Bestand konnte nicht gespeichert werden.',
'main' => $data
], $ok ? 200 : 500);
}
if ($action === 'approve_pending') {
$pendingIndex = (int)($_POST['pending_index'] ?? -1);
$editedRaw = $_POST['edited_item'] ?? '';
$pending = readJsonFile($section['pending_file'], true);
$main = readJsonFile($section['main_file'], false);
if (!isset($pending[$pendingIndex])) {
jsonResponse([
'ok' => false,
'message' => 'Meldung nicht gefunden.'
], 404);
}
$edited = json_decode((string)$editedRaw, true);
if (!is_array($edited)) {
$edited = $pending[$pendingIndex];
}
$item = normalizeItem($edited);
if ($item['id'] === '') {
$item['id'] = suggestNextId($main['items'], $section['prefix']);
}
if ($item['category'] === '') {
$item['category'] = $section['default_category'];
}
if ($item['area_type'] === '') {
$item['area_type'] = 'Freizeitangebot';
}
$item['status'] = 'published';
$item['source'] = 'Meldeformular';
$item['created_at'] = date('c');
$item['updated_at'] = date('c');
$main['items'][] = $item;
unset($pending[$pendingIndex]);
$pending = array_values($pending);
$okMain = writeJsonFile($section['main_file'], $main);
$okPending = writePendingFile($section['pending_file'], $pending);
jsonResponse([
'ok' => $okMain && $okPending,
'message' => ($okMain && $okPending) ? 'Meldung wurde freigegeben und übernommen.' : 'Beim Speichern ist ein Fehler aufgetreten.',
'main' => readJsonFile($section['main_file'], false),
'pending' => array_values(array_map('normalizePendingItem', $pending)),
'next_id' => suggestNextId(readJsonFile($section['main_file'], false)['items'], $section['prefix'])
], ($okMain && $okPending) ? 200 : 500);
}
if ($action === 'reject_pending') {
$pendingIndex = (int)($_POST['pending_index'] ?? -1);
$pending = readJsonFile($section['pending_file'], true);
if (!isset($pending[$pendingIndex])) {
jsonResponse([
'ok' => false,
'message' => 'Meldung nicht gefunden.'
], 404);
}
unset($pending[$pendingIndex]);
$pending = array_values($pending);
$ok = writePendingFile($section['pending_file'], $pending);
jsonResponse([
'ok' => $ok,
'message' => $ok ? 'Meldung wurde abgelehnt/entfernt.' : 'Meldung konnte nicht entfernt werden.',
'pending' => array_values(array_map('normalizePendingItem', $pending))
], $ok ? 200 : 500);
}
if ($action === 'bulk_import_pending') {
$pending = readJsonFile($section['pending_file'], true);
$main = readJsonFile($section['main_file'], false);
$approved = 0;
foreach ($pending as $pendingItem) {
if (!is_array($pendingItem)) {
continue;
}
$item = normalizeItem($pendingItem);
if ($item['name'] === '') {
continue;
}
if ($item['id'] === '') {
$item['id'] = suggestNextId($main['items'], $section['prefix']);
}
if ($item['category'] === '') {
$item['category'] = $section['default_category'];
}
$item['status'] = 'published';
$item['source'] = 'Meldeformular';
$item['created_at'] = date('c');
$item['updated_at'] = date('c');
$main['items'][] = $item;
$approved++;
}
$okMain = writeJsonFile($section['main_file'], $main);
$okPending = writePendingFile($section['pending_file'], []);
jsonResponse([
'ok' => $okMain && $okPending,
'message' => $approved . ' Meldungen wurden übernommen.',
'main' => readJsonFile($section['main_file'], false),
'pending' => []
], ($okMain && $okPending) ? 200 : 500);
}
jsonResponse([
'ok' => false,
'message' => 'Unbekannte Aktion.'
], 400);
}
/* =========================
LOGIN-SEITE
========================= */
$isLoggedIn = !empty($_SESSION['freizeit_admin_logged_in']);
?>
Freizeitdaten Verwaltung
Geschützter Bereich
Bitte Benutzername und Passwort eingeben, um die Freizeitdaten-Verwaltung zu öffnen.
Verwaltung für Freizeitangebote, Badeseen & Bäder sowie Grillplätze.
= esc($loginError) ?>
Freizeitdaten verwalten
Zentrale Verwaltung für Freizeitangebote, Badeseen & Bäder sowie Grillplätze. Du kannst bestehende Einträge bearbeiten und neue Meldungen aus dem Formular prüfen, freigeben oder ablehnen.
Bestand verwalten
Meldungen freigeben
JSON direkt speichern
Mehrere Bereiche
Aktiver Bereich
–
Bestand
0
Meldungen
0
Hinweise
0
Lade Daten …
Eintrag bearbeiten
Noch kein Eintrag gewählt
Wähle links einen Datensatz oder lege einen neuen Eintrag an.
Prüfung & Hinweise
Noch keine Prüfung durchgeführt.