Backup #1676

ID1676
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Framework/Controller.php
Version12
Typ modified
Größe7.6 KB
Hash8efdc6ee435b540da01645452ade79ad9701930e3f527a34f82a0411df88fa2d
Datum2025-12-27 11:45:58
Geändert vonclaude-code-hook
GrundClaude Code Pre-Hook Backup vor Edit-Operation
Datei existiert Ja

Dateiinhalt

<?php

declare(strict_types=1);

namespace Framework;

// @responsibility: Basis-Controller mit CSRF, JSON-Response, View-Rendering

abstract class Controller
{
    protected function csrfToken(): string
    {
        return CsrfService::getToken();
    }

    protected function csrfField(): string
    {
        return CsrfService::getTokenField();
    }

    protected function validateCsrf(): bool
    {
        $token = $_POST['_csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;

        return CsrfService::validateToken($token);
    }

    protected function requireCsrf(): void
    {
        if (!$this->validateCsrf()) {
            if ($this->isJsonRequest()) {
                $this->json(['error' => 'CSRF token invalid'], 403);
            } else {
                $this->text('CSRF token invalid', 403);
            }
            exit;
        }
    }

    private function isJsonRequest(): bool
    {
        $accept = $_SERVER['HTTP_ACCEPT'] ?? '';
        $contentType = $_SERVER['CONTENT_TYPE'] ?? '';

        return str_contains($accept, 'application/json') || str_contains($contentType, 'application/json');
    }

    protected function view(string $name, array $data = []): void
    {
        $data['csrfField'] = $this->csrfField();
        $data['csrfToken'] = $this->csrfToken();
        $__viewPath = VIEW_PATH . '/' . str_replace('.', '/', $name) . '.php';
        extract($data);

        if (file_exists($__viewPath)) {
            require $__viewPath;
        } else {
            throw new \Exception("View not found: {$name}");
        }
    }

    protected function json(mixed $data, int $status = 200): void
    {
        http_response_code($status);
        header('Content-Type: application/json');
        echo json_encode($data, JSON_UNESCAPED_UNICODE);
    }

    protected function redirect(string $url, int $status = 302): never
    {
        http_response_code($status);
        header("Location: {$url}");
        exit;
    }

    /**
     * Render a partial template (for HTMX responses).
     *
     * @param string $name Partial name (e.g., 'chat/message' or 'content/version')
     * @param array<string, mixed> $data Data to pass to partial
     */
    protected function partial(string $name, array $data = []): void
    {
        $data['csrfField'] = $this->csrfField();
        $data['csrfToken'] = $this->csrfToken();
        extract($data);

        // Try module-specific partials first, then global partials
        $paths = [
            VIEW_PATH . '/' . str_replace('.', '/', $name) . '.php',
            VIEW_PATH . '/partials/' . str_replace('.', '/', $name) . '.php',
        ];

        foreach ($paths as $file) {
            if (file_exists($file)) {
                require $file;

                return;
            }
        }

        throw new \Exception("Partial not found: {$name}");
    }

    /**
     * Output an HTMX-compatible alert message.
     *
     * @param string $type Alert type: 'success', 'error', 'warning', 'info'
     * @param string $message The message to display
     */
    protected function htmxAlert(string $type, string $message): void
    {
        $escapedMessage = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
        echo "<div class=\"alert {$type}\">{$escapedMessage}</div>";
    }

    /**
     * Output success alert for HTMX.
     */
    protected function htmxSuccess(string $message): void
    {
        $this->htmxAlert('success', $message);
    }

    /**
     * Output error alert for HTMX.
     */
    protected function htmxError(string $message): void
    {
        $this->htmxAlert('error', $message);
    }

    /**
     * Check if this is an HTMX request.
     */
    protected function isHtmxRequest(): bool
    {
        return isset($_SERVER['HTTP_HX_REQUEST']);
    }

    /**
     * HTMX redirect via header.
     */
    protected function htmxRedirect(string $url): void
    {
        header('HX-Redirect: ' . $url);
        $this->text('OK');
    }

    /**
     * Output plain text response.
     */
    protected function text(string $content, int $status = 200): void
    {
        http_response_code($status);
        header('Content-Type: text/plain; charset=utf-8');
        print $content;
    }

    /**
     * Output HTML response.
     */
    protected function html(string $content, int $status = 200): void
    {
        http_response_code($status);
        header('Content-Type: text/html; charset=utf-8');
        print $content;
    }

    /**
     * Output file download response.
     */
    protected function download(string $content, string $filename, string $contentType = 'application/octet-stream'): void
    {
        header('Content-Type: ' . $contentType . '; charset=utf-8');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Content-Length: ' . strlen($content));
        print $content;
    }

    /**
     * @return array<string, mixed>
     */
    protected function getJsonInput(): array
    {
        $input = file_get_contents('php://input');
        if ($input === false || $input === '') {
            return [];
        }

        $decoded = json_decode($input, true);
        if (!is_array($decoded)) {
            return [];
        }

        return $decoded;
    }

    protected function jsonError(string $message, int $status = 500): void
    {
        $this->json(['success' => false, 'error' => $message], $status);
    }

    /**
     * Return 404 Not Found response and exit.
     * Automatically detects JSON requests.
     */
    protected function notFound(string $message = 'Nicht gefunden'): never
    {
        if ($this->isJsonRequest()) {
            $this->json(['error' => $message], 404);
        } else {
            $this->text("404 - {$message}", 404);
        }
        exit;
    }

    /**
     * Decode JSON string to array with safe defaults.
     *
     * @return array<mixed>
     */
    protected function decodeJson(?string $json): array
    {
        if ($json === null || $json === '') {
            return [];
        }

        $decoded = json_decode($json, true);

        return is_array($decoded) ? $decoded : [];
    }

    /**
     * Get input value from GET or POST.
     */
    protected function getInput(string $key, mixed $default = null): mixed
    {
        return $_GET[$key] ?? $_POST[$key] ?? $default;
    }

    /**
     * Get trimmed string input.
     */
    protected function getString(string $key, string $default = ''): string
    {
        $value = $this->getInput($key, $default);

        return is_string($value) ? trim($value) : $default;
    }

    /**
     * Get integer input.
     */
    protected function getInt(string $key, int $default = 0): int
    {
        return (int) ($this->getInput($key, $default));
    }

    /**
     * Get current page number from request.
     */
    protected function getPage(): int
    {
        return max(1, $this->getInt('page', 1));
    }

    /**
     * Get limit with bounds.
     */
    protected function getLimit(int $max = 50, int $default = 10): int
    {
        return min($max, max(1, $this->getInt('limit', $default)));
    }

    /**
     * Calculate offset from page and limit.
     */
    protected function getOffset(int $limit): int
    {
        return ($this->getPage() - 1) * $limit;
    }

    /**
     * Create Pagination from request parameters.
     * This keeps HTTP-related code in Infrastructure (Controller) layer.
     */
    protected function getPagination(int $defaultLimit = 50, int $maxLimit = 100): \Domain\ValueObject\Pagination
    {
        $page = $this->getPage();
        $limit = $this->getLimit($maxLimit, $defaultLimit);

        return \Domain\ValueObject\Pagination::create($page, $limit, $maxLimit);
    }
}

Vollständig herunterladen

Aktionen

Herunterladen

Andere Versionen dieser Datei

ID Version Typ Größe Datum
2095 18 modified 8.6 KB 2025-12-29 00:20
1887 17 modified 8.6 KB 2025-12-28 01:00
1883 16 modified 8.5 KB 2025-12-28 01:00
1793 15 modified 8.5 KB 2025-12-27 14:09
1775 14 modified 8.1 KB 2025-12-27 14:03
1774 13 modified 8.0 KB 2025-12-27 14:03
1676 12 modified 7.6 KB 2025-12-27 11:45
1669 11 modified 7.4 KB 2025-12-27 11:29
877 10 modified 7.4 KB 2025-12-23 09:40
818 9 modified 7.3 KB 2025-12-23 08:09
640 8 modified 6.9 KB 2025-12-23 04:47
628 7 modified 6.9 KB 2025-12-23 04:44
627 6 modified 6.9 KB 2025-12-23 04:44
626 5 modified 6.0 KB 2025-12-23 04:44
496 4 modified 4.2 KB 2025-12-22 15:38
214 3 modified 2.3 KB 2025-12-22 01:41
89 2 modified 2.2 KB 2025-12-20 19:16
87 1 modified 1.2 KB 2025-12-20 19:16

← Zurück zur Übersicht