[ 'slug' => 'dinos', 'name' => 'Dino Generator', 'badge' => 'AI Art', 'description' => 'KI Dino-Bilderstellung & Google Photos Sync', 'path' => '/dinos', 'adminOnly' => false, 'order' => 1 ], 'plants' => [ 'slug' => 'plants', 'name' => 'Plant Tracker', 'badge' => 'AI Vision', 'description' => 'Pflanzen-Tracking, Pflege & KI-Erkennung', 'path' => '/plants', 'adminOnly' => false, 'order' => 2 ], 'storymachine' => [ 'slug' => 'storymachine', 'name' => 'Story Machine', 'badge' => 'Creative', 'description' => 'Interaktive Kinder-Geschichtenmaschine', 'path' => '/storymachine', 'adminOnly' => false, 'order' => 3 ], 'test' => [ 'slug' => 'test', 'name' => 'Test & Lab', 'badge' => 'Audio', 'description' => 'Modul-Showcase & Web Audio Synthesizer', 'path' => '/test', 'adminOnly' => false, 'order' => 4 ] ]; /** * Ermittelt den Webroot-Pfad der Domain */ private static function getRootPath(): string { return dirname(__DIR__, 3); } /** * Liest alle Module dynamisch aus dem Dateisystem */ public static function getAll(bool $refresh = false): array { if (self::$cachedModules !== null && !$refresh) { return self::$cachedModules; } $rootPath = self::getRootPath(); $modules = []; // Ignorierte Systemverzeichnisse $ignoredDirs = ['.git', '.vscode', 'home', 'philcore', 'node_modules', 'vendor', '_template']; // 1. Primär: modules/ Verzeichnis scannen $scanLocations = [ $rootPath . '/modules', $rootPath ]; foreach ($scanLocations as $basePath) { if (!is_dir($basePath)) continue; $entries = scandir($basePath); if ($entries === false) continue; foreach ($entries as $entry) { if ($entry === '.' || $entry === '..' || in_array($entry, $ignoredDirs, true) || isset($modules[$entry])) { continue; } $moduleDir = $basePath . '/' . $entry; if (!is_dir($moduleDir)) { continue; } $descriptorFile = $moduleDir . '/module.json'; if (file_exists($descriptorFile)) { $jsonContent = @file_get_contents($descriptorFile); $data = $jsonContent ? json_decode($jsonContent, true) : null; if (is_array($data)) { if (isset($data['enabled']) && $data['enabled'] === false) { continue; } $modules[$entry] = [ 'slug' => $entry, 'name' => $data['name'] ?? ucfirst($entry), 'badge' => $data['badge'] ?? 'Tool', 'description' => $data['description'] ?? '', 'path' => '/' . $entry, 'adminOnly' => !empty($data['adminOnly']), 'order' => isset($data['order']) ? (int)$data['order'] : 999 ]; } } } } // Falls keine Module gefunden wurden, Fallback nutzen if (empty($modules)) { $modules = self::$defaultFallback; } // Sortierung nach 'order', dann nach Name uasort($modules, function ($a, $b) { $orderA = $a['order'] ?? 999; $orderB = $b['order'] ?? 999; if ($orderA !== $orderB) { return $orderA <=> $orderB; } return strcasecmp($a['name'] ?? '', $b['name'] ?? ''); }); self::$cachedModules = $modules; return self::$cachedModules; } public static function getBySlug(string $slug): ?array { $all = self::getAll(); return $all[$slug] ?? null; } public static function getAccessibleForUser(array $userProjects, bool $isAdmin = false): array { $accessible = []; foreach (self::getAll() as $slug => $module) { if ($isAdmin) { $accessible[$slug] = $module; } elseif (in_array($slug, $userProjects, true) && empty($module['adminOnly'])) { $accessible[$slug] = $module; } } return $accessible; } public static function isValidModule(string $slug): bool { $all = self::getAll(); return isset($all[$slug]); } }