exec($sql); $log[] = ['ok', $label]; } catch (PDOException $e) { // Ignore "already exists" / "duplicate column" errors (1060, 1061, 1050) if (in_array($e->errorInfo[1], [1060, 1061, 1050], true)) { $log[] = ['skip', $label . ' (already exists, skipped)']; } else { $errors[] = $label . ': ' . $e->getMessage(); $log[] = ['err', $label . ': ' . $e->getMessage()]; } } } // ── 1. Create users table ──────────────────────────────────────────────────── run_sql($pdo, 'Create users table', " CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, display_name VARCHAR(100) NULL, role ENUM('superadmin','admin','superuser','user') NOT NULL DEFAULT 'user', rosary_limit INT NOT NULL DEFAULT 1, email_confirmed TINYINT(1) NOT NULL DEFAULT 0, confirm_token VARCHAR(64) NULL, reset_token VARCHAR(64) NULL, reset_expires DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ", $log, $errors); // ── 2. Create site_settings table ─────────────────────────────────────────── run_sql($pdo, 'Create site_settings table', " CREATE TABLE IF NOT EXISTS site_settings ( key_name VARCHAR(100) PRIMARY KEY, val TEXT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ", $log, $errors); // ── 3. Create sessions table (fresh install) ──────────────────────────────── run_sql($pdo, 'Create sessions table', " CREATE TABLE IF NOT EXISTS sessions ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, occasion VARCHAR(50) NOT NULL, mystery_set VARCHAR(50) NOT NULL, novena_day TINYINT NULL, subject_name VARCHAR(255) NULL, subject_pronoun VARCHAR(10) NULL, subject_dates VARCHAR(150) NULL, photo_path VARCHAR(500) NULL, novena_group_id INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ", $log, $errors); // ── 4. Create novena_groups table (fresh install) ──────────────────────────── run_sql($pdo, 'Create novena_groups table', " CREATE TABLE IF NOT EXISTS novena_groups ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, mystery_set VARCHAR(50) NOT NULL DEFAULT 'sorrowful', subject_name VARCHAR(255) NULL, subject_pronoun VARCHAR(10) NULL, subject_dates VARCHAR(150) NULL, photo_path VARCHAR(500) NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ", $log, $errors); // ── 5. Add columns to sessions ─────────────────────────────────────────────── foreach ([ ['Add sessions.user_id', "ALTER TABLE sessions ADD COLUMN user_id INT NULL AFTER id"], ['Add sessions.is_public', "ALTER TABLE sessions ADD COLUMN is_public TINYINT(1) NOT NULL DEFAULT 1 AFTER user_id"], ['Add sessions.slug', "ALTER TABLE sessions ADD COLUMN slug VARCHAR(255) NULL AFTER is_public"], ] as [$label, $sql]) { run_sql($pdo, $label, $sql, $log, $errors); } // ── 6. Add columns to novena_groups ───────────────────────────────────────── foreach ([ ['Add novena_groups.user_id', "ALTER TABLE novena_groups ADD COLUMN user_id INT NULL AFTER id"], ['Add novena_groups.is_public', "ALTER TABLE novena_groups ADD COLUMN is_public TINYINT(1) NOT NULL DEFAULT 1 AFTER user_id"], ['Add novena_groups.slug', "ALTER TABLE novena_groups ADD COLUMN slug VARCHAR(255) NULL AFTER is_public"], ] as [$label, $sql]) { run_sql($pdo, $label, $sql, $log, $errors); } // ── 7. Seed site_settings ──────────────────────────────────────────────────── $settings = [ 'smtp_host' => '', 'smtp_port' => '587', 'smtp_user' => '', 'smtp_pass' => '', 'smtp_from' => '', 'smtp_from_name' => 'Rosary Presenter', 'site_name' => 'Rosary Presenter', 'site_url' => '', ]; $ins_setting = $pdo->prepare('INSERT IGNORE INTO site_settings (key_name, val) VALUES (?, ?)'); foreach ($settings as $k => $v) { try { $ins_setting->execute([$k, $v]); $log[] = ['ok', "Seeded site_settings: {$k}"]; } catch (PDOException $e) { $errors[] = "site_settings {$k}: " . $e->getMessage(); } } // ── 8. Seed superadmin user ────────────────────────────────────────────────── $supadmin_hash = password_hash('supadmin', PASSWORD_BCRYPT); try { $pdo->prepare(" INSERT IGNORE INTO users (username, email, password_hash, display_name, role, rosary_limit, email_confirmed) VALUES ('supadmin', 'admin@example.com', ?, 'Super Admin', 'superadmin', -1, 1) ")->execute([$supadmin_hash]); $log[] = ['ok', 'Seeded superadmin user']; } catch (PDOException $e) { $errors[] = 'Seed superadmin: ' . $e->getMessage(); } // Get superadmin ID $supadmin_row = $pdo->query("SELECT id FROM users WHERE username = 'supadmin'")->fetch(); $supadmin_id = $supadmin_row ? (int)$supadmin_row['id'] : null; if ($supadmin_id) { // ── 9. Assign unowned sessions to superadmin ───────────────────────────── try { $affected = $pdo->prepare("UPDATE sessions SET user_id = ? WHERE user_id IS NULL") ->execute([$supadmin_id]); $log[] = ['ok', 'Assigned orphan sessions to superadmin']; } catch (PDOException $e) { $errors[] = 'Assign sessions: ' . $e->getMessage(); } // ── 10. Assign unowned novena_groups to superadmin ──────────────────────── try { $pdo->prepare("UPDATE novena_groups SET user_id = ? WHERE user_id IS NULL") ->execute([$supadmin_id]); $log[] = ['ok', 'Assigned orphan novena_groups to superadmin']; } catch (PDOException $e) { $errors[] = 'Assign novena_groups: ' . $e->getMessage(); } // ── 11. Generate slugs for sessions without one ─────────────────────────── try { $sessions_no_slug = $pdo->query("SELECT id, name, user_id FROM sessions WHERE slug IS NULL OR slug = ''")->fetchAll(); $upd_slug = $pdo->prepare("UPDATE sessions SET slug = ? WHERE id = ?"); foreach ($sessions_no_slug as $row) { $uid = (int)($row['user_id'] ?? $supadmin_id); $base = slugify($row['name']); $slug = unique_slug($row['name'], $uid, 'sessions', (int)$row['id']); $upd_slug->execute([$slug, $row['id']]); } $log[] = ['ok', 'Generated slugs for ' . count($sessions_no_slug) . ' sessions']; } catch (PDOException $e) { $errors[] = 'Generate session slugs: ' . $e->getMessage(); } // ── 12. Generate slugs for novena_groups without one ───────────────────── try { $groups_no_slug = $pdo->query("SELECT id, name, user_id FROM novena_groups WHERE slug IS NULL OR slug = ''")->fetchAll(); $upd_gslug = $pdo->prepare("UPDATE novena_groups SET slug = ? WHERE id = ?"); foreach ($groups_no_slug as $row) { $uid = (int)($row['user_id'] ?? $supadmin_id); $slug = unique_slug($row['name'], $uid, 'novena_groups', (int)$row['id']); $upd_gslug->execute([$slug, $row['id']]); } $log[] = ['ok', 'Generated slugs for ' . count($groups_no_slug) . ' novena groups']; } catch (PDOException $e) { $errors[] = 'Generate novena_group slugs: ' . $e->getMessage(); } } // ── Render result page ─────────────────────────────────────────────────────── $overall_ok = empty($errors); ?> Migration v3 — <?= APP_NAME ?>

— Migration v3

⚠ DELETE this file (migrate_v3.php) from your server immediately after reviewing this page.

Superadmin Credentials

Username: supadmin
Password: supadmin
Role: superadmin

CHANGE THE PASSWORD IMMEDIATELY — go to /admin/profile after logging in.
Also update the email from admin@example.com to your real email.

Migration Log

StatusStep

Next Steps

  1. Delete migrate_v3.php from your server.
  2. Go to /login and sign in with supadmin / supadmin.
  3. Go to /admin/profile and change your password and email.
  4. Go to /admin/settings to configure SMTP and your site URL.