Initiales CMS: Deutschsprachiges Blog-System mit Admin-Bereich

Vollständiges, schlankes PHP/SQLite-CMS für IT-, KI- und Gaming-Inhalte:

- Core: DB-Singleton, Auth mit Passwort-Hashing, Session-Cookies,
  CSRF-Schutz, Login-Rate-Limit, Bild-Upload mit serverseitiger Validierung
- Admin: Dashboard, Artikel/Seiten-Verwaltung mit Quill WYSIWYG-Editor,
  Kategorien, Navigation (Drag & Drop), Medienbibliothek, Profil
- Frontend: Responsive Dark-Theme, Artikel-Grid, Kategorie-Filter,
  Archiv, Paginierung, SEO-Meta-Tags
- Sicherheit: Prepared Statements, HTML-Sanitizer, .htaccess-Schutz
  für sensible Verzeichnisse, PHP-Ausführungsschutz im Upload-Ordner
- Installation: install.php erstellt DB-Schema und Admin-Account

https://claude.ai/code/session_01Xsg4j2t4S9goMuWVpF3ezG
This commit is contained in:
Claude
2026-04-05 20:59:52 +00:00
commit 3c97192386
45 changed files with 2839 additions and 0 deletions

212
admin/navigation.php Normal file
View File

@@ -0,0 +1,212 @@
<?php
require_once __DIR__ . '/../core/auth.php';
auth_start_session();
auth_require_login();
$pdo = db();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_verify();
// Reihenfolge per AJAX aktualisieren
if (isset($_POST['reorder'])) {
$order = json_decode($_POST['reorder'], true);
if (is_array($order)) {
$stmt = $pdo->prepare('UPDATE navigation SET sort_order = ? WHERE id = ?');
foreach ($order as $pos => $navId) {
$stmt->execute([$pos, (int) $navId]);
}
}
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])) {
header('Content-Type: application/json');
echo json_encode(['success' => true]);
exit;
}
flash('success', 'Reihenfolge aktualisiert.');
redirect('/admin/navigation.php');
}
if (isset($_POST['delete_id'])) {
$stmt = $pdo->prepare('DELETE FROM navigation WHERE id = ?');
$stmt->execute([(int) $_POST['delete_id']]);
flash('success', 'Navigationspunkt gelöscht.');
redirect('/admin/navigation.php');
}
if (isset($_POST['save'])) {
$navId = !empty($_POST['nav_id']) ? (int) $_POST['nav_id'] : null;
$label = trim($_POST['label'] ?? '');
$type = $_POST['type'] ?? 'url';
$target = trim($_POST['target'] ?? '');
$sortOrder = (int) ($_POST['sort_order'] ?? 0);
if (!in_array($type, ['url', 'page', 'category', 'home'])) {
$type = 'url';
}
if ($label === '') {
flash('error', 'Bezeichnung ist erforderlich.');
} else {
if ($navId) {
$stmt = $pdo->prepare('UPDATE navigation SET label=?, type=?, target=?, sort_order=? WHERE id=?');
$stmt->execute([$label, $type, $target, $sortOrder, $navId]);
flash('success', 'Navigationspunkt aktualisiert.');
} else {
$maxOrder = (int) $pdo->query('SELECT COALESCE(MAX(sort_order),0) FROM navigation')->fetchColumn();
$stmt = $pdo->prepare('INSERT INTO navigation (label, type, target, sort_order) VALUES (?, ?, ?, ?)');
$stmt->execute([$label, $type, $target, $maxOrder + 1]);
flash('success', 'Navigationspunkt erstellt.');
}
}
redirect('/admin/navigation.php');
}
}
$navItems = $pdo->query('SELECT * FROM navigation ORDER BY sort_order')->fetchAll();
$allPages = $pdo->query("SELECT id, title FROM pages WHERE status='published' ORDER BY title")->fetchAll();
$allCategories = $pdo->query('SELECT id, name FROM categories ORDER BY sort_order, name')->fetchAll();
$editNav = null;
if (isset($_GET['edit'])) {
$stmt = $pdo->prepare('SELECT * FROM navigation WHERE id = ?');
$stmt->execute([(int) $_GET['edit']]);
$editNav = $stmt->fetch();
}
$pageTitle = 'Navigation';
$currentPage = 'navigation';
ob_start();
?>
<div class="card">
<h3><?= $editNav ? 'Navigationspunkt bearbeiten' : 'Neuer Navigationspunkt' ?></h3>
<form method="post" class="inline-edit-form">
<?= csrf_field() ?>
<?php if ($editNav): ?>
<input type="hidden" name="nav_id" value="<?= $editNav['id'] ?>">
<?php endif; ?>
<div class="form-row-inline">
<div class="form-group">
<label for="label">Bezeichnung</label>
<input type="text" id="label" name="label" required
value="<?= e($editNav['label'] ?? '') ?>">
</div>
<div class="form-group">
<label for="type">Typ</label>
<select id="type" name="type" onchange="updateTargetField(this.value)">
<option value="home" <?= ($editNav['type'] ?? '') === 'home' ? 'selected' : '' ?>>Startseite</option>
<option value="page" <?= ($editNav['type'] ?? '') === 'page' ? 'selected' : '' ?>>Seite</option>
<option value="category" <?= ($editNav['type'] ?? '') === 'category' ? 'selected' : '' ?>>Kategorie</option>
<option value="url" <?= ($editNav['type'] ?? '') === 'url' ? 'selected' : '' ?>>URL</option>
</select>
</div>
<div class="form-group" id="targetGroup">
<label for="target">Ziel</label>
<input type="text" id="targetInput" name="target"
value="<?= e($editNav['target'] ?? '') ?>" placeholder="URL eingeben">
<select id="targetPageSelect" name="target_page" style="display:none">
<option value=""> Seite wählen </option>
<?php foreach ($allPages as $p): ?>
<option value="<?= $p['id'] ?>" <?= ($editNav['target'] ?? '') == $p['id'] ? 'selected' : '' ?>>
<?= e($p['title']) ?>
</option>
<?php endforeach; ?>
</select>
<select id="targetCatSelect" name="target_category" style="display:none">
<option value=""> Kategorie wählen </option>
<?php foreach ($allCategories as $c): ?>
<option value="<?= $c['id'] ?>" <?= ($editNav['target'] ?? '') == $c['id'] ? 'selected' : '' ?>>
<?= e($c['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>&nbsp;</label>
<button type="submit" name="save" value="1" class="btn btn-primary">Speichern</button>
<?php if ($editNav): ?>
<a href="/admin/navigation.php" class="btn">Abbrechen</a>
<?php endif; ?>
</div>
</div>
</form>
</div>
<div class="card">
<h3>Navigationspunkte</h3>
<?php if (empty($navItems)): ?>
<p class="empty-state">Keine Navigationspunkte vorhanden.</p>
<?php else: ?>
<table class="table" id="navTable">
<thead>
<tr><th>Reihenfolge</th><th>Bezeichnung</th><th>Typ</th><th>Ziel</th><th>Aktionen</th></tr>
</thead>
<tbody id="navBody">
<?php foreach ($navItems as $nav): ?>
<tr data-id="<?= $nav['id'] ?>">
<td class="drag-handle" style="cursor:grab">&#9776; <?= $nav['sort_order'] ?></td>
<td><?= e($nav['label']) ?></td>
<td><?= e($nav['type']) ?></td>
<td><?= e($nav['target']) ?></td>
<td class="actions">
<a href="?edit=<?= $nav['id'] ?>" class="btn btn-sm">Bearbeiten</a>
<form method="post" class="inline-form" onsubmit="return confirm('Wirklich löschen?')">
<?= csrf_field() ?>
<input type="hidden" name="delete_id" value="<?= $nav['id'] ?>">
<button type="submit" class="btn btn-sm btn-danger">Löschen</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<?php
$content = ob_get_clean();
$extraScripts = '
<script>
function updateTargetField(type) {
var input = document.getElementById("targetInput");
var pageSelect = document.getElementById("targetPageSelect");
var catSelect = document.getElementById("targetCatSelect");
input.style.display = "none"; pageSelect.style.display = "none"; catSelect.style.display = "none";
input.name = ""; pageSelect.name = ""; catSelect.name = "";
if (type === "url") { input.style.display = ""; input.name = "target"; }
else if (type === "page") { pageSelect.style.display = ""; pageSelect.name = "target"; }
else if (type === "category") { catSelect.style.display = ""; catSelect.name = "target"; }
else { input.value = ""; input.name = "target"; }
}
document.addEventListener("DOMContentLoaded", function() {
updateTargetField(document.getElementById("type").value);
// Drag & Drop Sortierung
var tbody = document.getElementById("navBody");
if (!tbody) return;
var dragEl = null;
tbody.querySelectorAll("tr").forEach(function(row) {
row.draggable = true;
row.addEventListener("dragstart", function(e) { dragEl = row; row.style.opacity = "0.4"; });
row.addEventListener("dragend", function() { row.style.opacity = "1"; });
row.addEventListener("dragover", function(e) { e.preventDefault(); });
row.addEventListener("drop", function(e) {
e.preventDefault();
if (dragEl !== row) { tbody.insertBefore(dragEl, row); saveOrder(); }
});
});
function saveOrder() {
var ids = [];
tbody.querySelectorAll("tr").forEach(function(r) { ids.push(r.dataset.id); });
var formData = new FormData();
formData.append("reorder", JSON.stringify(ids));
formData.append("csrf_token", "<?= csrf_token() ?>");
fetch("/admin/navigation.php", {
method: "POST", body: formData,
headers: {"X-Requested-With": "XMLHttpRequest"}
});
}
});
</script>
';
include __DIR__ . '/templates/layout.php';