philippurbschat.de/home/app/services/ModuleService.php
Philipp Urbschat 3de05f542c
refactor: subprojekte nach /modules/ verschoben und test-sound wiederhergestellt
- 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
2026-09-13 00:33:01 +02:00

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]);
}
}