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:
212
admin/navigation.php
Normal file
212
admin/navigation.php
Normal 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> </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">☰ <?= $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';
|
||||
Reference in New Issue
Block a user