Files
glastree/installer/functions.php
T

534 lines
17 KiB
PHP
Raw Normal View History

2026-06-01 16:11:29 +02:00
<?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 &copy; <?= 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
}