'Autorenprofil', 'structure' => 'Struktur', 'organization' => 'Organisation', 'contract' => 'Contract', 'rule' => 'Regel', ]; public function __construct() { $this->db = DatabaseFactory::content(); } /** * GET /config */ public function index(): void { $typeFilter = $_GET['type'] ?? ''; $statusFilter = $_GET['status'] ?? ''; $sql = 'SELECT c.*, p.name as parent_name FROM content_config c LEFT JOIN content_config p ON c.parent_id = p.id WHERE 1=1'; $params = []; if ($typeFilter !== '' && in_array($typeFilter, self::TYPES, true)) { $sql .= ' AND c.type = ?'; $params[] = $typeFilter; } if ($statusFilter !== '' && in_array($statusFilter, ['draft', 'active', 'deprecated'], true)) { $sql .= ' AND c.status = ?'; $params[] = $statusFilter; } $sql .= ' ORDER BY c.type, c.name'; $stmt = $this->db->prepare($sql); $stmt->execute($params); $configs = $stmt->fetchAll(\PDO::FETCH_ASSOC); $stats = $this->getStatistics(); $this->view('config.index', [ 'title' => 'Content-Konfiguration', 'configs' => $configs, 'stats' => $stats, 'types' => self::TYPES, 'typeLabels' => self::TYPE_LABELS, 'currentType' => $typeFilter, 'currentStatus' => $statusFilter, ]); } /** * GET /config/new */ public function configNew(): void { $parents = $this->getParentOptions(); $this->view('config.form', [ 'title' => 'Neue Konfiguration', 'config' => null, 'types' => self::TYPES, 'typeLabels' => self::TYPE_LABELS, 'parents' => $parents, 'isEdit' => false, ]); } /** * POST /config */ public function store(): void { $this->requireCsrf(); $type = $_POST['type'] ?? ''; $name = trim($_POST['name'] ?? ''); $slug = trim($_POST['slug'] ?? ''); $description = trim($_POST['description'] ?? ''); $content = $_POST['content'] ?? '{}'; $version = trim($_POST['version'] ?? '1.0'); $status = $_POST['status'] ?? 'draft'; $parentId = !empty($_POST['parent_id']) ? (int) $_POST['parent_id'] : null; if (!in_array($type, self::TYPES, true) || $name === '' || $slug === '') { $_SESSION['error'] = 'Typ, Name und Slug sind erforderlich.'; header('Location: /config/new'); exit; } // JSON validieren $decoded = json_decode($content, true); if ($decoded === null && $content !== 'null') { $_SESSION['error'] = 'Ungültiges JSON-Format.'; header('Location: /config/new'); exit; } $stmt = $this->db->prepare( 'INSERT INTO content_config (type, name, slug, description, content, version, status, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)' ); $stmt->execute([$type, $name, $slug, $description ?: null, $content, $version, $status, $parentId]); $id = $this->db->lastInsertId(); $_SESSION['success'] = 'Konfiguration erfolgreich erstellt.'; header('Location: /config/' . $id); exit; } /** * GET /config/{id} */ public function show(string $id): void { $config = $this->findById((int) $id); if ($config === null) { http_response_code(404); echo 'Konfiguration nicht gefunden'; return; } $children = $this->getChildren((int) $id); $history = $this->getHistory((int) $id); $this->view('config.show', [ 'title' => $config['name'], 'config' => $config, 'children' => $children, 'history' => $history, 'typeLabels' => self::TYPE_LABELS, ]); } /** * GET /config/{id}/edit */ public function edit(string $id): void { $config = $this->findById((int) $id); if ($config === null) { http_response_code(404); echo 'Konfiguration nicht gefunden'; return; } $parents = $this->getParentOptions((int) $id); $this->view('config.form', [ 'title' => 'Bearbeiten: ' . $config['name'], 'config' => $config, 'types' => self::TYPES, 'typeLabels' => self::TYPE_LABELS, 'parents' => $parents, 'isEdit' => true, ]); } /** * POST /config/{id} */ public function update(string $id): void { $this->requireCsrf(); $config = $this->findById((int) $id); if ($config === null) { http_response_code(404); echo 'Konfiguration nicht gefunden'; return; } $name = trim($_POST['name'] ?? ''); $slug = trim($_POST['slug'] ?? ''); $description = trim($_POST['description'] ?? ''); $content = $_POST['content'] ?? '{}'; $newVersion = trim($_POST['new_version'] ?? ''); $changeDescription = trim($_POST['change_description'] ?? ''); $status = $_POST['status'] ?? $config['status']; $parentId = !empty($_POST['parent_id']) ? (int) $_POST['parent_id'] : null; if ($name === '' || $slug === '' || $newVersion === '') { $_SESSION['error'] = 'Name, Slug und neue Version sind erforderlich.'; header('Location: /config/' . $id . '/edit'); exit; } // JSON validieren $decoded = json_decode($content, true); if ($decoded === null && $content !== 'null') { $_SESSION['error'] = 'Ungültiges JSON-Format.'; header('Location: /config/' . $id . '/edit'); exit; } // Alte Version in History speichern $stmt = $this->db->prepare( 'INSERT INTO content_config_history (config_id, content, version, changed_by, change_description) VALUES (?, ?, ?, ?, ?)' ); $stmt->execute([ $id, $config['content'], $config['version'], 'web-ui', $changeDescription ?: 'Update auf Version ' . $newVersion, ]); // Config aktualisieren $stmt = $this->db->prepare( 'UPDATE content_config SET name = ?, slug = ?, description = ?, content = ?, version = ?, status = ?, parent_id = ? WHERE id = ?' ); $stmt->execute([$name, $slug, $description ?: null, $content, $newVersion, $status, $parentId, $id]); $_SESSION['success'] = 'Konfiguration auf Version ' . $newVersion . ' aktualisiert.'; header('Location: /config/' . $id); exit; } /** * POST /config/{id}/delete */ public function delete(string $id): void { $this->requireCsrf(); $config = $this->findById((int) $id); if ($config === null) { http_response_code(404); echo 'Konfiguration nicht gefunden'; return; } // Prüfen ob Children existieren $stmt = $this->db->prepare('SELECT COUNT(*) FROM content_config WHERE parent_id = ?'); $stmt->execute([$id]); if ($stmt->fetchColumn() > 0) { $_SESSION['error'] = 'Kann nicht gelöscht werden: Es existieren verknüpfte Einträge.'; header('Location: /config/' . $id); exit; } // History löschen $stmt = $this->db->prepare('DELETE FROM content_config_history WHERE config_id = ?'); $stmt->execute([$id]); // Config löschen $stmt = $this->db->prepare('DELETE FROM content_config WHERE id = ?'); $stmt->execute([$id]); $_SESSION['success'] = 'Konfiguration gelöscht.'; header('Location: /config'); exit; } // ========== Private Helpers ========== private function findById(int $id): ?array { $stmt = $this->db->prepare( 'SELECT c.*, p.name as parent_name FROM content_config c LEFT JOIN content_config p ON c.parent_id = p.id WHERE c.id = ?' ); $stmt->execute([$id]); $result = $stmt->fetch(\PDO::FETCH_ASSOC); return $result !== false ? $result : null; } private function getStatistics(): array { $stats = ['total' => 0, 'by_type' => [], 'by_status' => []]; $stmt = $this->db->query('SELECT type, COUNT(*) as cnt FROM content_config GROUP BY type'); foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $stats['by_type'][$row['type']] = (int) $row['cnt']; $stats['total'] += (int) $row['cnt']; } $stmt = $this->db->query('SELECT status, COUNT(*) as cnt FROM content_config GROUP BY status'); foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $stats['by_status'][$row['status']] = (int) $row['cnt']; } return $stats; } private function getParentOptions(?int $excludeId = null): array { $sql = 'SELECT id, type, name FROM content_config WHERE type IN ("contract", "organization") ORDER BY type, name'; $stmt = $this->db->query($sql); $parents = []; foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { if ($excludeId !== null && (int) $row['id'] === $excludeId) { continue; } $parents[] = $row; } return $parents; } private function getChildren(int $parentId): array { $stmt = $this->db->prepare('SELECT * FROM content_config WHERE parent_id = ? ORDER BY name'); $stmt->execute([$parentId]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } private function getHistory(int $configId): array { $stmt = $this->db->prepare( 'SELECT * FROM content_config_history WHERE config_id = ? ORDER BY created_at DESC LIMIT 10' ); $stmt->execute([$configId]); return $stmt->fetchAll(\PDO::FETCH_ASSOC); } }