- Alle Module nach /modules/ verschoben (dinos, plants, storymachine, test, _template) - .htaccess transparentes Routing fuer /modules/ eingerichtet - ModuleService Auto-Discovery auf /modules/ Verzeichnis angepasst - Original Web Audio Synthese (playOrganicFart) und Furzkissen-UI in modules/test/index.php wiederhergestellt - AGENTS.md Dokumentation aktualisiert
158 lines
5.2 KiB
PHP
158 lines
5.2 KiB
PHP
<?php
|
|
/**
|
|
* ModuleService - Dynamische Auto-Discovery für Subprojekte & Module
|
|
*
|
|
* Jedes Verzeichnis im Root mit einer `module.json` wird automatisch als Modul registriert
|
|
* und steht sofort in der Benutzer- und Rechteverwaltung zur Verfügung.
|
|
*/
|
|
class ModuleService {
|
|
private static ?array $cachedModules = null;
|
|
|
|
// Fallback-Definitionen, falls Dateisystem-Zugriff blockiert ist
|
|
private static array $defaultFallback = [
|
|
'dinos' => [
|
|
'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]);
|
|
}
|
|
}
|