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
108 lines
3.0 KiB
PHP
108 lines
3.0 KiB
PHP
<?php
|
|
/**
|
|
* Hilfsfunktionen
|
|
*/
|
|
|
|
function e(string $str): string
|
|
{
|
|
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function slugify(string $str): string
|
|
{
|
|
$replacements = [
|
|
'ä' => 'ae', 'ö' => 'oe', 'ü' => 'ue', 'ß' => 'ss',
|
|
'Ä' => 'ae', 'Ö' => 'oe', 'Ü' => 'ue',
|
|
];
|
|
$str = str_replace(array_keys($replacements), array_values($replacements), $str);
|
|
$str = mb_strtolower($str, 'UTF-8');
|
|
$str = preg_replace('/[^a-z0-9\s-]/', '', $str);
|
|
$str = preg_replace('/[\s-]+/', '-', $str);
|
|
return trim($str, '-');
|
|
}
|
|
|
|
function redirect(string $url): never
|
|
{
|
|
header('Location: ' . $url);
|
|
exit;
|
|
}
|
|
|
|
function flash(string $type, string $message): void
|
|
{
|
|
$_SESSION['flash'][] = ['type' => $type, 'message' => $message];
|
|
}
|
|
|
|
function flash_display(): string
|
|
{
|
|
if (empty($_SESSION['flash'])) {
|
|
return '';
|
|
}
|
|
$html = '';
|
|
foreach ($_SESSION['flash'] as $msg) {
|
|
$cls = e($msg['type']);
|
|
$text = e($msg['message']);
|
|
$html .= "<div class=\"flash flash-{$cls}\">{$text}</div>";
|
|
}
|
|
$_SESSION['flash'] = [];
|
|
return $html;
|
|
}
|
|
|
|
function paginate(int $total, int $page, int $perPage): array
|
|
{
|
|
$totalPages = max(1, (int) ceil($total / $perPage));
|
|
$page = max(1, min($page, $totalPages));
|
|
$offset = ($page - 1) * $perPage;
|
|
return [
|
|
'offset' => $offset,
|
|
'limit' => $perPage,
|
|
'total_pages' => $totalPages,
|
|
'current_page' => $page,
|
|
'total' => $total,
|
|
];
|
|
}
|
|
|
|
function format_date(string $datetime): string
|
|
{
|
|
$months = [
|
|
1 => 'Januar', 2 => 'Februar', 3 => 'März', 4 => 'April',
|
|
5 => 'Mai', 6 => 'Juni', 7 => 'Juli', 8 => 'August',
|
|
9 => 'September', 10 => 'Oktober', 11 => 'November', 12 => 'Dezember',
|
|
];
|
|
$ts = strtotime($datetime);
|
|
if ($ts === false) {
|
|
return $datetime;
|
|
}
|
|
$day = (int) date('j', $ts);
|
|
$month = $months[(int) date('n', $ts)];
|
|
$year = date('Y', $ts);
|
|
return "{$day}. {$month} {$year}";
|
|
}
|
|
|
|
function excerpt(string $html, int $length = 200): string
|
|
{
|
|
$text = strip_tags($html);
|
|
if (mb_strlen($text) <= $length) {
|
|
return $text;
|
|
}
|
|
$truncated = mb_substr($text, 0, $length);
|
|
$lastSpace = mb_strrpos($truncated, ' ');
|
|
if ($lastSpace !== false) {
|
|
$truncated = mb_substr($truncated, 0, $lastSpace);
|
|
}
|
|
return $truncated . '…';
|
|
}
|
|
|
|
function sanitize_html(string $html): string
|
|
{
|
|
$allowed = '<p><br><strong><b><em><i><u><ul><ol><li><h2><h3><h4><a><img><blockquote><pre><code><hr><table><thead><tbody><tr><th><td>';
|
|
$html = strip_tags($html, $allowed);
|
|
|
|
// Event-Handler und javascript:-URLs entfernen
|
|
$html = preg_replace('/\bon\w+\s*=\s*["\'][^"\']*["\']/i', '', $html);
|
|
$html = preg_replace('/\bon\w+\s*=\s*\S+/i', '', $html);
|
|
$html = preg_replace('/href\s*=\s*["\']?\s*javascript\s*:[^"\'>\s]*/i', 'href="#"', $html);
|
|
$html = preg_replace('/src\s*=\s*["\']?\s*javascript\s*:[^"\'>\s]*/i', 'src=""', $html);
|
|
|
|
return $html;
|
|
}
|