Add online menu management, modernized and hardened
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>
This commit is contained in:
1 parent
21ad6c31cf
commit
f1bbf59548
8 files changed
+1382
-27
No files matched your search
+135
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Menu helper functions
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/db.php';
|
||||
|
||||
function get_full_menu(bool $availableOnly = false): array {
|
||||
$pdo = get_db();
|
||||
|
||||
$catStmt = $pdo->query("SELECT * FROM menu_categories ORDER BY sort_order ASC, id ASC");
|
||||
$categories = $catStmt->fetchAll() ?: [];
|
||||
|
||||
$itemSql = "SELECT * FROM menu_items";
|
||||
if ($availableOnly) {
|
||||
$itemSql .= " WHERE is_available = 1";
|
||||
}
|
||||
$itemSql .= " ORDER BY sort_order ASC, id ASC";
|
||||
|
||||
$itemStmt = $pdo->query($itemSql);
|
||||
$items = $itemStmt->fetchAll() ?: [];
|
||||
|
||||
// Group items by category_id
|
||||
$grouped = [];
|
||||
foreach ($categories as $cat) {
|
||||
$cat['items'] = [];
|
||||
$grouped[$cat['id']] = $cat;
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (isset($grouped[$item['category_id']])) {
|
||||
$grouped[$item['category_id']]['items'][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($grouped);
|
||||
}
|
||||
|
||||
function get_all_categories(): array {
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->query("SELECT * FROM menu_categories ORDER BY sort_order ASC, id ASC");
|
||||
return $stmt->fetchAll() ?: [];
|
||||
}
|
||||
|
||||
function get_category_by_id(int $id): ?array {
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM menu_categories WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$res = $stmt->fetch();
|
||||
return $res ?: null;
|
||||
}
|
||||
|
||||
function category_exists(int $id): bool {
|
||||
if ($id <= 0) {
|
||||
return false;
|
||||
}
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT 1 FROM menu_categories WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
return (bool)$stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function save_category(array $data, ?int $id = null): int {
|
||||
$pdo = get_db();
|
||||
$name = trim($data['name'] ?? '');
|
||||
$description = trim($data['description'] ?? '');
|
||||
$sortOrder = (int)($data['sort_order'] ?? 0);
|
||||
|
||||
if ($id && $id > 0) {
|
||||
$stmt = $pdo->prepare("UPDATE menu_categories SET name = ?, description = ?, sort_order = ? WHERE id = ?");
|
||||
$stmt->execute([$name, $description, $sortOrder, $id]);
|
||||
return $id;
|
||||
} else {
|
||||
$stmt = $pdo->prepare("INSERT INTO menu_categories (name, description, sort_order) VALUES (?, ?, ?)");
|
||||
$stmt->execute([$name, $description, $sortOrder]);
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
function delete_category(int $id): bool {
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("DELETE FROM menu_categories WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
function get_menu_item_by_id(int $id): ?array {
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("SELECT * FROM menu_items WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$res = $stmt->fetch();
|
||||
return $res ?: null;
|
||||
}
|
||||
|
||||
function save_menu_item(array $data, ?int $id = null): int {
|
||||
$pdo = get_db();
|
||||
$categoryId = (int)($data['category_id'] ?? 0);
|
||||
$name = trim($data['name'] ?? '');
|
||||
$description = trim($data['description'] ?? '');
|
||||
$price = trim($data['price'] ?? '');
|
||||
$badge = trim($data['badge'] ?? '');
|
||||
$isAvailable = !empty($data['is_available']) ? 1 : 0;
|
||||
$sortOrder = (int)($data['sort_order'] ?? 0);
|
||||
|
||||
if ($id && $id > 0) {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE menu_items SET
|
||||
category_id = ?, name = ?, description = ?, price = ?,
|
||||
badge = ?, is_available = ?, sort_order = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$categoryId, $name, $description, $price, $badge, $isAvailable, $sortOrder, $id]);
|
||||
return $id;
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO menu_items (category_id, name, description, price, badge, is_available, sort_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
");
|
||||
$stmt->execute([$categoryId, $name, $description, $price, $badge, $isAvailable, $sortOrder]);
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_menu_item_availability(int $id): bool {
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("UPDATE menu_items SET is_available = CASE WHEN is_available = 1 THEN 0 ELSE 1 END, updated_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
|
||||
function delete_menu_item(int $id): bool {
|
||||
$pdo = get_db();
|
||||
$stmt = $pdo->prepare("DELETE FROM menu_items WHERE id = ?");
|
||||
return $stmt->execute([$id]);
|
||||
}
|
||||
Reference in new issue
Block a user