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
170 lines
6.4 KiB
PHP
170 lines
6.4 KiB
PHP
<?php
|
|
require_once __DIR__ . '/../core/auth.php';
|
|
auth_start_session();
|
|
auth_require_login();
|
|
|
|
$pdo = db();
|
|
$id = isset($_GET['id']) ? (int) $_GET['id'] : null;
|
|
$page = null;
|
|
|
|
if ($id) {
|
|
$stmt = $pdo->prepare('SELECT * FROM pages WHERE id = ?');
|
|
$stmt->execute([$id]);
|
|
$page = $stmt->fetch();
|
|
if (!$page) {
|
|
flash('error', 'Seite nicht gefunden.');
|
|
redirect('/admin/pages.php');
|
|
}
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
csrf_verify();
|
|
|
|
$title = trim($_POST['title'] ?? '');
|
|
$slug = trim($_POST['slug'] ?? '');
|
|
$body = $_POST['body'] ?? '';
|
|
$status = in_array($_POST['status'] ?? '', ['draft', 'published']) ? $_POST['status'] : 'draft';
|
|
|
|
$errors = [];
|
|
if ($title === '') {
|
|
$errors[] = 'Titel ist erforderlich.';
|
|
}
|
|
if ($slug === '') {
|
|
$slug = slugify($title);
|
|
} else {
|
|
$slug = slugify($slug);
|
|
}
|
|
|
|
$slugCheck = $pdo->prepare('SELECT id FROM pages WHERE slug = ? AND id != ?');
|
|
$slugCheck->execute([$slug, $id ?? 0]);
|
|
if ($slugCheck->fetch()) {
|
|
$errors[] = 'Dieser Slug wird bereits verwendet.';
|
|
}
|
|
|
|
$body = sanitize_html($body);
|
|
|
|
if (empty($errors)) {
|
|
if ($id) {
|
|
$stmt = $pdo->prepare("UPDATE pages SET title=?, slug=?, body=?, status=?, updated_at=datetime('now') WHERE id=?");
|
|
$stmt->execute([$title, $slug, $body, $status, $id]);
|
|
flash('success', 'Seite aktualisiert.');
|
|
} else {
|
|
$stmt = $pdo->prepare('INSERT INTO pages (title, slug, body, status) VALUES (?, ?, ?, ?)');
|
|
$stmt->execute([$title, $slug, $body, $status]);
|
|
$id = $pdo->lastInsertId();
|
|
flash('success', 'Seite erstellt.');
|
|
}
|
|
redirect('/admin/page-edit.php?id=' . $id);
|
|
} else {
|
|
foreach ($errors as $err) {
|
|
flash('error', $err);
|
|
}
|
|
}
|
|
}
|
|
|
|
$pageTitle = $page ? 'Seite bearbeiten' : 'Neue Seite';
|
|
$currentPage = 'pages';
|
|
|
|
$extraHead = '<link href="https://cdn.quilljs.com/1.3.7/quill.snow.css" rel="stylesheet">';
|
|
|
|
ob_start();
|
|
?>
|
|
<form method="post" class="edit-form" id="pageForm">
|
|
<?= csrf_field() ?>
|
|
|
|
<div class="form-row">
|
|
<div class="form-col-8">
|
|
<div class="form-group">
|
|
<label for="title">Titel</label>
|
|
<input type="text" id="title" name="title" required
|
|
value="<?= e($page['title'] ?? $_POST['title'] ?? '') ?>">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="slug">Slug</label>
|
|
<input type="text" id="slug" name="slug"
|
|
value="<?= e($page['slug'] ?? $_POST['slug'] ?? '') ?>"
|
|
placeholder="Wird automatisch generiert">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Inhalt</label>
|
|
<div id="editor"><?= $page['body'] ?? $_POST['body'] ?? '' ?></div>
|
|
<textarea name="body" id="bodyHidden" style="display:none"><?= e($page['body'] ?? $_POST['body'] ?? '') ?></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="form-col-4">
|
|
<div class="card sidebar-card">
|
|
<h4>Status</h4>
|
|
<div class="form-group">
|
|
<label>
|
|
<input type="radio" name="status" value="draft"
|
|
<?= ($page['status'] ?? 'draft') === 'draft' ? 'checked' : '' ?>>
|
|
Entwurf
|
|
</label>
|
|
<label>
|
|
<input type="radio" name="status" value="published"
|
|
<?= ($page['status'] ?? '') === 'published' ? 'checked' : '' ?>>
|
|
Veröffentlicht
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-block">Speichern</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<?php
|
|
$content = ob_get_clean();
|
|
|
|
$extraScripts = '
|
|
<script src="https://cdn.quilljs.com/1.3.7/quill.min.js"></script>
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
var quill = new Quill("#editor", {
|
|
theme: "snow",
|
|
modules: {
|
|
toolbar: {
|
|
container: [
|
|
[{"header": [2, 3, 4, false]}],
|
|
["bold", "italic", "underline"],
|
|
[{"list": "ordered"}, {"list": "bullet"}],
|
|
["blockquote", "code-block"],
|
|
["link", "image"],
|
|
["clean"]
|
|
],
|
|
handlers: {
|
|
image: function() {
|
|
var input = document.createElement("input");
|
|
input.setAttribute("type", "file");
|
|
input.setAttribute("accept", "image/*");
|
|
input.click();
|
|
input.onchange = function() {
|
|
var file = input.files[0];
|
|
if (!file) return;
|
|
var formData = new FormData();
|
|
formData.append("image", file);
|
|
formData.append("csrf_token", document.querySelector("[name=csrf_token]").value);
|
|
fetch("/admin/upload-handler.php", {method:"POST", body:formData})
|
|
.then(function(r){return r.json();})
|
|
.then(function(data){
|
|
if(data.success){var range=quill.getSelection(true);quill.insertEmbed(range.index,"image",data.url);}
|
|
else{alert(data.error||"Upload fehlgeschlagen");}
|
|
}).catch(function(){alert("Upload fehlgeschlagen");});
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
document.getElementById("pageForm").addEventListener("submit", function(){
|
|
document.getElementById("bodyHidden").value = quill.root.innerHTML;
|
|
});
|
|
var titleEl=document.getElementById("title"),slugEl=document.getElementById("slug"),slugManual=slugEl.value!=="";
|
|
slugEl.addEventListener("input",function(){slugManual=slugEl.value!=="";});
|
|
titleEl.addEventListener("input",function(){
|
|
if(!slugManual){slugEl.value=titleEl.value.toLowerCase().replace(/[äÄ]/g,"ae").replace(/[öÖ]/g,"oe").replace(/[üÜ]/g,"ue").replace(/ß/g,"ss").replace(/[^a-z0-9\\s-]/g,"").replace(/[\\s-]+/g,"-").replace(/^-|-$/g,"");}
|
|
});
|
|
});
|
|
</script>
|
|
';
|
|
|
|
include __DIR__ . '/templates/layout.php';
|