diff --git a/db/migrate.php b/db/migrate.php
new file mode 100644
index 0000000..4b9832d
--- /dev/null
+++ b/db/migrate.php
@@ -0,0 +1,92 @@
+exec(
+ "\n CREATE TABLE IF NOT EXISTS migrations (\n id INT AUTO_INCREMENT PRIMARY KEY,\n migration_name VARCHAR(255) NOT NULL UNIQUE,\n executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n "
+ );
+ } catch (PDOException $e) {
+ return ["Error creating migrations table: " . $e->getMessage()];
+ }
+
+ // 2. Get executed migrations
+ $executed = [];
+ try {
+ $stmt = $pdo->query("SELECT migration_name FROM migrations");
+ while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $executed[] = $row['migration_name'];
+ }
+ } catch (PDOException $e) {
+ return ["Error fetching executed migrations: " . $e->getMessage()];
+ }
+
+ // 3. Scan for migration files
+ $migrationFiles = glob(__DIR__ . '/migrations/*.php');
+ if ($migrationFiles === false) {
+ return ["Error scanning migration directory."];
+ }
+
+ // Sort files to ensure order (by name usually works if named correctly)
+ sort($migrationFiles);
+
+ $count = 0;
+ foreach ($migrationFiles as $file) {
+ $filename = basename($file);
+
+ if (in_array($filename, $executed)) {
+ continue;
+ }
+
+ // Run migration
+ try {
+ // We use output buffering to capture echo output from migration files
+ ob_start();
+ // Include inside a closure/function scope to avoid variable collisions
+ // but we need to ensure $pdo is available if they use it.
+ // Most files do: require config.php; $pdo = db();
+ // Since we already required config.php, require_once will skip it.
+ // So they will just get $pdo = db(); which works.
+ include $file;
+ $output = ob_get_clean();
+
+ // Log success
+ $messages[] = "Executed $filename: " . trim($output);
+
+ // Record in DB
+ $stmt = $pdo->prepare("INSERT INTO migrations (migration_name) VALUES (?)");
+ $stmt->execute([$filename]);
+ $count++;
+
+ } catch (Throwable $e) {
+ ob_end_clean(); // Clean buffer if error
+ $messages[] = "Failed to execute $filename: " . $e->getMessage();
+ return $messages; // Stop on error
+ }
+ }
+
+ if ($count === 0) {
+ $messages[] = "No new migrations to run.";
+ } else {
+ $messages[] = "Successfully ran $count migrations.";
+ }
+
+ return $messages;
+}
+
+// If run directly from CLI
+if (php_sapi_name() === 'cli' && basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME'])) {
+ $results = run_migrations();
+ echo implode("\n", $results) . "\n";
+}
diff --git a/db/migrations/initial_schema.php b/db/migrations/initial_schema.php
index 2d4c91d..e48f9c3 100644
--- a/db/migrations/initial_schema.php
+++ b/db/migrations/initial_schema.php
@@ -1,5 +1,5 @@
getMessage();
-}
+}
\ No newline at end of file
diff --git a/install.php b/install.php
index d01da28..711fefb 100644
--- a/install.php
+++ b/install.php
@@ -2,7 +2,12 @@
// install.php - Simple Installer for Flatlogic LAMP Project
session_start();
-$step = $_GET['step'] ?? 1;
+// Ensure errors are displayed for debugging during installation
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+error_reporting(E_ALL);
+
+$step = isset($_GET['step']) ? (int)$_GET['step'] : 1;
$message = '';
$messageType = '';
@@ -37,7 +42,10 @@ function write_db_config($host, $name, $user, $pass) {
// Handle Form Submissions
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- if ($step == 1) {
+ // Determine step from hidden field or GET, defaulting to 1
+ $postStep = isset($_POST['step']) ? (int)$_POST['step'] : $step;
+
+ if ($postStep == 1) {
$host = $_POST['db_host'] ?? '';
$name = $_POST['db_name'] ?? '';
$user = $_POST['db_user'] ?? '';
@@ -63,36 +71,73 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$message = "Connection failed: " . $e->getMessage();
$messageType = "danger";
}
- } elseif ($step == 2) {
- require_once __DIR__ . '/includes/app.php';
+ } elseif ($postStep == 2) {
+ // Step 2: Create Admin & Run Migrations
+ // Include config and migration script
+ if (file_exists(__DIR__ . '/db/config.php')) {
+ require_once __DIR__ . '/db/config.php';
+ } else {
+ $message = "Configuration file missing. Please go back to Step 1.";
+ $messageType = "danger";
+ $step = 1;
+ }
+
+ if (file_exists(__DIR__ . '/db/migrate.php')) {
+ require_once __DIR__ . '/db/migrate.php';
+ }
+
$email = $_POST['admin_email'] ?? '';
$password = $_POST['admin_pass'] ?? '';
$fullName = $_POST['admin_name'] ?? 'Administrator';
- if ($email && $password) {
+ if ($email && $password && defined('DB_HOST')) {
try {
- ensure_schema(); // Make sure tables exist
+ // 1. Run Migrations
+ $migrationResults = [];
+ if (function_exists('run_migrations')) {
+ $migrationResults = run_migrations();
+ }
+
+ // 2. Also ensure basic schema from app.php (just in case)
+ // We do this after migrations so migrations take precedence if they exist
+ if (file_exists(__DIR__ . '/includes/app.php')) {
+ // We catch output to prevent it from messing up headers/layout if app.php has echoes
+ ob_start();
+ require_once __DIR__ . '/includes/app.php';
+ if (function_exists('ensure_schema')) {
+ ensure_schema();
+ }
+ ob_end_clean();
+ }
+
+ // 3. Create Admin User
+ $pdo = db();
// Check if admin exists
- $stmt = db()->prepare("SELECT id FROM users WHERE email = ?");
+ $stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$email]);
if ($stmt->fetch()) {
// Update existing
- $stmt = db()->prepare("UPDATE users SET password = ?, full_name = ?, role = 'admin', status = 'active' WHERE email = ?");
+ $stmt = $pdo->prepare("UPDATE users SET password = ?, full_name = ?, role = 'admin', status = 'active' WHERE email = ?");
$stmt->execute([password_hash($password, PASSWORD_DEFAULT), $fullName, $email]);
} else {
// Create new
- $stmt = db()->prepare("INSERT INTO users (email, password, full_name, role, status) VALUES (?, ?, ?, 'admin', 'active')");
+ $stmt = $pdo->prepare("INSERT INTO users (email, password, full_name, role, status) VALUES (?, ?, ?, 'admin', 'active')");
$stmt->execute([$email, password_hash($password, PASSWORD_DEFAULT), $fullName]);
}
- $message = "Admin account created successfully!";
+ $migMsg = implode("
", $migrationResults);
+ $message = "Admin account created successfully!
$migMsg";
$messageType = "success";
$step = 3; // Success page
+
} catch (Exception $e) {
- $message = "Error creating admin: " . $e->getMessage();
+ $message = "Error: " . $e->getMessage();
+ $messageType = "danger";
+ } catch (Throwable $e) {
+ $message = "Fatal Error: " . $e->getMessage();
$messageType = "danger";
}
} else {
@@ -149,6 +194,7 @@ if (file_exists(__DIR__ . '/db/config.php')) {