Files
netwatch/app/lib/helpers.php
2026-02-13 11:03:51 +01:00

305 lines
7.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* app/lib/helpers.php
*
* Zentrale Hilfsfunktionen
* - Output-Escaping
* - Redirects
* - Flash-Messages
* - Request-Helper
* - Allgemeine Utilities
*
* KEINE Business-Logik
*/
/**
* Sitzungs-Keys
*/
const FLASH_SESSION_KEY = 'flash_messages';
/* =========================
* Output / Sicherheit
* ========================= */
/**
* HTML-Escaping
*
* @param string|null $value
* @return string
*/
function e(?string $value): string
{
if ($value === null) {
return '';
}
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
/* =========================
* Redirects
* ========================= */
/**
* HTTP Redirect
*
* @param string $url
* @param int $code
*/
function redirect(string $url, int $code = 302): void
{
if (!headers_sent()) {
header('Location: ' . $url, true, $code);
}
exit;
}
/* =========================
* Flash Messages
* ========================= */
/**
* Flash-Message setzen
*
* @param string $type (success, error, info)
* @param string $message
*/
function flash(string $type, string $message): void
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
if (!isset($_SESSION[FLASH_SESSION_KEY])) {
$_SESSION[FLASH_SESSION_KEY] = [];
}
$_SESSION[FLASH_SESSION_KEY][] = ['type' => $type, 'message' => $message];
}
/**
* Flash-Messages abrufen & löschen
*
* @return array
*/
function getFlashes(): array
{
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
}
$messages = $_SESSION[FLASH_SESSION_KEY] ?? [];
unset($_SESSION[FLASH_SESSION_KEY]);
return $messages;
}
/* =========================
* Request Helper
* ========================= */
/**
* POST-Wert holen
*
* @param string $key
* @param mixed $default
* @return mixed
*/
function post(string $key, $default = null)
{
return $_POST[$key] ?? $default;
}
/**
* GET-Wert holen
*
* @param string $key
* @param mixed $default
* @return mixed
*/
function get(string $key, $default = null)
{
return $_GET[$key] ?? $default;
}
/**
* Prüfen, ob Request POST ist
*
* @return bool
*/
function isPost(): bool
{
return ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST';
}
/* =========================
* Validierung
* ========================= */
/**
* Prüft auf leeren Wert
*
* @param mixed $value
* @return bool
*/
function isEmpty($value): bool
{
if (is_string($value)) {
return trim($value) === '';
}
return empty($value);
}
/* =========================
* Pfade / URLs
* ========================= */
/**
* Baut eine URL zur App
*
* @param string $path
* @return string
*/
function url(string $path = ''): string
{
if ($path === '') {
$path = '/';
}
if (preg_match('~^([a-z]+:)?//~i', $path)) {
return $path;
}
$script = $_SERVER['SCRIPT_NAME'] ?? '';
$baseDir = rtrim(strtr(dirname($script), '\\\\', '/'), '/');
if ($baseDir === '.' || $baseDir === '\\\\') {
$baseDir = '';
}
$segment = ltrim($path, '/');
$prefix = $baseDir === '' ? '' : $baseDir;
if ($segment === '') {
return $prefix === '' ? '/' : $prefix;
}
return ($prefix === '' ? '' : $prefix) . '/' . $segment;
}
/* =========================
* Debug / Entwicklung
* ========================= */
/**
* Dump & Die (nur Dev)
*
* @param mixed $value
*/
function dd($value): void
{
echo '<pre>';
var_dump($value);
echo '</pre>';
exit;
}
/**
* Zeigt eine sauber gestaltete Fehlerseite für 40x-Status-Codes mit Erklärung.
*
* @param int $statusCode Client-Error-Status (400499)
* @param string $explanation Optionale Zusatzinfo zur Problemursache
* @param string[] $tips Handlungsvorschläge für Benutzer (optional)
*/
function renderClientError(int $statusCode, string $explanation = '', array $tips = []): void
{
if ($statusCode < 400 || $statusCode >= 500) {
$statusCode = 400;
}
$reasons = [
400 => 'Ungültige Anfrage',
401 => 'Nicht authentifiziert',
403 => 'Zugriff verweigert',
404 => 'Seite nicht gefunden',
405 => 'Methode nicht erlaubt',
408 => 'Anfrage abgelaufen',
429 => 'Zu viele Anfragen',
];
$reason = $reasons[$statusCode] ?? 'Clientseitiger Fehler';
$defaultExplanation = match ($statusCode) {
400 => 'Die Anfrage konnte aufgrund fehlender oder falscher Daten nicht verstanden werden.',
401 => 'Bitte melden Sie sich an oder verwenden gültige Zugangsdaten.',
403 => 'Sie besitzen keinen Zugriff auf diesen Bereich.',
404 => 'Die angeforderte Ressource existiert nicht oder wurde verschoben.',
405 => 'Diese Aktion ist auf dem Server nicht erlaubt.',
408 => 'Die Anfrage hat zu lange gedauert; bitte erneut versuchen.',
429 => 'Sie senden zu viele Anfragen in kurzer Zeit.',
default => 'Die Anfrage kann nicht verarbeitet werden; überprüfen Sie die Eingaben.',
};
$message = $explanation !== '' ? $explanation : $defaultExplanation;
http_response_code($statusCode);
$css = <<<CSS
body { font-family: 'Segoe UI', system-ui, sans-serif; margin: 0; background: #0b1220; color: #e0e7ff; }
.page { min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 2rem; }
.card { max-width: 640px; background: linear-gradient(145deg, #16213d, #0d0f1f); border: 1px solid rgba(224,231,255,0.2); border-radius: 1rem; box-shadow: 0 20px 45px rgba(0,0,0,0.45); padding: 2rem; }
h1 { margin: 0 0 0.5rem; font-size: clamp(2.5rem, 3vw, 3.5rem); }
p { margin: 0 0 1rem; line-height: 1.6; color: rgba(224,231,255,0.9); }
.badge { display: inline-flex; align-items: center; padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.85rem; background: rgba(255,255,255,0.08); color: #a5b4fc; margin-bottom: 1rem; }
ul { margin: 1rem 0 0; padding-left: 1.25rem; color: rgba(224,231,255,0.85); }
a { color: #7dd3fc; text-decoration: none; }
a:hover { text-decoration: underline; }
CSS;
$rendered = strtr(<<<'HTML'
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Fehler {{code}}</title>
<style>{{css}}</style>
</head>
<body>
<div class="page">
<section class="card">
<div class="badge">{{status}}</div>
<h1>{{code}} · {{reason}}</h1>
<p>{{message}}</p>
{{tips}}
<p>Zurück zur Startseite: <a href="/">Dashboard</a></p>
</section>
</div>
</body>
</html>
HTML, [
'{{code}}' => e((string)$statusCode),
'{{reason}}' => e($reason),
'{{status}}' => e(sprintf('%d Fehler', $statusCode)),
'{{message}}' => e($message),
'{{css}}' => $css,
'{{tips}}' => count($tips) === 0
? ''
: '<ul>' . implode('', array_map(fn($tip) => '<li>' . e($tip) . '</li>', $tips)) . '</ul>',
]);
echo $rendered;
exit;
}
/* =========================
* Sonstiges
* ========================= */
// TODO: Weitere Helfer nach Bedarf
// - Datum formatieren
// - Bytes → MB
// - UUID erzeugen
// - SVG-Koordinaten normalisieren