534 lines
17 KiB
PHP
534 lines
17 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
// =============================================================================
|
||
|
|
// Glastree Installer - Helper Functions
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
function getBaseUrl(): string
|
||
|
|
{
|
||
|
|
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
|
||
|
|
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||
|
|
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
|
||
|
|
$scriptDir = str_replace('/installer', '', $scriptDir);
|
||
|
|
return $scheme . '://' . $host . $scriptDir;
|
||
|
|
}
|
||
|
|
|
||
|
|
function siteUrl(string $path = ''): string
|
||
|
|
{
|
||
|
|
return rtrim(getBaseUrl(), '/') . '/' . ltrim($path, '/');
|
||
|
|
}
|
||
|
|
|
||
|
|
function redirect(string $url): never
|
||
|
|
{
|
||
|
|
header('Location: ' . $url);
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isEnvFileExists(): bool
|
||
|
|
{
|
||
|
|
return file_exists(dirname(__DIR__) . '/.env');
|
||
|
|
}
|
||
|
|
|
||
|
|
function e(mixed $value): string
|
||
|
|
{
|
||
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||
|
|
}
|
||
|
|
|
||
|
|
function old(string $key, mixed $default = ''): string
|
||
|
|
{
|
||
|
|
return e($_POST[$key] ?? $_SESSION['form_data'][$key] ?? $default);
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkRequirements(): array
|
||
|
|
{
|
||
|
|
$checks = [];
|
||
|
|
|
||
|
|
// PHP version
|
||
|
|
$checks[] = [
|
||
|
|
'name' => '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}" : '';
|
||
|
|
?>
|
||
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="it">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
|
<title><?= e($title) ?> — Glastree Installer</title>
|
||
|
|
<link rel="stylesheet" href="assets/style.css">
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="installer-wrapper">
|
||
|
|
<div class="installer-header">
|
||
|
|
<div class="container">
|
||
|
|
<h1><i class="icon-tree"></i> Glastree</h1>
|
||
|
|
<p class="subtitle">Installazione Wizard</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="container">
|
||
|
|
<?php if ($step): ?>
|
||
|
|
<div class="step-indicator">Passo <?= e($step) ?> di 6</div>
|
||
|
|
<?php endif; ?>
|
||
|
|
<div class="card">
|
||
|
|
<div class="card-body">
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderFooter(): void
|
||
|
|
{
|
||
|
|
?>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="installer-footer">
|
||
|
|
<div class="container">
|
||
|
|
<p>Glastree © <?= date('Y') ?></p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderErrors(array $errors): void
|
||
|
|
{
|
||
|
|
if (empty($errors)) return;
|
||
|
|
?>
|
||
|
|
<div class="alert alert-danger">
|
||
|
|
<strong><i class="icon-error"></i> Errori:</strong>
|
||
|
|
<ul class="mb-0">
|
||
|
|
<?php foreach ($errors as $error): ?>
|
||
|
|
<li><?= e($error) ?></li>
|
||
|
|
<?php endforeach; ?>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
<?php
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderSuccess(string $message, array $log = []): void
|
||
|
|
{
|
||
|
|
?>
|
||
|
|
<div class="alert alert-success">
|
||
|
|
<strong><i class="icon-ok"></i> <?= e($message) ?></strong>
|
||
|
|
<?php if (!empty($log)): ?>
|
||
|
|
<ul class="mb-0 mt-2">
|
||
|
|
<?php foreach ($log as $item): ?>
|
||
|
|
<li><?= e($item) ?></li>
|
||
|
|
<?php endforeach; ?>
|
||
|
|
</ul>
|
||
|
|
<?php endif; ?>
|
||
|
|
</div>
|
||
|
|
<?php
|
||
|
|
}
|