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>
136 lines
4.2 KiB
PHP
136 lines
4.2 KiB
PHP
<?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]);
|
|
}
|