From 9b7d9ceccb45e15c1f90543a51ecb49b45138392 Mon Sep 17 00:00:00 2001 From: David Dyer Date: Sat, 29 Aug 2026 21:57:42 -0400 Subject: [PATCH] Add SQLite-backed announcement system and admin portal - Add SQLite database initialization and management in inc/db.php - Implement session authentication and CSRF security helpers in inc/auth.php - Implement announcement querying and saving in inc/announcements.php - Add management dashboard, login, and logout in admin/ - Protect db/ directory with Apache access controls --- admin/index.php | 361 ++++++++++++++++++++++++++++++++++++++++++ admin/login.php | 150 ++++++++++++++++++ admin/logout.php | 5 + db/.htaccess | 1 + inc/announcements.php | 80 ++++++++++ inc/auth.php | 82 ++++++++++ inc/db.php | 82 ++++++++++ 7 files changed, 761 insertions(+) create mode 100644 admin/index.php create mode 100644 admin/login.php create mode 100644 admin/logout.php create mode 100644 db/.htaccess create mode 100644 inc/announcements.php create mode 100644 inc/auth.php create mode 100644 inc/db.php diff --git a/admin/index.php b/admin/index.php new file mode 100644 index 0000000..84a369d --- /dev/null +++ b/admin/index.php @@ -0,0 +1,361 @@ + isset($_POST['is_active']) ? 1 : 0, + 'type' => $_POST['type'] ?? 'holiday', + 'headline' => $_POST['headline'] ?? '', + 'message' => $_POST['message'] ?? '', + 'start_date' => $_POST['start_date'] ?? null, + 'end_date' => $_POST['end_date'] ?? null, + 'show_banner' => isset($_POST['show_banner']) ? 1 : 0, + 'show_in_hours' => isset($_POST['show_in_hours']) ? 1 : 0, + ], $annId); + + $successMsg = 'Announcement updated successfully!'; + } +} + +// Fetch current announcement (or default empty template) +$announcement = get_active_announcement(); +if (!$announcement) { + $stmt = $pdo->query("SELECT * FROM announcements ORDER BY id DESC LIMIT 1"); + $announcement = $stmt->fetch() ?: [ + 'id' => 0, + 'is_active' => 0, + 'type' => 'holiday', + 'headline' => 'Holiday Closure', + 'message' => 'The Old Fitz will be closed for the holiday weekend.', + 'start_date' => '', + 'end_date' => '', + 'show_banner' => 1, + 'show_in_hours' => 1 + ]; +} +?> + + + + + + Pub Management Dashboard | The Old Fitz + + + + + + + + + + +
+
+
+

Content & Operations

+

Site Announcements & Alerts

+
+ + +
+ + + + + +
+ + + + +
+
+

+ 📢 Announcement Settings +

+ + ● Live on site + + ○ Inactive / Off + +
+ +
+
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Leave blank to show immediately while enabled. +
+ +
+ + + Auto-hides after 11:59 PM on this date. +
+ +
+ +
+ + +
+
+
+ +
+ +
+
+
+
+
+ + + diff --git a/admin/login.php b/admin/login.php new file mode 100644 index 0000000..f36619e --- /dev/null +++ b/admin/login.php @@ -0,0 +1,150 @@ + + + + + + + Admin Login | The Old Fitz Public House + + + + + + + +
+ + + + + + +
+
+ + +
+
+ + +
+ +
+ + ← Return to Pub Website +
+ + diff --git a/admin/logout.php b/admin/logout.php new file mode 100644 index 0000000..72bf39d --- /dev/null +++ b/admin/logout.php @@ -0,0 +1,5 @@ += :today2) + ORDER BY id DESC + LIMIT 1 + "; + + $stmt = $pdo->prepare($sql); + $stmt->execute([ + ':today1' => $today, + ':today2' => $today + ]); + $result = $stmt->fetch(); + + return $result ?: null; +} + +function get_all_announcements(): array { + $pdo = get_db(); + $stmt = $pdo->query("SELECT * FROM announcements ORDER BY id DESC"); + return $stmt->fetchAll(); +} + +function save_announcement(array $data, ?int $id = null): int { + $pdo = get_db(); + + $isActive = !empty($data['is_active']) ? 1 : 0; + $type = in_array($data['type'] ?? '', ['holiday', 'special', 'closure']) ? $data['type'] : 'holiday'; + $headline = trim($data['headline'] ?? ''); + $message = trim($data['message'] ?? ''); + $startDate = !empty($data['start_date']) ? trim($data['start_date']) : null; + $endDate = !empty($data['end_date']) ? trim($data['end_date']) : null; + $showBanner = !empty($data['show_banner']) ? 1 : 0; + $showInHours = !empty($data['show_in_hours']) ? 1 : 0; + + if ($id) { + $stmt = $pdo->prepare(" + UPDATE announcements SET + is_active = ?, + type = ?, + headline = ?, + message = ?, + start_date = ?, + end_date = ?, + show_banner = ?, + show_in_hours = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ? + "); + $stmt->execute([ + $isActive, $type, $headline, $message, $startDate, $endDate, + $showBanner, $showInHours, $id + ]); + return $id; + } else { + $stmt = $pdo->prepare(" + INSERT INTO announcements ( + is_active, type, headline, message, start_date, end_date, show_banner, show_in_hours + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + "); + $stmt->execute([ + $isActive, $type, $headline, $message, $startDate, $endDate, + $showBanner, $showInHours + ]); + return (int)$pdo->lastInsertId(); + } +} diff --git a/inc/auth.php b/inc/auth.php new file mode 100644 index 0000000..9e06ec9 --- /dev/null +++ b/inc/auth.php @@ -0,0 +1,82 @@ + 86400 * 7, // 7 days + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Lax' + ]); + session_start(); +} + +require_once __DIR__ . '/db.php'; + +function is_authenticated(): bool { + return !empty($_SESSION['admin_user_id']); +} + +function require_auth(): void { + if (!is_authenticated()) { + header('Location: login.php'); + exit; + } +} + +function get_logged_in_user(): ?array { + if (!is_authenticated()) { + return null; + } + return [ + 'id' => $_SESSION['admin_user_id'], + 'username' => $_SESSION['admin_username'] ?? 'admin', + 'role' => $_SESSION['admin_role'] ?? 'admin' + ]; +} + +function authenticate_user(string $username, string $password): bool { + $pdo = get_db(); + $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); + $stmt->execute([trim($username)]); + $user = $stmt->fetch(); + + if ($user && password_verify($password, $user['password_hash'])) { + session_regenerate_id(true); + $_SESSION['admin_user_id'] = $user['id']; + $_SESSION['admin_username'] = $user['username']; + $_SESSION['admin_role'] = $user['role']; + return true; + } + + return false; +} + +function logout_user(): void { + $_SESSION = []; + if (ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, + $params["path"], $params["domain"], + $params["secure"], $params["httponly"] + ); + } + session_destroy(); +} + +function get_csrf_token(): string { + if (empty($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); + } + return $_SESSION['csrf_token']; +} + +function verify_csrf_token(?string $token): bool { + if (empty($_SESSION['csrf_token']) || empty($token)) { + return false; + } + return hash_equals($_SESSION['csrf_token'], $token); +} diff --git a/inc/db.php b/inc/db.php new file mode 100644 index 0000000..9a8ca36 --- /dev/null +++ b/inc/db.php @@ -0,0 +1,82 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_TIMEOUT => 5 + ]); + + // Enable WAL mode for concurrency and performance + $pdo->exec('PRAGMA journal_mode = WAL;'); + $pdo->exec('PRAGMA foreign_keys = ON;'); + + // Initialize tables if needed + init_db_schema($pdo); + + return $pdo; +} + +function init_db_schema(PDO $pdo): void { + $pdo->exec(" + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + role TEXT NOT NULL DEFAULT 'admin', + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS announcements ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + is_active INTEGER NOT NULL DEFAULT 1, + type TEXT NOT NULL DEFAULT 'holiday', + headline TEXT NOT NULL, + message TEXT NOT NULL, + start_date TEXT NULL, + end_date TEXT NULL, + show_banner INTEGER NOT NULL DEFAULT 1, + show_in_hours INTEGER NOT NULL DEFAULT 1, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + "); + + // Seed default admin user if table is empty (username: admin / password: password) + $stmt = $pdo->query("SELECT COUNT(*) as count FROM users"); + $userCount = $stmt->fetchColumn(); + if ($userCount == 0) { + $defaultPassHash = password_hash('password', PASSWORD_DEFAULT); + $insert = $pdo->prepare("INSERT INTO users (username, password_hash, role) VALUES (?, ?, 'admin')"); + $insert->execute(['admin', $defaultPassHash]); + } + + // Seed default Labor Day announcement row if table is empty + $stmtAnn = $pdo->query("SELECT COUNT(*) as count FROM announcements"); + $annCount = $stmtAnn->fetchColumn(); + if ($annCount == 0) { + $insertAnn = $pdo->prepare(" + INSERT INTO announcements ( + is_active, type, headline, message, start_date, end_date, show_banner, show_in_hours + ) VALUES ( + 1, 'holiday', 'Labor Day Closure', 'The Old Fitz will be closed Thursday, Sept 3 through Saturday, Sept 5 for the holiday weekend. Doors reopen Thursday, Sept 10 at 4:00 PM!', NULL, NULL, 1, 1 + ) + "); + $insertAnn->execute(); + } +}