netgescon-day0/app/Services/ModuleManager.php

552 lines
16 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use ZipArchive;
/**
* NetGescon Module Manager
*
* Gestisce installazione, disinstallazione e aggiornamento moduli
* Supporta sistema hook, permessi e menu dinamici
*/
class ModuleManager
{
protected $modulesPath;
protected $activeModules = [];
public function __construct()
{
$this->modulesPath = app_path('Modules');
$this->loadActiveModules();
}
/**
* Carica lista moduli attivi dalla cache/database
*/
protected function loadActiveModules()
{
if (!$this->modulesTableExists()) {
$this->activeModules = [];
return;
}
$this->activeModules = Cache::remember('netgescon.active_modules', 3600, function () {
try {
return DB::table('modules')->where('status', 'active')->pluck('name')->toArray();
} catch (\Throwable $e) {
Log::warning('ModuleManager: impossibile caricare moduli attivi', [
'error' => $e->getMessage(),
]);
return [];
}
});
}
/**
* Installa un nuovo modulo da file ZIP
*/
public function installModule($zipPath, $validateLicense = true)
{
try {
Log::info("Inizio installazione modulo da: {$zipPath}");
// 1. Estrai e valida
$extractPath = $this->extractModule($zipPath);
$config = $this->validateModuleConfig($extractPath);
// 2. Verifica licenza (se richiesta)
if ($validateLicense && !$this->validateLicense($config)) {
throw new \Exception("Licenza non valida per il modulo {$config['name']}");
}
// 3. Verifica dipendenze
$this->checkDependencies($config);
// 4. Backup database
$this->createBackup($config['name']);
// 5. Esegui migrazioni
$this->runMigrations($config['name']);
// 6. Registra routes
$this->registerRoutes($config['name']);
// 7. Installa permessi
$this->installPermissions($config);
// 8. Aggiorna menu
$this->updateMenuSystem($config);
// 9. Copia assets
$this->publishAssets($config['name']);
// 10. Registra modulo nel database
$this->registerModule($config);
// 11. Pulisci cache
$this->clearModuleCache();
Log::info("Modulo {$config['name']} installato con successo");
return [
'success' => true,
'message' => "Modulo {$config['display_name']} installato con successo",
'module' => $config
];
} catch (\Exception $e) {
Log::error("Errore installazione modulo: " . $e->getMessage());
// Rollback se necessario
if (isset($config)) {
$this->rollbackInstallation($config['name']);
}
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Disinstalla un modulo
*/
public function uninstallModule($moduleName, $keepData = false)
{
try {
Log::info("Inizio disinstallazione modulo: {$moduleName}");
$config = $this->getModuleConfig($moduleName);
// 1. Controlla dipendenze inverse
$dependentModules = $this->getDependentModules($moduleName);
if (!empty($dependentModules)) {
throw new \Exception("Impossibile disinstallare: moduli dipendenti attivi: " . implode(', ', $dependentModules));
}
// 2. Backup prima della disinstallazione
if (!$keepData) {
$this->createBackup($moduleName);
}
// 3. Disattiva modulo
$this->deactivateModule($moduleName);
// 4. Rimuovi routes
$this->unregisterRoutes($moduleName);
// 5. Rimuovi permessi
$this->uninstallPermissions($config);
// 6. Rimuovi menu
$this->removeMenuItems($config);
// 7. Esegui migrazioni rollback (se non si tengono i dati)
if (!$keepData) {
$this->rollbackMigrations($moduleName);
}
// 8. Rimuovi assets
$this->removeAssets($moduleName);
// 9. Rimuovi file modulo
$this->removeModuleFiles($moduleName);
// 10. Rimuovi dal database
$this->unregisterModule($moduleName);
// 11. Pulisci cache
$this->clearModuleCache();
Log::info("Modulo {$moduleName} disinstallato con successo");
return [
'success' => true,
'message' => "Modulo {$moduleName} disinstallato con successo"
];
} catch (\Exception $e) {
Log::error("Errore disinstallazione modulo: " . $e->getMessage());
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Aggiorna un modulo esistente
*/
public function updateModule($moduleName, $zipPath)
{
try {
Log::info("Inizio aggiornamento modulo: {$moduleName}");
$oldConfig = $this->getModuleConfig($moduleName);
// 1. Backup completo
$backupPath = $this->createFullBackup($moduleName);
// 2. Estrai nuova versione
$extractPath = $this->extractModule($zipPath);
$newConfig = $this->validateModuleConfig($extractPath);
// 3. Verifica compatibilità versioni
if (!$this->isCompatibleVersion($oldConfig, $newConfig)) {
throw new \Exception("Versione non compatibile");
}
// 4. Disattiva temporaneamente
$this->deactivateModule($moduleName);
// 5. Aggiorna file
$this->updateModuleFiles($moduleName, $extractPath);
// 6. Esegui migrazioni di aggiornamento
$this->runMigrations($moduleName);
// 7. Aggiorna configurazione
$this->updateModuleConfig($newConfig);
// 8. Riattiva modulo
$this->activateModule($moduleName);
// 9. Pulisci cache
$this->clearModuleCache();
Log::info("Modulo {$moduleName} aggiornato con successo");
return [
'success' => true,
'message' => "Modulo {$moduleName} aggiornato alla versione {$newConfig['version']}",
'old_version' => $oldConfig['version'],
'new_version' => $newConfig['version']
];
} catch (\Exception $e) {
Log::error("Errore aggiornamento modulo: " . $e->getMessage());
// Ripristina backup se necessario
if (isset($backupPath)) {
$this->restoreBackup($moduleName, $backupPath);
}
return [
'success' => false,
'message' => $e->getMessage()
];
}
}
/**
* Ottieni lista moduli attivi
*/
public function getActiveModules()
{
return collect($this->activeModules)->map(function ($moduleName) {
return $this->getModuleInfo($moduleName);
});
}
/**
* Ottieni lista tutti i moduli (attivi e inattivi)
*/
public function getAllModules()
{
if (!$this->modulesTableExists()) {
return collect();
}
return DB::table('modules')->get()->map(function ($module) {
$module->config = json_decode($module->config, true);
return $module;
});
}
/**
* Verifica licenza modulo (connessione server licenze)
*/
public function validateLicense($config)
{
if ($config['license'] === 'free') {
return true;
}
// TODO: Implementare verifica licenza online
$licenseServer = config('netgescon.license_server');
$licenseKey = config("modules.{$config['name']}.license_key");
// Chiamata API al server licenze
// return $this->callLicenseServer($licenseServer, $config['name'], $licenseKey);
// Per ora ritorna true (da implementare)
return true;
}
/**
* Attiva/disattiva un modulo
*/
public function toggleModule($moduleName)
{
if (!$this->modulesTableExists()) {
throw new \Exception('Tabella modules non disponibile');
}
$module = DB::table('modules')->where('name', $moduleName)->first();
if (!$module) {
throw new \Exception("Modulo {$moduleName} non trovato");
}
$newStatus = $module->status === 'active' ? 'inactive' : 'active';
DB::table('modules')->where('name', $moduleName)->update([
'status' => $newStatus,
'updated_at' => now()
]);
$this->clearModuleCache();
return $newStatus;
}
// =====================================================
// METODI PRIVATI DI SUPPORTO
// =====================================================
private function extractModule($zipPath)
{
$zip = new ZipArchive;
$extractPath = storage_path('app/modules/temp/' . uniqid());
if ($zip->open($zipPath) === TRUE) {
$zip->extractTo($extractPath);
$zip->close();
return $extractPath;
} else {
throw new \Exception("Impossibile estrarre il file ZIP del modulo");
}
}
private function validateModuleConfig($extractPath)
{
$configPath = $extractPath . '/module.json';
if (!File::exists($configPath)) {
throw new \Exception("File module.json non trovato");
}
$config = json_decode(File::get($configPath), true);
if (!$config || !isset($config['name'], $config['version'])) {
throw new \Exception("Configurazione modulo non valida");
}
return $config;
}
private function checkDependencies($config)
{
if (!isset($config['dependencies'])) {
return true;
}
foreach ($config['dependencies'] as $dependency) {
if (!in_array($dependency, $this->activeModules)) {
throw new \Exception("Dipendenza mancante: {$dependency}");
}
}
return true;
}
private function registerModule($config)
{
if (!$this->modulesTableExists()) {
throw new \Exception('Tabella modules non disponibile');
}
DB::table('modules')->updateOrInsert(
['name' => $config['name']],
[
'name' => $config['name'],
'display_name' => $config['display_name'],
'version' => $config['version'],
'status' => 'active',
'config' => json_encode($config),
'installed_at' => now(),
'updated_at' => now()
]
);
}
private function clearModuleCache()
{
Cache::forget('netgescon.active_modules');
Cache::forget('netgescon.modules_menu');
Cache::forget('netgescon.modules_permissions');
// Ricarica moduli attivi
$this->loadActiveModules();
}
private function getModuleConfig($moduleName)
{
$configPath = $this->modulesPath . "/{$moduleName}/module.json";
if (!File::exists($configPath)) {
throw new \Exception("Configurazione modulo {$moduleName} non trovata");
}
return json_decode(File::get($configPath), true);
}
private function getModuleInfo($moduleName)
{
$config = $this->getModuleConfig($moduleName);
$dbInfo = null;
if ($this->modulesTableExists()) {
try {
$dbInfo = DB::table('modules')->where('name', $moduleName)->first();
} catch (\Throwable $e) {
Log::warning('ModuleManager: impossibile recuperare info modulo', [
'module' => $moduleName,
'error' => $e->getMessage(),
]);
}
}
return (object) array_merge($config, [
'status' => $dbInfo->status ?? 'unknown',
'installed_at' => $dbInfo->installed_at ?? null,
'updated_at' => $dbInfo->updated_at ?? null
]);
}
private function modulesTableExists(): bool
{
try {
return DB::connection()->getSchemaBuilder()->hasTable('modules');
} catch (\Throwable $e) {
Log::notice('ModuleManager: schema builder non disponibile', [
'error' => $e->getMessage(),
]);
return false;
}
}
// TODO: Implementare metodi rimanenti
private function runMigrations($moduleName)
{ /* TODO */
}
private function registerRoutes($moduleName)
{ /* TODO */
}
private function installPermissions($config)
{
if (!isset($config['permissions']) || empty($config['permissions'])) {
return;
}
$permissions = $config['permissions'];
$createdPermissions = [];
try {
foreach ($permissions as $permission) {
// Crea il permesso se non esiste
$existingPermission = \Spatie\Permission\Models\Permission::where('name', $permission['name'])->first();
if (!$existingPermission) {
$newPermission = \Spatie\Permission\Models\Permission::create([
'name' => $permission['name'],
'guard_name' => $permission['guard_name'] ?? 'web',
'description' => $permission['description'] ?? null,
]);
$createdPermissions[] = $newPermission->name;
Log::info("Permesso creato: {$newPermission->name}");
}
}
// Assegna i permessi ai ruoli appropriati
$this->assignPermissionsToRoles($permissions);
Log::info("Installazione permessi completata", ['permissions' => $createdPermissions]);
} catch (\Exception $e) {
Log::error("Errore installazione permessi", ['error' => $e->getMessage()]);
throw $e;
}
}
/**
* Assegna i permessi ai ruoli appropriati
*/
private function assignPermissionsToRoles($permissions)
{
// Ottieni i ruoli esistenti
$adminRoles = ['admin', 'amministratore', 'super-admin'];
foreach ($permissions as $permission) {
$permName = $permission['name'];
$assignToRoles = $permission['roles'] ?? $adminRoles; // Default: assegna agli admin
foreach ($assignToRoles as $roleName) {
$role = \Spatie\Permission\Models\Role::where('name', $roleName)->first();
if ($role) {
// Assegna il permesso al ruolo se non ce l'ha già
if (!$role->hasPermissionTo($permName)) {
$role->givePermissionTo($permName);
Log::info("Permesso {$permName} assegnato al ruolo {$roleName}");
}
}
}
}
}
private function uninstallPermissions($config)
{
if (!isset($config['permissions']) || empty($config['permissions'])) {
return;
}
try {
foreach ($config['permissions'] as $permission) {
$existingPermission = \Spatie\Permission\Models\Permission::where('name', $permission['name'])->first();
if ($existingPermission) {
// Rimuovi il permesso da tutti i ruoli
$existingPermission->roles()->detach();
// Elimina il permesso
$existingPermission->delete();
Log::info("Permesso rimosso: {$permission['name']}");
}
}
} catch (\Exception $e) {
Log::error("Errore rimozione permessi", ['error' => $e->getMessage()]);
}
}
private function updateMenuSystem($config)
{ /* TODO */
}
private function publishAssets($moduleName)
{ /* TODO */
}
private function createBackup($moduleName)
{ /* TODO */
}
private function rollbackInstallation($moduleName)
{ /* TODO */
}
// ... altri metodi di supporto
}