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

5
templates/404.php Normal file
View File

@@ -0,0 +1,5 @@
<div class="error-page">
<h1>404</h1>
<p>Die angeforderte Seite wurde nicht gefunden.</p>
<a href="/" class="btn">&larr; Zurück zur Startseite</a>
</div>

View File

@@ -0,0 +1,22 @@
<article class="article-card">
<?php if (!empty($article['cover_image'])): ?>
<a href="/artikel/<?= e($article['slug']) ?>" class="card-image">
<img src="<?= e($article['cover_image']) ?>" alt="<?= e($article['title']) ?>" loading="lazy">
</a>
<?php endif; ?>
<div class="card-body">
<?php if (!empty($article['category_name'])): ?>
<a href="/kategorie/<?= e($article['category_slug']) ?>" class="card-category"><?= e($article['category_name']) ?></a>
<?php endif; ?>
<h2 class="card-title">
<a href="/artikel/<?= e($article['slug']) ?>"><?= e($article['title']) ?></a>
</h2>
<p class="card-excerpt">
<?= e($article['excerpt'] ?: excerpt($article['body'])) ?>
</p>
<div class="card-meta">
<time><?= format_date($article['published_at'] ?? $article['created_at']) ?></time>
<a href="/artikel/<?= e($article['slug']) ?>" class="read-more">Weiterlesen &rarr;</a>
</div>
</div>
</article>

5
templates/_footer.php Normal file
View File

@@ -0,0 +1,5 @@
<footer class="site-footer">
<div class="container">
<p>&copy; <?= date('Y') ?> <?= e(SITE_TITLE) ?>. Alle Rechte vorbehalten.</p>
</div>
</footer>

11
templates/_header.php Normal file
View File

@@ -0,0 +1,11 @@
<header class="site-header">
<div class="container header-inner">
<a href="/" class="site-logo"><?= e(SITE_TITLE) ?></a>
<button class="menu-toggle" id="menuToggle" aria-label="Menü">&#9776;</button>
<nav class="site-nav" id="siteNav">
<?php foreach ($navItems as $nav): ?>
<a href="<?= e($nav['url']) ?>"><?= e($nav['label']) ?></a>
<?php endforeach; ?>
</nav>
</div>
</header>

15
templates/_pagination.php Normal file
View File

@@ -0,0 +1,15 @@
<?php if ($pag['total_pages'] > 1): ?>
<nav class="pagination">
<?php if ($pag['current_page'] > 1): ?>
<a href="?page=<?= $pag['current_page'] - 1 ?>">&laquo; Zurück</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $pag['total_pages']; $i++): ?>
<a href="?page=<?= $i ?>" class="<?= $i === $pag['current_page'] ? 'active' : '' ?>"><?= $i ?></a>
<?php endfor; ?>
<?php if ($pag['current_page'] < $pag['total_pages']): ?>
<a href="?page=<?= $pag['current_page'] + 1 ?>">Weiter &raquo;</a>
<?php endif; ?>
</nav>
<?php endif; ?>

16
templates/archive.php Normal file
View File

@@ -0,0 +1,16 @@
<header class="page-header">
<h1>Archiv</h1>
<p class="page-description">Alle veröffentlichten Artikel</p>
</header>
<?php if (empty($articles)): ?>
<p class="empty-state">Noch keine Artikel veröffentlicht.</p>
<?php else: ?>
<div class="article-grid">
<?php foreach ($articles as $article): ?>
<?php include __DIR__ . '/_article-card.php'; ?>
<?php endforeach; ?>
</div>
<?php include __DIR__ . '/_pagination.php'; ?>
<?php endif; ?>

23
templates/article.php Normal file
View File

@@ -0,0 +1,23 @@
<article class="article-single">
<?php if (!empty($article['cover_image'])): ?>
<div class="article-cover">
<img src="<?= e($article['cover_image']) ?>" alt="<?= e($article['title']) ?>">
</div>
<?php endif; ?>
<header class="article-header">
<?php if (!empty($article['category_name'])): ?>
<a href="/kategorie/<?= e($article['category_slug']) ?>" class="article-category"><?= e($article['category_name']) ?></a>
<?php endif; ?>
<h1><?= e($article['title']) ?></h1>
<time class="article-date"><?= format_date($article['published_at'] ?? $article['created_at']) ?></time>
</header>
<div class="article-body prose">
<?= $article['body'] ?>
</div>
<footer class="article-footer">
<a href="/" class="back-link">&larr; Zurück zur Übersicht</a>
</footer>
</article>

18
templates/category.php Normal file
View File

@@ -0,0 +1,18 @@
<header class="page-header">
<h1>Kategorie: <?= e($category['name']) ?></h1>
<?php if ($category['description']): ?>
<p class="page-description"><?= e($category['description']) ?></p>
<?php endif; ?>
</header>
<?php if (empty($articles)): ?>
<p class="empty-state">Keine Artikel in dieser Kategorie.</p>
<?php else: ?>
<div class="article-grid">
<?php foreach ($articles as $article): ?>
<?php include __DIR__ . '/_article-card.php'; ?>
<?php endforeach; ?>
</div>
<?php include __DIR__ . '/_pagination.php'; ?>
<?php endif; ?>

25
templates/home.php Normal file
View File

@@ -0,0 +1,25 @@
<section class="hero">
<h1><?= e(SITE_TITLE) ?></h1>
<p class="hero-subtitle"><?= e(SITE_DESCRIPTION) ?></p>
</section>
<?php if (!empty($categories)): ?>
<div class="category-bar">
<?php foreach ($categories as $cat): ?>
<a href="/kategorie/<?= e($cat['slug']) ?>" class="category-tag"><?= e($cat['name']) ?></a>
<?php endforeach; ?>
<a href="/archiv" class="category-tag">Archiv</a>
</div>
<?php endif; ?>
<?php if (empty($articles)): ?>
<p class="empty-state">Noch keine Artikel veröffentlicht.</p>
<?php else: ?>
<div class="article-grid">
<?php foreach ($articles as $article): ?>
<?php include __DIR__ . '/_article-card.php'; ?>
<?php endforeach; ?>
</div>
<?php include __DIR__ . '/_pagination.php'; ?>
<?php endif; ?>

28
templates/layout.php Normal file
View File

@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($pageTitle ?? SITE_TITLE) ?></title>
<meta name="description" content="<?= e($metaDescription ?? SITE_DESCRIPTION) ?>">
<?php if (!empty($ogImage)): ?>
<meta property="og:image" content="<?= e($ogImage) ?>">
<?php endif; ?>
<meta property="og:title" content="<?= e($pageTitle ?? SITE_TITLE) ?>">
<meta property="og:type" content="website">
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
<?php include __DIR__ . '/_header.php'; ?>
<main class="site-main">
<div class="container">
<?= $content ?? '' ?>
</div>
</main>
<?php include __DIR__ . '/_footer.php'; ?>
<script src="/assets/js/main.js"></script>
</body>
</html>

6
templates/page.php Normal file
View File

@@ -0,0 +1,6 @@
<article class="static-page">
<h1><?= e($staticPage['title']) ?></h1>
<div class="prose">
<?= $staticPage['body'] ?>
</div>
</article>