repository = $repository; $this->apiFormatter = $apiFormatter; $this->audit = $audit; } /** * GET /semantic-explorer/ontologie * Klassifizierte Entities mit Ontologie-Klassen und Verbindungen */ public function index(): void { $search = $this->getString('search'); $classFilter = $this->getString('class'); $pagination = $this->getPagination(50); // Ontologie-Klassen für Filter-Dropdown $classes = $this->repository->findAll(); foreach ($classes as &$class) { $class['properties_decoded'] = $this->decodeJson($class['properties'] ?? null); } // Klassifizierte Entities mit Relationen $totalCount = $this->repository->countClassifiedEntities($search, $classFilter); $entities = $this->repository->findClassifiedEntities($search, $classFilter, $pagination->limit, $pagination->offset); $pagination = $pagination->withTotal($totalCount); $stats = $this->repository->getStats(); $this->view('semantic-explorer.ontologie', [ 'title' => 'Ontologie', 'classes' => $classes, 'entities' => $entities, 'stats' => $stats, 'currentSearch' => $search, 'currentClass' => $classFilter, 'currentPage' => $pagination->page, 'totalCount' => $pagination->totalCount, 'totalPages' => $pagination->totalPages(), ]); } /** * GET /semantic-explorer/ontologie/new */ public function create(): void { $this->view('semantic-explorer.ontologie.new', [ 'title' => 'Neue Ontologie-Klasse', 'classes' => $this->repository->findForSelect(), ]); } /** * POST /semantic-explorer/ontologie */ public function store(): void { $this->requireCsrf(); $input = $this->getJsonInput(); if (empty($input)) { $input = $_POST; } $name = trim($input['name'] ?? ''); $parentId = isset($input['parent_class_id']) && $input['parent_class_id'] !== '' ? (int) $input['parent_class_id'] : null; $description = trim($input['description'] ?? '') ?: null; $properties = $input['properties'] ?? []; if ($name === '') { if ($this->isHtmxRequest()) { $this->htmxError('Name ist erforderlich'); return; } $this->json($this->apiFormatter->validationError('Name ist erforderlich', ['name' => 'Pflichtfeld']), 400); return; } try { $id = $this->repository->create($name, $parentId, $description, $properties); // Audit log $this->audit->logCreate( table: 'ontology_classes', id: $id, data: ['name' => $name, 'parent_class_id' => $parentId, 'description' => $description], actor: 'user', actorType: 'user' ); if ($this->isHtmxRequest()) { $this->htmxRedirect('/semantic-explorer/ontologie/' . $id); return; } $this->json($this->apiFormatter->created($id, 'Ontologie-Klasse erstellt')); } catch (\Exception $e) { if ($this->isHtmxRequest()) { $this->htmxError($e->getMessage()); return; } $this->json($this->apiFormatter->error($e->getMessage(), 'SERVER_ERROR'), 500); } } /** * GET /semantic-explorer/ontologie/{id}/edit */ public function edit(int $id): void { $class = $this->repository->find($id); if ($class === null) { $this->notFound('Klasse nicht gefunden'); } $this->view('semantic-explorer.ontologie.edit', [ 'title' => 'Klasse bearbeiten', 'class' => $class, 'classes' => $this->repository->findForSelect(), ]); } /** * POST /semantic-explorer/ontologie/{id} */ public function update(int $id): void { $this->requireCsrf(); $input = $this->getJsonInput(); if (empty($input)) { $input = $_POST; } $name = trim($input['name'] ?? ''); $parentId = isset($input['parent_class_id']) && $input['parent_class_id'] !== '' ? (int) $input['parent_class_id'] : null; $description = trim($input['description'] ?? '') ?: null; $properties = $input['properties'] ?? []; if ($name === '') { if ($this->isHtmxRequest()) { $this->htmxError('Name ist erforderlich'); return; } $this->json($this->apiFormatter->validationError('Name ist erforderlich', ['name' => 'Pflichtfeld']), 400); return; } try { // Get old state for audit $oldClass = $this->repository->find($id); $this->repository->update($id, $name, $parentId, $description, $properties); // Audit log $this->audit->logUpdate( table: 'ontology_classes', id: $id, oldData: $oldClass !== null ? ['name' => $oldClass['name'], 'description' => $oldClass['description'] ?? null] : [], newData: ['name' => $name, 'parent_class_id' => $parentId, 'description' => $description], actor: 'user', actorType: 'user' ); if ($this->isHtmxRequest()) { $this->htmxRedirect('/semantic-explorer/ontologie/' . $id); return; } $this->json($this->apiFormatter->ok('Ontologie-Klasse aktualisiert')); } catch (\Exception $e) { if ($this->isHtmxRequest()) { $this->htmxError($e->getMessage()); return; } $this->json($this->apiFormatter->error($e->getMessage(), 'SERVER_ERROR'), 500); } } /** * POST /semantic-explorer/ontologie/{id}/delete */ public function destroy(int $id): void { try { // Get data before delete for audit $class = $this->repository->find($id); $success = $this->repository->delete($id); if ($success) { // Audit log $this->audit->logDelete( table: 'ontology_classes', id: $id, data: $class !== null ? ['name' => $class['name'], 'description' => $class['description'] ?? null] : [], reason: 'User deleted via UI', actor: 'user', actorType: 'user' ); $this->json($this->apiFormatter->ok('Ontologie-Klasse gelöscht')); } else { $this->json($this->apiFormatter->error('Klasse hat noch Unterklassen', 'HAS_CHILDREN'), 400); } } catch (\Exception $e) { $this->json($this->apiFormatter->error($e->getMessage(), 'SERVER_ERROR'), 500); } } }