Refine module manifest discovery handling
This commit is contained in:
parent
c883063543
commit
dd825b4450
|
|
@ -1,11 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Support\Modules\ModuleManifest;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use ZipArchive;
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ public function installModule($zipPath, $validateLicense = true)
|
|||
return [
|
||||
'success' => true,
|
||||
'message' => "Modulo {$config['display_name']} installato con successo",
|
||||
'module' => $config
|
||||
'module' => $config,
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Errore installazione modulo: " . $e->getMessage());
|
||||
|
|
@ -109,7 +108,7 @@ public function installModule($zipPath, $validateLicense = true)
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -168,14 +167,14 @@ public function uninstallModule($moduleName, $keepData = false)
|
|||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => "Modulo {$moduleName} disinstallato con successo"
|
||||
'message' => "Modulo {$moduleName} disinstallato con successo",
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Errore disinstallazione modulo: " . $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -226,7 +225,7 @@ public function updateModule($moduleName, $zipPath)
|
|||
'success' => true,
|
||||
'message' => "Modulo {$moduleName} aggiornato alla versione {$newConfig['version']}",
|
||||
'old_version' => $oldConfig['version'],
|
||||
'new_version' => $newConfig['version']
|
||||
'new_version' => $newConfig['version'],
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Errore aggiornamento modulo: " . $e->getMessage());
|
||||
|
|
@ -238,7 +237,7 @@ public function updateModule($moduleName, $zipPath)
|
|||
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -263,11 +262,40 @@ public function getAllModules()
|
|||
}
|
||||
|
||||
return DB::table('modules')->get()->map(function ($module) {
|
||||
$module->config = json_decode($module->config, true);
|
||||
$config = json_decode($module->config, true);
|
||||
$module->config = is_array($config) ? ModuleManifest::normalize($config) : [];
|
||||
return $module;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ottieni il catalogo locale dei moduli con manifest normalizzato.
|
||||
*/
|
||||
public function discoverLocalModules()
|
||||
{
|
||||
if (! File::isDirectory($this->modulesPath)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return collect(File::directories($this->modulesPath))
|
||||
->map(function (string $modulePath) {
|
||||
$moduleName = basename($modulePath);
|
||||
|
||||
try {
|
||||
return (object) $this->getModuleConfig($moduleName);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('ModuleManager: manifest locale non leggibile', [
|
||||
'module' => $moduleName,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
})
|
||||
->filter()
|
||||
->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica licenza modulo (connessione server licenze)
|
||||
*/
|
||||
|
|
@ -307,7 +335,7 @@ public function toggleModule($moduleName)
|
|||
|
||||
DB::table('modules')->where('name', $moduleName)->update([
|
||||
'status' => $newStatus,
|
||||
'updated_at' => now()
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->clearModuleCache();
|
||||
|
|
@ -324,7 +352,7 @@ private function extractModule($zipPath)
|
|||
$zip = new ZipArchive;
|
||||
$extractPath = storage_path('app/modules/temp/' . uniqid());
|
||||
|
||||
if ($zip->open($zipPath) === TRUE) {
|
||||
if ($zip->open($zipPath) === true) {
|
||||
$zip->extractTo($extractPath);
|
||||
$zip->close();
|
||||
return $extractPath;
|
||||
|
|
@ -347,7 +375,7 @@ private function validateModuleConfig($extractPath)
|
|||
throw new \Exception("Configurazione modulo non valida");
|
||||
}
|
||||
|
||||
return $config;
|
||||
return ModuleManifest::normalize($config);
|
||||
}
|
||||
|
||||
private function checkDependencies($config)
|
||||
|
|
@ -371,16 +399,18 @@ private function registerModule($config)
|
|||
throw new \Exception('Tabella modules non disponibile');
|
||||
}
|
||||
|
||||
$config = ModuleManifest::normalize(is_array($config) ? $config : []);
|
||||
|
||||
DB::table('modules')->updateOrInsert(
|
||||
['name' => $config['name']],
|
||||
[
|
||||
'name' => $config['name'],
|
||||
'display_name' => $config['display_name'],
|
||||
'display_name' => $config['display_name'] ?? $config['name'],
|
||||
'version' => $config['version'],
|
||||
'status' => 'active',
|
||||
'config' => json_encode($config),
|
||||
'installed_at' => now(),
|
||||
'updated_at' => now()
|
||||
'updated_at' => now(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -403,7 +433,12 @@ private function getModuleConfig($moduleName)
|
|||
throw new \Exception("Configurazione modulo {$moduleName} non trovata");
|
||||
}
|
||||
|
||||
return json_decode(File::get($configPath), true);
|
||||
$config = json_decode(File::get($configPath), true);
|
||||
if (! is_array($config)) {
|
||||
throw new \Exception("Configurazione modulo {$moduleName} non valida");
|
||||
}
|
||||
|
||||
return ModuleManifest::normalize($config);
|
||||
}
|
||||
|
||||
private function getModuleInfo($moduleName)
|
||||
|
|
@ -425,7 +460,7 @@ private function getModuleInfo($moduleName)
|
|||
return (object) array_merge($config, [
|
||||
'status' => $dbInfo->status ?? 'unknown',
|
||||
'installed_at' => $dbInfo->installed_at ?? null,
|
||||
'updated_at' => $dbInfo->updated_at ?? null
|
||||
'updated_at' => $dbInfo->updated_at ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -451,6 +486,8 @@ private function registerRoutes($moduleName)
|
|||
}
|
||||
private function installPermissions($config)
|
||||
{
|
||||
$config = ModuleManifest::normalize(is_array($config) ? $config : []);
|
||||
|
||||
if (! isset($config['permissions']) || empty($config['permissions'])) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
192
app/Support/Modules/ModuleManifest.php
Normal file
192
app/Support/Modules/ModuleManifest.php
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
namespace App\Support\Modules;
|
||||
|
||||
final class ModuleManifest
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $manifest
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function normalize(array $manifest): array
|
||||
{
|
||||
$normalized = $manifest;
|
||||
|
||||
$name = trim((string) ($manifest['name'] ?? ''));
|
||||
$normalized['name'] = $name;
|
||||
$normalized['display_name'] = self::normalizeNullableString($manifest['display_name'] ?? null) ?? self::normalizeNullableString($manifest['title'] ?? null) ?? $name;
|
||||
$normalized['description'] = self::normalizeNullableString($manifest['description'] ?? null);
|
||||
$normalized['version'] = self::normalizeNullableString($manifest['version'] ?? null) ?? '0.0.0';
|
||||
$normalized['author'] = self::normalizeNullableString($manifest['author'] ?? null);
|
||||
$normalized['license'] = self::normalizeNullableString($manifest['license'] ?? null) ?? 'free';
|
||||
$normalized['category'] = self::normalizeNullableString($manifest['category'] ?? null);
|
||||
$normalized['dependencies'] = self::normalizeStringList($manifest['dependencies'] ?? []);
|
||||
$normalized['optional_dependencies'] = self::normalizeStringList($manifest['optional_dependencies'] ?? []);
|
||||
$normalized['hooks'] = self::normalizeStringList($manifest['hooks'] ?? []);
|
||||
$normalized['features'] = self::normalizeStringList($manifest['features'] ?? []);
|
||||
$normalized['permissions'] = self::normalizePermissions($manifest['permissions'] ?? []);
|
||||
$normalized['menu_items'] = self::normalizeMenuItems($manifest['menu_items'] ?? []);
|
||||
$normalized['requirements'] = is_array($manifest['requirements'] ?? null) ? $manifest['requirements'] : [];
|
||||
$normalized['config'] = is_array($manifest['config'] ?? null) ? $manifest['config'] : [];
|
||||
$normalized['manifest_version'] = (int) ($manifest['manifest_version'] ?? 1);
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private static function normalizeNullableString(mixed $value): ?string
|
||||
{
|
||||
if (! is_scalar($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$normalized = trim((string) $value);
|
||||
|
||||
return $normalized !== '' ? $normalized : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private static function normalizeStringList(mixed $value): array
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$normalized = [];
|
||||
foreach ($value as $item) {
|
||||
$item = self::normalizeNullableString($item);
|
||||
if ($item !== null) {
|
||||
$normalized[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($normalized));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array<int, array{name:string,display_name:string,description:?string,guard_name:string}>
|
||||
*/
|
||||
private static function normalizePermissions(mixed $value): array
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$permissions = [];
|
||||
|
||||
if (self::isAssociative($value)) {
|
||||
foreach ($value as $name => $description) {
|
||||
$permissionName = self::normalizeNullableString($name);
|
||||
if ($permissionName === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$display = self::normalizeNullableString($description) ?? self::humanizePermissionName($permissionName);
|
||||
|
||||
$permissions[] = [
|
||||
'name' => $permissionName,
|
||||
'display_name' => $display,
|
||||
'description' => self::normalizeNullableString($description) ?? $display,
|
||||
'guard_name' => 'web',
|
||||
];
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
foreach ($value as $permission) {
|
||||
if (is_string($permission)) {
|
||||
$permissionName = self::normalizeNullableString($permission);
|
||||
if ($permissionName === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$permissions[] = [
|
||||
'name' => $permissionName,
|
||||
'display_name' => self::humanizePermissionName($permissionName),
|
||||
'description' => null,
|
||||
'guard_name' => 'web',
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! is_array($permission)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$permissionName = self::normalizeNullableString($permission['name'] ?? null);
|
||||
if ($permissionName === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$permissions[] = [
|
||||
'name' => $permissionName,
|
||||
'display_name' => self::normalizeNullableString($permission['display_name'] ?? null) ?? self::humanizePermissionName($permissionName),
|
||||
'description' => self::normalizeNullableString($permission['description'] ?? null),
|
||||
'guard_name' => self::normalizeNullableString($permission['guard_name'] ?? null) ?? 'web',
|
||||
];
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private static function normalizeMenuItems(mixed $value): array
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$menuItems = [];
|
||||
foreach ($value as $item) {
|
||||
if (! is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = self::normalizeNullableString($item['title'] ?? null);
|
||||
$route = self::normalizeNullableString($item['route'] ?? null);
|
||||
if ($title === null || $route === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$menuItems[] = [
|
||||
'title' => $title,
|
||||
'route' => $route,
|
||||
'icon' => self::normalizeNullableString($item['icon'] ?? null),
|
||||
'permission' => self::normalizeNullableString($item['permission'] ?? null),
|
||||
'parent' => self::normalizeNullableString($item['parent'] ?? null),
|
||||
'description' => self::normalizeNullableString($item['description'] ?? null),
|
||||
'order' => isset($item['order']) && is_numeric($item['order']) ? (int) $item['order'] : 0,
|
||||
'badge' => $item['badge'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
usort($menuItems, static fn(array $left, array $right): int => ($left['order'] ?? 0) <=> ($right['order'] ?? 0));
|
||||
|
||||
return $menuItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $value
|
||||
*/
|
||||
private static function isAssociative(array $value): bool
|
||||
{
|
||||
return array_keys($value) !== range(0, count($value) - 1);
|
||||
}
|
||||
|
||||
private static function humanizePermissionName(string $permissionName): string
|
||||
{
|
||||
$label = str_replace(['.', '_', '-'], ' ', $permissionName);
|
||||
|
||||
return ucfirst(trim(preg_replace('/\s+/', ' ', $label) ?? $label));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user