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
213 lines
9.2 KiB
PHP
213 lines
9.2 KiB
PHP
<?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';
|