basePdo) { BaseSchema::ensure($this->basePdo); } $this->scanModules(); } public function all(): array { return $this->modules; } public function get(string $name): ?array { return $this->modules[$name] ?? null; } public function isEnabled(string $name): bool { $module = $this->get($name); return (bool)($module['enabled'] ?? false); } public function setEnabled(string $name, bool $enabled): void { if (!$this->basePdo) { return; } $module = $this->get($name); if (!$module) { return; } $stmt = $this->basePdo->prepare( "INSERT INTO nexus_modules (name, title, version, enabled, installed_at, updated_at) VALUES (:name, :title, :version, :enabled, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT(name) DO UPDATE SET enabled = excluded.enabled, title = excluded.title, version = excluded.version, updated_at = CURRENT_TIMESTAMP" ); $stmt->execute([ 'name' => $name, 'title' => (string)($module['title'] ?? $name), 'version' => (string)($module['version'] ?? ''), 'enabled' => $enabled ? 1 : 0, ]); $this->modules[$name]['enabled'] = $enabled; } public function settings(string $name): array { if (!$this->basePdo) { return []; } $stmt = $this->basePdo->prepare( "SELECT settings FROM nexus_module_settings WHERE name = :name LIMIT 1" ); $stmt->execute(['name' => $name]); $row = $stmt->fetch(\PDO::FETCH_ASSOC); if (!$row || empty($row['settings'])) { return []; } $decoded = json_decode((string)$row['settings'], true); return is_array($decoded) ? $decoded : []; } public function saveSettings(string $name, array $settings): void { if (!$this->basePdo) { return; } $payload = json_encode($settings, JSON_UNESCAPED_UNICODE); if ($payload === false) { return; } $stmt = $this->basePdo->prepare( "INSERT INTO nexus_module_settings (name, settings, updated_at) VALUES (:name, :settings, CURRENT_TIMESTAMP) ON CONFLICT(name) DO UPDATE SET settings = excluded.settings, updated_at = CURRENT_TIMESTAMP" ); $stmt->execute([ 'name' => $name, 'settings' => $payload, ]); } public function modulePdo(string $name, array $fallback = []): ?\PDO { $settings = $this->settings($name); $db = $settings['db'] ?? $fallback; if (!is_array($db) || empty($db)) { throw new ModuleConfigException( $name, 'Modul nicht konfiguriert. Bitte Setup ausfuehren.' ); } if (!isset($db['options'])) { $db['options'] = [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, ]; } try { $pdo = Database::createFromArray($db); } catch (\Throwable $e) { if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL) { @file_put_contents( __DIR__ . '/../../debug/module_db_error.log', '[' . date('c') . '] ' . $name . ': ' . $e->getMessage() . PHP_EOL, FILE_APPEND ); } throw new ModuleConfigException( $name, 'Modul-Datenbank nicht korrekt konfiguriert.', $e->getMessage(), 0, $e ); } if ($name === 'kea' && !empty($settings['kea_auto_init'])) { try { Database::ensureKeaSchema($pdo, [ 'auto_init' => true, 'init_cmd' => $settings['kea_init_cmd'] ?? null, 'init_script' => $settings['kea_init_script'] ?? null, 'kea_db_version' => $settings['kea_db_version'] ?? '', ]); } catch (\Throwable $e) { if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL) { @file_put_contents( __DIR__ . '/../../debug/module_db_error.log', '[' . date('c') . '] ' . $name . ': ' . $e->getMessage() . PHP_EOL, FILE_APPEND ); } throw new ModuleConfigException( $name, 'Modul-Datenbank nicht korrekt konfiguriert.', $e->getMessage(), 0, $e ); } } return $pdo; } public function resolvePage(string $name, string $page): ?string { $module = $this->get($name); if (!$module) { return null; } if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $page)) { return null; } $path = $module['path'] . '/pages/' . $page . '.php'; if (is_file($path)) { return $path; } return null; } public function bootEnabled(): void { foreach ($this->modules as $name => $module) { if (!empty($module['enabled'])) { $bootstrap = $module['path'] . '/bootstrap.php'; if (is_file($bootstrap)) { $modules = $this; require_once $bootstrap; } } } } public function registerFunction(string $module, string $name, callable $fn): void { $key = $module . ':' . $name; $this->callbacks[$key] = $fn; } public function call(string $module, string $name, mixed ...$args): mixed { $key = $module . ':' . $name; if (!isset($this->callbacks[$key])) { throw new \RuntimeException("Module callback not found: {$key}"); } return ($this->callbacks[$key])(...$args); } private function scanModules(): void { $this->modules = []; if (!is_dir($this->modulesPath)) { if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL) { @file_put_contents(__DIR__ . '/../../debug/module_scan.log', 'Modules path not found: ' . $this->modulesPath . PHP_EOL, FILE_APPEND); } return; } foreach (glob($this->modulesPath . '/*', GLOB_ONLYDIR) as $dir) { $name = basename($dir); $manifest = $dir . '/module.json'; if (!is_file($manifest)) { continue; } $raw = file_get_contents($manifest); $data = $raw ? json_decode($raw, true) : null; if (!is_array($data)) { continue; } $module = [ 'name' => $name, 'title' => $data['title'] ?? $name, 'version' => $data['version'] ?? '', 'description' => $data['description'] ?? '', 'setup' => $data['setup'] ?? [], 'menu' => $data['menu'] ?? [], 'sidebar' => $data['sidebar'] ?? [], 'db_defaults' => $data['db_defaults'] ?? [], 'path' => $dir, 'enabled' => false, ]; $module['enabled'] = $this->loadEnabledState($name, $module); $this->modules[$name] = $module; } if (defined('APP_DEBUG_TOOL') && APP_DEBUG_TOOL) { @file_put_contents(__DIR__ . '/../../debug/module_scan.log', 'Modules loaded: ' . implode(', ', array_keys($this->modules)) . PHP_EOL, FILE_APPEND); } } private function loadEnabledState(string $name, array $module): bool { if (!$this->basePdo) { return false; } $stmt = $this->basePdo->prepare( "SELECT enabled FROM nexus_modules WHERE name = :name LIMIT 1" ); $stmt->execute(['name' => $name]); $row = $stmt->fetch(\PDO::FETCH_ASSOC); if ($row !== false) { return (bool)$row['enabled']; } $stmt = $this->basePdo->prepare( "INSERT INTO nexus_modules (name, title, version, enabled, installed_at, updated_at) VALUES (:name, :title, :version, :enabled, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" ); $stmt->bindValue(':name', $name, \PDO::PARAM_STR); $stmt->bindValue(':title', (string)$module['title'], \PDO::PARAM_STR); $stmt->bindValue(':version', (string)$module['version'], \PDO::PARAM_STR); $stmt->bindValue(':enabled', false, \PDO::PARAM_BOOL); $stmt->execute(); return false; } }