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