added moderation portal with admin authentification and seperate styling

This commit is contained in:
2026-04-20 16:01:10 +02:00
parent 11a062dd84
commit 7dea362c89
7 changed files with 604 additions and 5 deletions

254
public/admin.php Normal file
View File

@@ -0,0 +1,254 @@
<?php
// =====================================================================
// Moderation Page
// Lists pending Contributions for Review. Moderators can approve
// or reject Contributions.
// ToDo: Extend with News Management, User Management, Analytics.
// =====================================================================
require_once __DIR__ . '/api/db.php';
require_once __DIR__ . '/api/auth.php';
// -----------------------------------------------------------------
// Routing: Login, Logout, or Main Page
// -----------------------------------------------------------------
$page = $_GET['page'] ?? 'main';
// Handle Login Form Submission
if ($page === 'login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['password'] ?? '';
if (admin_login($password)) {
header('Location: admin.php');
exit;
} else {
$login_error = 'Falsches Passwort.';
}
}
// Handle Logout
if ($page === 'logout') {
admin_logout();
header('Location: admin.php?page=login');
exit;
}
// -----------------------------------------------------------------
// Load Municipality for Theming (needed for both Login and Main Page)
// -----------------------------------------------------------------
$pdo = get_db();
$stmt = $pdo->prepare("SELECT * FROM municipalities WHERE slug = :slug");
$stmt->execute([':slug' => 'lohne']);
$municipality = $stmt->fetch();
// Show Login Page if not authenticated
if ($page === 'login' || !is_admin()) {
show_login_page($municipality, $login_error ?? null);
exit;
}
// -----------------------------------------------------------------
// Handle Moderation Actions (Approve / Reject)
// -----------------------------------------------------------------
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mod_action'])) {
$contribution_id = $_POST['contribution_id'] ?? '';
$mod_action = $_POST['mod_action'];
if ($contribution_id && in_array($mod_action, ['approved', 'rejected'])) {
$stmt = $pdo->prepare("UPDATE contributions SET status = :status WHERE contribution_id = :id");
$stmt->execute([':status' => $mod_action, ':id' => $contribution_id]);
}
// Redirects to prevent Form Resubmission on Refresh
header('Location: admin.php');
exit;
}
// -----------------------------------------------------------------
// Load Contributions Data
// -----------------------------------------------------------------
// Pending Contributions
$stmt = $pdo->prepare("
SELECT contribution_id, title, category, description, author_name, geom_type, status, created_at
FROM contributions
WHERE municipality_id = :mid AND status = 'pending'
ORDER BY created_at DESC
");
$stmt->execute([':mid' => $municipality['municipality_id']]);
$pending = $stmt->fetchAll();
// Recently moderated Contributions
$stmt = $pdo->prepare("
SELECT contribution_id, title, category, description, author_name, geom_type, status, created_at, updated_at
FROM contributions
WHERE municipality_id = :mid AND status IN ('approved', 'rejected')
ORDER BY updated_at DESC
LIMIT 20
");
$stmt->execute([':mid' => $municipality['municipality_id']]);
$moderated = $stmt->fetchAll();
// Statistics
$stmt = $pdo->prepare("
SELECT status, COUNT(*) as count
FROM contributions
WHERE municipality_id = :mid
GROUP BY status
");
$stmt->execute([':mid' => $municipality['municipality_id']]);
$stats_rows = $stmt->fetchAll();
$stats = [];
foreach ($stats_rows as $row) {
$stats[$row['status']] = $row['count'];
}
// -----------------------------------------------------------------
// Render Main Page
// -----------------------------------------------------------------
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Moderation — <?= htmlspecialchars($municipality['name']) ?></title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="admin.css">
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>
</head>
<body>
<div class="admin-header">
<h1><i class="fa-solid fa-shield-halved"></i> Moderation — <?= htmlspecialchars($municipality['name']) ?></h1>
<div class="admin-nav">
<a href="index.php"><i class="fa-solid fa-map"></i> Zur Karte</a>
<a href="admin.php?page=logout"><i class="fa-solid fa-right-from-bracket"></i> Abmelden</a>
</div>
</div>
<div class="admin-container">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number"><?= ($stats['pending'] ?? 0) ?></div>
<div class="stat-label">Ausstehend</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= ($stats['approved'] ?? 0) ?></div>
<div class="stat-label">Freigegeben</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= ($stats['rejected'] ?? 0) ?></div>
<div class="stat-label">Abgelehnt</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= array_sum(array_column($stats_rows, 'count')) ?></div>
<div class="stat-label">Gesamt</div>
</div>
</div>
<div class="section">
<h2><i class="fa-solid fa-clock"></i> Ausstehende Beiträge (<?= count($pending) ?>)</h2>
<?php if (empty($pending)): ?>
<div class="empty-state">
<i class="fa-solid fa-check-circle" style="font-size:2rem;margin-bottom:8px;display:block;"></i>
Keine ausstehenden Beiträge.
</div>
<?php else: ?>
<?php foreach ($pending as $item): ?>
<div class="contribution-row">
<div class="contribution-info">
<div class="title"><?= htmlspecialchars($item['title']) ?></div>
<div class="meta">
<span class="badge badge-<?= $item['geom_type'] ?>"><?= $item['geom_type'] ?></span>
<span class="badge badge-pending">ausstehend</span>
· <?= htmlspecialchars($item['category']) ?>
· <?= htmlspecialchars($item['author_name']) ?>
· <?= date('d.m.Y H:i', strtotime($item['created_at'])) ?>
</div>
<?php if ($item['description']): ?>
<div class="description"><?= htmlspecialchars($item['description']) ?></div>
<?php endif; ?>
</div>
<div class="action-buttons">
<form method="POST">
<input type="hidden" name="contribution_id" value="<?= $item['contribution_id'] ?>">
<input type="hidden" name="mod_action" value="approved">
<button type="submit" class="btn btn-approve"><i class="fa-solid fa-check"></i> Freigeben</button>
</form>
<form method="POST">
<input type="hidden" name="contribution_id" value="<?= $item['contribution_id'] ?>">
<input type="hidden" name="mod_action" value="rejected">
<button type="submit" class="btn btn-reject"><i class="fa-solid fa-xmark"></i> Ablehnen</button>
</form>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
<div class="section">
<h2><i class="fa-solid fa-history"></i> Kürzlich moderiert</h2>
<?php if (empty($moderated)): ?>
<div class="empty-state">Noch keine moderierten Beiträge.</div>
<?php else: ?>
<?php foreach ($moderated as $item): ?>
<div class="contribution-row">
<div class="contribution-info">
<div class="title"><?= htmlspecialchars($item['title']) ?></div>
<div class="meta">
<span class="badge badge-<?= $item['geom_type'] ?>"><?= $item['geom_type'] ?></span>
<span class="badge badge-<?= $item['status'] ?>"><?= $item['status'] === 'approved' ? 'freigegeben' : 'abgelehnt' ?></span>
· <?= htmlspecialchars($item['category']) ?>
· <?= htmlspecialchars($item['author_name']) ?>
· <?= date('d.m.Y H:i', strtotime($item['created_at'])) ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
</body>
</html>
<?php
// -----------------------------------------------------------------
// Login Page
// -----------------------------------------------------------------
function show_login_page($municipality, $error = null) {
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Moderation — Anmeldung</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="admin.css">
<style>:root { --color-primary: <?= htmlspecialchars($municipality['primary_color']) ?>; }</style>
</head>
<body>
<div class="login-wrapper">
<div class="login-box">
<h1><i class="fa-solid fa-shield-halved"></i> Moderation</h1>
<p>Bitte geben Sie das Moderationspasswort ein.</p>
<?php if ($error): ?>
<div class="error"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<form method="POST" action="admin.php?page=login">
<input type="password" name="password" placeholder="Passwort" autofocus>
<button type="submit"><i class="fa-solid fa-right-to-bracket"></i> Anmelden</button>
</form>
<div class="back-link"><a href="index.php">← Zurück zur Karte</a></div>
</div>
</div>
</body>
</html>
<?php
}
?>