38929-vm/install.php
2026-03-09 10:29:02 +00:00

306 lines
14 KiB
PHP

<?php
session_start();
$lockFile = __DIR__ . '/install.lock';
if (file_exists($lockFile)) {
die("Installation is locked. Remove 'install.lock' to re-run the installer.");
}
$step = isset($_GET['step']) ? (int)$_GET['step'] : 1;
$error = '';
$success = '';
// Helper to run SQL file
function runSqlFile($pdo, $filePath) {
if (!file_exists($filePath)) return false;
$sql = file_get_contents($filePath);
$queries = preg_split("/;+(?=[^']*'([^']*'[^']*')*[^']*$)/", $sql);
foreach ($queries as $query) {
$query = trim($query);
if ($query) {
try {
$pdo->exec($query);
} catch (PDOException $e) {
$msg = $e->getMessage();
if (
strpos($msg, 'already exists') !== false ||
strpos($msg, 'Duplicate column name') !== false ||
strpos($msg, 'Duplicate key name') !== false ||
strpos($msg, 'Duplicate entry') !== false ||
strpos($msg, "Can't DROP") !== false ||
strpos($msg, "Cannot drop index") !== false ||
strpos($msg, "needed in a foreign key constraint") !== false ||
strpos($msg, 'check that column/key exists') !== false ||
strpos($msg, 'Unknown table') !== false
) {
continue;
}
throw $e;
}
}
}
return true;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($step === 2) {
$host = $_POST['db_host'] ?? '127.0.0.1';
$user = $_POST['db_user'] ?? '';
$pass = $_POST['db_pass'] ?? '';
$name = $_POST['db_name'] ?? '';
try {
$pdo = new PDO("mysql:host=$host;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
$pdo->exec("USE `$name` ");
// Read existing helpers from current config if it exists
$helpers = '';
if (file_exists(__DIR__ . '/db/config.php')) {
$currentConfig = file_get_contents(__DIR__ . '/db/config.php');
// Extract functions after the db() function or constants
if (preg_match('/function check_permission.*$/s', $currentConfig, $matches)) {
$helpers = $matches[0];
}
}
// If helpers weren't found, use a set of default ones
if (empty($helpers)) {
$helpers = "
function check_permission(\$page = null, \$user_id = null) {
if (!\$user_id) \$user_id =
eg_SESSION['user_id'] ?? null;
if (!\$page) \$page = basename(
eg_SERVER['PHP_SELF']);
if (!\$user_id) return ['view' => 0, 'add' => 0, 'edit' => 0, 'delete' => 0];
static \$permissions_cache = [];
\$cache_key = \$user_id . '_' . \$page;
if (isset(
eg_permissions_cache[\$cache_key])) return \$permissions_cache[\$cache_key];
\$stmt = db()->prepare(\"SELECT can_view as view, can_add as `add`, can_edit as edit, can_delete as `delete` FROM user_permissions WHERE user_id = ? AND page = ?\");
\$stmt->execute([\$user_id, \$page]);
\$perms = \$stmt->fetch();
if (!\$perms) return ['view' => 0, 'add' => 0, 'edit' => 0, 'delete' => 0];
\$result = ['view' => (int)
eg_perms['view'], 'add' => (int)
eg_perms['add'], 'edit' => (int)
eg_perms['edit'], 'delete' => (int)
eg_perms['delete']];
\$permissions_cache[\$cache_key] = \$result;
return \$result;
}
function has_permission(\$action, \$page = null, \$user_id = null) {
\$perms = check_permission(
eg_page, \$user_id);
return !empty(
eg_perms[\$action]);
}";
}
$configContent = "<?php\n"
. "define('DB_HOST', '$host');\n"
. "define('DB_NAME', '$name');\n"
. "define('DB_USER', '$user');\n"
. "define('DB_PASS', '$pass');\n\n"
. "function db() {\n"
. " static \$pdo;
"
. " if (!\$pdo) {\n"
. " \$pdo = new PDO(\"mysql:host=\" . DB_HOST . \";dbname=\" . DB_NAME . \";charset=utf8mb4\", DB_USER, DB_PASS, [\n"
. " PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n"
. " PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n"
. " PDO::ATTR_EMULATE_PREPARES => false,\n"
. " ]);\n"
. " }\n"
. " return \$pdo;\n"
. "}\n\n"
. trim($helpers) . "\n";
if (!is_dir(__DIR__ . '/db')) mkdir(__DIR__ . '/db', 0755, true);
file_put_contents(__DIR__ . '/db/config.php', $configContent);
// Run initial schema
$pdo->exec("SET FOREIGN_KEY_CHECKS = 0;");
$migrations = glob(__DIR__ . "/db/migrations/*.sql"); sort($migrations); foreach ($migrations as $migration) { runSqlFile($pdo, $migration); }
$pdo->exec("SET FOREIGN_KEY_CHECKS = 1;");
$_SESSION['db_configured'] = true;
header("Location: install.php?step=3");
exit;
} catch (PDOException $e) {
$error = "Connection failed: " . $e->getMessage();
}
} elseif ($step === 3) {
require_once __DIR__ . '/db/config.php';
$user = $_POST['admin_user'] ?? '';
$pass = $_POST['admin_pass'] ?? '';
$email = $_POST['admin_email'] ?? '';
if (empty($user) || empty($pass)) {
$error = "Username and password are required.";
} else {
try {
$hash = password_hash($pass, PASSWORD_DEFAULT);
$db = db();
// Clear existing users
$db->exec("SET FOREIGN_KEY_CHECKS = 0;");
$db->exec("TRUNCATE users;");
$db->exec("SET FOREIGN_KEY_CHECKS = 1;");
// Ensure company and branch exist (schema usually seeds them, but we ensure)
$db->exec("INSERT IGNORE INTO companies (id, name_en, name_ar) VALUES (1, 'Laundry Brand', 'علامة غسيل')");
$db->exec("INSERT IGNORE INTO branches (id, company_id, name_en, name_ar) VALUES (1, 1, 'Main Branch', 'الفرع الرئيسي')");
$stmt = $db->prepare("INSERT INTO users (branch_id, company_id, username, password_hash, full_name_en, role, email) VALUES (1, 1, ?, ?, 'System Administrator', 'super_admin', ?)");
$stmt->execute([$user, $hash, $email]);
file_put_contents($lockFile, date('Y-m-d H:i:s'));
header("Location: install.php?step=4");
exit;
} catch (Exception $e) {
$error = "Admin setup failed: " . $e->getMessage();
}
}
}
}
// Environment Checks
$checks = [
'PHP Version >= 8.0' => version_compare(PHP_VERSION, '8.0.0', '>='),
'PDO Extension' => extension_loaded('pdo_mysql'),
'Config Writable' => is_writable(__DIR__ . '/db/config.php') || is_writable(__DIR__),
'Assets Writable' => (is_writable(__DIR__ . '/assets/images') || (is_dir(__DIR__ . '/assets/images') && is_writable(__DIR__ . '/assets/images')))
];
$envReady = !in_array(false, $checks, true);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fresh Installation</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { background: #f0f2f5; font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
.install-card { width: 100%; max-width: 550px; padding: 2.5rem; border-radius: 20px; box-shadow: 0 15px 35px rgba(0,0,0,0.05); background: #fff; }
.step-indicator { display: flex; justify-content: space-between; margin-bottom: 2.5rem; position: relative; }
.step-indicator::before { content: ''; position: absolute; top: 15px; left: 0; right: 0; height: 2px; background: #e9ecef; z-index: 1; }
.step-dot { width: 32px; height: 32px; border-radius: 50%; background: #e9ecef; display: flex; align-items: center; justify-content: center; font-weight: 600; color: #6c757d; z-index: 2; position: relative; transition: all 0.3s ease; }
.step-dot.active { background: #0d6efd; color: #fff; transform: scale(1.1); box-shadow: 0 0 15px rgba(13, 110, 253, 0.3); }
.step-dot.completed { background: #198754; color: #fff; }
h3 { font-weight: 700; color: #1a1d20; letter-spacing: -0.5px; }
h5 { font-weight: 600; color: #495057; margin-bottom: 1.5rem; }
.form-label { font-weight: 500; color: #495057; }
.btn-primary { padding: 0.75rem; font-weight: 600; border-radius: 10px; transition: all 0.2s; }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(13, 110, 253, 0.2); }
</style>
</head>
<body>
<div class="install-card">
<div class="text-center mb-4">
<h3 class="mb-1">Setup Wizard</h3>
<p class="text-muted small">Step-by-step application installation</p>
</div>
<div class="step-indicator">
<div class="step-dot <?php echo $step >= 1 ? ($step > 1 ? 'completed' : 'active') : ''; ?>">1</div>
<div class="step-dot <?php echo $step >= 2 ? ($step > 2 ? 'completed' : 'active') : ''; ?>">2</div>
<div class="step-dot <?php echo $step >= 3 ? ($step > 3 ? 'completed' : 'active') : ''; ?>">3</div>
<div class="step-dot <?php echo $step >= 4 ? ($step > 4 ? 'completed' : 'active') : ''; ?>">4</div>
</div>
<?php if ($error): ?>
<div class="alert alert-danger border-0 shadow-sm mb-4"><?php echo $error; ?></div>
<?php endif; ?>
<?php if ($step === 1): ?>
<h5>Checking Environment</h5>
<div class="list-group list-group-flush mb-4 border rounded-3 overflow-hidden">
<?php foreach ($checks as $label => $pass):
?>
<div class="list-group-item d-flex justify-content-between align-items-center py-3">
<span class="text-secondary"><?php echo $label; ?></span>
<?php if ($pass):
?>
<span class="badge bg-success-subtle text-success px-3 py-2 rounded-pill">Passed</span>
<?php else:
?>
<span class="badge bg-danger-subtle text-danger px-3 py-2 rounded-pill">Failed</span>
<?php endif;
?>
</div>
<?php endforeach;
?>
</div>
<div class="d-grid">
<a href="?step=2" class="btn btn-primary <?php echo !$envReady ? 'disabled' : ''; ?>">Continue to Database</a>
</div>
<?php elseif ($step === 2):
?>
<h5>Database Configuration</h5>
<form method="POST">
<div class="mb-3">
<label class="form-label">Database Host</label>
<input type="text" name="db_host" class="form-control form-control-lg bg-light border-0" value="127.0.0.1" required>
</div>
<div class="mb-3">
<label class="form-label">Database Name</label>
<input type="text" name="db_name" class="form-control form-control-lg bg-light border-0" placeholder="e.g. laundry_app" required>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Username</label>
<input type="text" name="db_user" class="form-control form-control-lg bg-light border-0" required>
</div>
<div class="col-md-6">
<label class="form-label">Password</label>
<input type="password" name="db_pass" class="form-control form-control-lg bg-light border-0">
</div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">Initialize Database</button>
</div>
</form>
<?php elseif ($step === 3):
?>
<h5>Administrator Account</h5>
<form method="POST">
<div class="mb-3">
<label class="form-label">Admin Username</label>
<input type="text" name="admin_user" class="form-control form-control-lg bg-light border-0" value="admin" required>
</div>
<div class="mb-3">
<label class="form-label">Email Address</label>
<input type="email" name="admin_email" class="form-control form-control-lg bg-light border-0" placeholder="admin@domain.com">
</div>
<div class="mb-4">
<label class="form-label">Password</label>
<input type="password" name="admin_pass" class="form-control form-control-lg bg-light border-0" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary btn-lg">Complete Setup</button>
</div>
</form>
<?php elseif ($step === 4):
?>
<div class="text-center">
<div class="mb-4">
<div class="bg-success-subtle text-success d-inline-flex align-items-center justify-content-center rounded-circle" style="width: 80px; height: 80px;">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="currentColor" class="bi bi-check2-all" viewBox="0 0 16 16">
<path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"/>
<path d="m5.354 7.146.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708z"/>
</svg>
</div>
</div>
<h5>Installation Complete!</h5>
<p class="text-muted mb-4">The application has been configured and the administrator account is ready.</p>
<div class="d-grid gap-2">
<a href="login.php" class="btn btn-primary btn-lg">Login to System</a>
<p class="small text-danger mt-3">Warning: Delete install.php for production security.</p>
</div>
</div>
<?php endif;
?>
</div>
</body>
</html>