Adds a public menu page (menu.php) and an admin CRUD interface (admin/menu.php + admin/menu.js) backed by new menu_categories and menu_items tables, replacing the "menu lives on Facebook" placeholder on the homepage. The admin JS/PHP came out of an AI-assisted pass that used outdated JS (var, .then() chains, string-built HTML) and had some rough edges security-wise, so this also: - Rewrites the admin JS to modern ES (const/let, async/await, template literals, dataset API) and splits it out of admin/menu.php into admin/menu.js for proper separation of concerns - Adds declare(strict_types=1) and PHP 8 match-based action dispatch in place of an if-chain - Validates category_id/item_id/category_id server-side before use, so a bad ID returns a clean JSON error instead of an uncaught PDOException or a null item breaking the client - Rejects non-string POST fields instead of crashing trim() on them - Replaces the category-save handler's full-page location.reload() with in-place DOM updates (panel insert/update, resort by sort_order, keep the item modal's category dropdown in sync), so every admin action is now AJAX with no reload - Sets data-name/data-desc via dataset assignment rather than string- interpolating them into an HTML attribute, closing a stored-XSS path through the category name/description fields Removes seed-menu.php, a leftover stub with no working code left in it after the AI's one-time seeding run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
91 lines
2.9 KiB
PHP
91 lines
2.9 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Database connection & schema management for SQLite
|
|
*/
|
|
|
|
function get_db(): PDO {
|
|
static $pdo = null;
|
|
if ($pdo !== null) {
|
|
return $pdo;
|
|
}
|
|
|
|
$dbDir = __DIR__ . '/../db';
|
|
if (!is_dir($dbDir)) {
|
|
mkdir($dbDir, 0755, true);
|
|
}
|
|
|
|
$dbPath = $dbDir . '/fitz.sqlite';
|
|
$isNew = !file_exists($dbPath);
|
|
|
|
$pdo = new PDO('sqlite:' . $dbPath, null, null, [
|
|
PDO::ATTR_ERRMODE => 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
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS menu_categories (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
description TEXT NULL,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS menu_items (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
category_id INTEGER NOT NULL REFERENCES menu_categories(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
description TEXT NULL,
|
|
price TEXT NOT NULL,
|
|
badge TEXT NULL,
|
|
is_available INTEGER NOT NULL DEFAULT 1,
|
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
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]);
|
|
}
|
|
}
|