'PHP 8.2+', 'passed' => PHP_VERSION_ID >= 80200, 'message' => PHP_VERSION_ID >= 80200 ? 'PHP ' . PHP_VERSION : 'Versione attuale: PHP ' . PHP_VERSION . '. Necessaria 8.2+', 'critical' => true, ]; // Extensions $extensions = [ 'pdo_mysql' => 'PDO MySQL', 'mbstring' => 'MBString', 'xml' => 'XML', 'curl' => 'cURL', 'zip' => 'Zip', 'gd' => 'GD (immagini)', 'fileinfo' => 'Fileinfo', ]; foreach ($extensions as $ext => $label) { $checks[] = [ 'name' => $label, 'passed' => extension_loaded($ext), 'message' => extension_loaded($ext) ? 'Caricata' : 'Estensione "' . $ext . '" non trovata', 'critical' => true, ]; } // Directory permissions $dirs = [ 'storage' => dirname(__DIR__) . '/storage', 'bootstrap/cache' => dirname(__DIR__) . '/bootstrap/cache', ]; foreach ($dirs as $label => $dir) { $writable = is_dir($dir) && is_writable($dir); $checks[] = [ 'name' => $label . '/ scrivibile', 'passed' => $writable, 'message' => $writable ? 'Permessi OK' : 'Cartella non scrivibile. Esegui: chmod -R 775 ' . $dir, 'critical' => true, ]; } // mod_rewrite (suggested) $checks[] = [ 'name' => 'mod_rewrite (Apache)', 'passed' => true, 'message' => 'Raccomandato per URL puliti. Verifica configurazione Apache.', 'critical' => false, ]; // HTTPS $isHttps = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on'; $checks[] = [ 'name' => 'HTTPS attivo', 'passed' => $isHttps, 'message' => $isHttps ? 'Connessione sicura' : 'Connessione non crittata. Configura SSL in produzione.', 'critical' => false, ]; return $checks; } function allRequirementsMet(array $checks): bool { foreach ($checks as $check) { if (!empty($check['critical']) && !$check['passed']) { return false; } } return true; } function testDatabaseConnection(string $host, int $port, string $database, string $user, string $pass): array { try { $dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4"; $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5, ]); return ['success' => true, 'message' => 'Connessione riuscita.']; } catch (PDOException $e) { return ['success' => false, 'message' => 'Errore: ' . $e->getMessage()]; } } function testRootConnection(string $host, int $port, string $user, string $pass): array { try { $dsn = "mysql:host={$host};port={$port};charset=utf8mb4"; $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_TIMEOUT => 5, ]); return ['success' => true, 'message' => 'Connessione root riuscita.', 'pdo' => $pdo]; } catch (PDOException $e) { return ['success' => false, 'message' => 'Errore: ' . $e->getMessage()]; } } function createDatabaseAndUser(string $rootUser, string $rootPass, string $host, int $port, string $dbName, string $dbUser, string $dbPass): array { $result = testRootConnection($host, $port, $rootUser, $rootPass); if (!$result['success']) { return $result; } try { $pdo = $result['pdo']; $pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); $pdo->exec("CREATE USER IF NOT EXISTS '{$dbUser}'@'localhost' IDENTIFIED BY '{$dbPass}'"); $pdo->exec("GRANT ALL ON `{$dbName}`.* TO '{$dbUser}'@'localhost'"); $pdo->exec("FLUSH PRIVILEGES"); return ['success' => true, 'message' => 'Database e utente creati con successo.']; } catch (PDOException $e) { return ['success' => false, 'message' => 'Errore: ' . $e->getMessage()]; } } function writeEnvFile(array $params): bool { $rootDir = dirname(__DIR__); $examplePath = $rootDir . '/.env.example'; $envPath = $rootDir . '/.env'; if (!file_exists($examplePath)) { return false; } $content = file_get_contents($examplePath); $replacements = [ 'APP_NAME=' => 'APP_NAME=' . ($params['app_name'] ?? 'Glastree'), 'APP_ENV=' => 'APP_ENV=production', 'APP_KEY=' => 'APP_KEY=', 'APP_DEBUG=' => 'APP_DEBUG=false', 'APP_URL=' => 'APP_URL=' . ($params['app_url'] ?? 'http://localhost'), 'DB_CONNECTION=' => 'DB_CONNECTION=mysql', '# DB_HOST=' => 'DB_HOST=', '# DB_PORT=' => 'DB_PORT=', '# DB_DATABASE=' => 'DB_DATABASE=', '# DB_USERNAME=' => 'DB_USERNAME=', '# DB_PASSWORD=' => 'DB_PASSWORD=', ]; foreach ($replacements as $search => $replace) { if (str_starts_with($content, $search) || preg_match('/^' . preg_quote($search, '/') . '/m', $content)) { $content = preg_replace( '/^' . preg_quote($search, '/') . '.*$/m', $replace . ($params['db_host'] ?? '127.0.0.1'), $content ); } } // Specific DB replacements (handle commented lines) $content = preg_replace( '/^# DB_HOST=.*$/m', 'DB_HOST=' . ($params['db_host'] ?? '127.0.0.1'), $content ); $content = preg_replace( '/^# DB_PORT=.*$/m', 'DB_PORT=' . ($params['db_port'] ?? '3306'), $content ); $content = preg_replace( '/^# DB_DATABASE=.*$/m', 'DB_DATABASE=' . ($params['db_database'] ?? 'glastree'), $content ); $content = preg_replace( '/^# DB_USERNAME=.*$/m', 'DB_USERNAME=' . ($params['db_username'] ?? 'glastree'), $content ); $content = preg_replace( '/^# DB_PASSWORD=.*$/m', 'DB_PASSWORD=' . ($params['db_password'] ?? ''), $content ); // Ensure APP_KEY= line exists if (!preg_match('/^APP_KEY=/m', $content)) { $content .= "\nAPP_KEY=\n"; } return file_put_contents($envPath, $content) !== false; } function bootstrapLaravel(): object { $rootDir = dirname(__DIR__); require_once $rootDir . '/vendor/autoload.php'; $app = require $rootDir . '/bootstrap/app.php'; $kernel = $app->make(\Illuminate\Contracts\Console\Kernel::class); return $kernel; } function runArtisanCommands(object $kernel, array $commands): array { $results = []; foreach ($commands as $command => $options) { try { $exitCode = $kernel->call($command, $options ?? []); $results[] = [ 'command' => $command, 'success' => $exitCode === 0, 'output' => $kernel->output(), ]; } catch (\Exception $e) { $results[] = [ 'command' => $command, 'success' => false, 'output' => $e->getMessage(), ]; } } return $results; } function restoreFromBackup(string $zipPath, string $dbUser, string $dbPass, string $dbName): array { $rootDir = dirname(__DIR__); $restoreDir = sys_get_temp_dir() . '/glastree-restore-' . uniqid(); if (!file_exists($zipPath)) { return ['success' => false, 'message' => 'File ZIP non trovato: ' . $zipPath]; } $zip = new ZipArchive(); if ($zip->open($zipPath) !== true) { return ['success' => false, 'message' => 'Impossibile aprire il file ZIP.']; } $zip->extractTo($restoreDir); $zip->close(); $log = []; // 1. Import database.sql $sqlFile = $restoreDir . '/database.sql'; if (file_exists($sqlFile)) { $cmd = sprintf( 'mysql --host=%s --port=%s --user=%s --password=%s %s < %s 2>&1', escapeshellarg($_SESSION['db_host'] ?? '127.0.0.1'), escapeshellarg((string)($_SESSION['db_port'] ?? '3306')), escapeshellarg($dbUser), escapeshellarg($dbPass), escapeshellarg($dbName), escapeshellarg($sqlFile) ); exec($cmd, $output, $exitCode); if ($exitCode === 0) { $log[] = 'Database importato con successo.'; } else { $log[] = 'Errore import database: ' . implode("\n", $output); return ['success' => false, 'message' => 'Import database fallito.', 'log' => $log]; } } else { $log[] = 'database.sql non trovato nel backup.'; } // 2. Copy files to storage $filesDir = $restoreDir . '/files'; $targetDir = $rootDir . '/storage/app/documenti'; if (is_dir($filesDir)) { if (!is_dir($targetDir)) { mkdir($targetDir, 0755, true); } $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($filesDir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST ); foreach ($files as $file) { $relative = substr($file->getPathname(), strlen($filesDir) + 1); $target = $targetDir . '/' . $relative; if ($file->isDir()) { if (!is_dir($target)) { mkdir($target, 0755, true); } } else { copy($file->getPathname(), $target); } } $log[] = 'File documenti ripristinati.'; } else { $log[] = 'Cartella files non trovata nel backup.'; } // 3. Merge .env if present (preserving current DB credentials) $envBackup = $restoreDir . '/.env'; if (file_exists($envBackup)) { $currentEnv = file_get_contents($envBackup); $currentEnv = preg_replace('/^DB_HOST=.*$/m', 'DB_HOST=' . ($_SESSION['db_host'] ?? '127.0.0.1'), $currentEnv); $currentEnv = preg_replace('/^DB_PORT=.*$/m', 'DB_PORT=' . ($_SESSION['db_port'] ?? '3306'), $currentEnv); $currentEnv = preg_replace('/^DB_DATABASE=.*$/m', 'DB_DATABASE=' . $dbName, $currentEnv); $currentEnv = preg_replace('/^DB_USERNAME=.*$/m', 'DB_USERNAME=' . $dbUser, $currentEnv); $currentEnv = preg_replace('/^DB_PASSWORD=.*$/m', 'DB_PASSWORD=' . $dbPass, $currentEnv); // Only merge if APP_URL differs from localhost if (str_contains($currentEnv, 'APP_URL=http://localhost') || str_contains($currentEnv, 'APP_URL=')) { $currentEnv = preg_replace( '/^APP_URL=.*$/m', 'APP_URL=' . getBaseUrl(), $currentEnv ); } file_put_contents($rootDir . '/.env', $currentEnv); $log[] = '.env ripristinato dal backup (credenziali DB preservate).'; } // 4. Copy manifest if exists if (file_exists($restoreDir . '/manifest.json')) { $log[] = 'Manifest: ' . file_get_contents($restoreDir . '/manifest.json'); } // Cleanup temp $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($restoreDir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($it as $file) { $file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname()); } rmdir($restoreDir); return ['success' => true, 'message' => 'Backup ripristinato con successo.', 'log' => $log]; } function createSuperAdmin(string $name, string $email, string $password): array { try { $kernel = bootstrapLaravel(); $kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\DiocesiSeeder']); $kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\ComuniSeeder']); $kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\RoleSeeder']); $kernel->call('db:seed', ['--force' => true, '--class' => 'Database\\Seeders\\RolePresetSeeder']); } catch (\Exception $e) { // Seeder might fail if already seeded, that's OK } try { $user = new \App\Models\User(); $user->name = $name; $user->email = $email; $user->password = \Illuminate\Support\Facades\Hash::make($password); $user->is_admin = true; $user->status = 'active'; $user->role_preset_id = 1; $user->permissions = [ 'individui' => 2, 'gruppi' => 2, 'eventi' => 2, 'documenti' => 2, 'mailing' => 2, 'viste' => 2, 'report' => 2, 'settings' => 2, ]; $user->save(); return ['success' => true, 'message' => 'Amministratore creato con successo.']; } catch (\Exception $e) { return ['success' => false, 'message' => 'Errore creazione admin: ' . $e->getMessage()]; } } function getExistingUsers(): array { try { $kernel = bootstrapLaravel(); $users = \App\Models\User::all(['id', 'name', 'email', 'is_admin'])->toArray(); return $users; } catch (\Exception $e) { return []; } } function selfDestruct(): bool { $installerDir = __DIR__; if (!is_dir($installerDir)) { return false; } $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($installerDir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST ); foreach ($it as $file) { if ($file->isDir()) { rmdir($file->getPathname()); } else { unlink($file->getPathname()); } } rmdir($installerDir); return !is_dir($installerDir); } function renderHeader(string $title, string $step = ''): void { $stepLabel = $step ? " — Step {$step}" : ''; ?>
Installazione Wizard