GitHunt
ZA

zaxwebs/sfa-restaurant-menu

Single-File Restaurant Menu + Admin β€” README

A tiny, batteries-included restaurant menu with an admin UI. It’s one PHP file, uses SQLite for storage, and Bootstrap 5 for styling.


✨ Features

  • Public menu with search, category & Veg/Non-Veg filters
  • Admin dashboard: add/edit/delete items and categories
  • Image uploads (JPG/PNG/GIF/WebP, ~3 MB limit)
  • JSON export/import of categories & items
  • Built-in auth (admin user), CSRF protection, and basic rate limiting
  • Zero-setup SQLite DB (menu.db), single-file deploy
  • Sensible security headers + strict HTML escaping

πŸš€ Quickstart

  1. Requirements

    • PHP 8.0+
    • PDO SQLite & SQLite3 extensions enabled
    • fileinfo extension (for MIME checking)
    • Web server user must be able to write to the app directory (creates menu.db/ and uploads/)
  2. Deploy

    • Save the provided PHP file as index.php (or any name) in a PHP-enabled web root.
    • Ensure an images fallback exists at images/default-item.webp (see β€œAssets” below).
    • Visit the site root for the public menu: /?
    • Visit the admin: /?admin=1&page=dashboard
      • Default login: admin / changeme β†’ immediately change it in Admin β†’ Settings.
  3. First steps (Admin)

    • Create Categories in Admin β†’ Categories
    • Add Items in Admin β†’ Add Item
    • (Optional) Upload item images; otherwise the default image is used.

βš™οΈ Configuration

Edit the constants at the top of the file to suit your environment:

const APP_TITLE = 'Menu';
const UPLOAD_DIR = 'uploads';
const MAX_UPLOAD_BYTES = 3_000_000; // ~3 MB
const ADMIN_USER = 'admin'; // username is fixed
const DEFAULT_ITEM_IMG = 'images/default-item.webp'; // fallback image path

Notes

  • UPLOAD_DIR will be auto-created if missing.
  • DEFAULT_ITEM_IMG should exist and be web-accessible. Create images/ and put a small WebP there (see β€œAssets”).
  • ADMIN_USER is fixed to simplify the UI. Change only the password in Settings.

πŸ—ƒοΈ Data model

  • settings(key TEXT PRIMARY KEY, val TEXT NOT NULL)
    • stores admin_hash (password hash) and future settings
  • categories(id INTEGER PK, name TEXT UNIQUE NOT NULL)
  • items(id INTEGER PK, name TEXT, price REAL, veg INTEGER, category_id INTEGER NULL, descr TEXT, img TEXT NULL)
    • veg = 1 for Veg, 0 for Non-Veg
    • category_id is nullable; deletes set to NULL (no cascade)

The database file is menu.db in the app directory. WAL mode is enabled for safe concurrent reads/writes.


πŸ‘©β€πŸ³ Admin UI

Open: /?admin=1&page=dashboard

  • Dashboard: counts, upload dir size
  • Items: search, filter by category, pagination, edit/delete
  • Add Item: name, price, Veg/Non-Veg, category, description, image upload
  • Categories: create/rename/delete
  • Settings:
    • Change password (requires current)
    • Export JSON: downloads categories & items
    • Import JSON: upload or paste JSON to append new categories/items

Image uploads

  • Types: image/jpeg, image/png, image/gif, image/webp
  • Max size: MAX_UPLOAD_BYTES (~3 MB by default)
  • Stored under UPLOAD_DIR/ with timestamped, random filenames
  • Replacing an image for an item deletes the previous file (only if it resides under UPLOAD_DIR/)

🍽️ Public UI

Open: /?

Filters (query params)

  • q β€” search in item name/description
  • cat β€” filter by category id (int)
  • veg β€” "" (all), "1" (Veg), "0" (Non-Veg)

Examples:

  • /?q=pasta
  • /?cat=2&veg=1

Items are grouped by category; uncategorized items show under Uncategorized.


πŸ” Security

  • Sessions: secure cookie flags (httponly, samesite=Lax, secure if HTTPS)
  • CSRF: hidden token on all POST forms
  • Auth: password stored with password_hash() (bcrypt/argon per PHP default)
  • Rate limit: 5 failed logins β†’ block for 60 seconds
  • Headers:
    • X-Frame-Options: SAMEORIGIN
    • X-Content-Type-Options: nosniff
    • Referrer-Policy: strict-origin-when-cross-origin
  • Output: all dynamic content is escaped via htmlspecialchars

Production checklist

  • Serve over HTTPS
  • Change the default password immediately
  • Ensure uploads/ is not executable and only serves static files (typical shared hosting is fine)
  • Keep DEFAULT_ITEM_IMG and uploads/ paths outside any PHP-executing locations if you do custom server configs
  • Regularly back up menu.db and uploads/

πŸ” Import / Export

  • Export: Admin β†’ Settings β†’ Download JSON Export (downloads categories & items, excluding images)
  • Import: Admin β†’ Settings β†’ Import (file upload or paste JSON)
    • Categories are inserted if missing (INSERT OR IGNORE)
    • Items are appended; images are not imported (set img: null)
    • For items, category_id is used if provided; otherwise, if a "category" name is present and exists, it’s used.

A minimal items JSON example:

{
  "categories": [
    { "id": 1, "name": "Pasta" },
    { "id": 2, "name": "Desserts" }
  ],
  "items": [
    { "name": "Aglio e Olio", "price": 8.5, "veg": 1, "category": "Pasta", "descr": "Garlic & olive oil." },
    { "name": "Tiramisu", "price": 6.0, "veg": 1, "category": "Desserts", "descr": "Classic Italian dessert." }
  ]
}

🧱 Assets

  • Default image path: images/default-item.webp
    Create the folder and add any small placeholder (e.g., 800Γ—600 WebP).
    If an item has no valid upload, this fallback is used.

  • Uploaded images live under uploads/ and are referenced directly in <img src="...">.


πŸ§ͺ Troubleshooting

  • Blank page / DB error: ensure PHP has PDO SQLite and SQLite3 enabled.
  • Uploads fail:
    • Increase upload_max_filesize and post_max_size in php.ini above MAX_UPLOAD_BYTES.
    • Ensure the web server user can write to the app directory & uploads/.
  • Default image missing: create images/default-item.webp and make it readable by the server.
  • Stuck on default login warning: change password in Admin β†’ Settings.

🧰 Optional: Docker (example)

# Dockerfile
FROM php:8.3-apache
RUN docker-php-ext-install pdo pdo_sqlite
RUN apt-get update && apt-get install -y libmagic1 && rm -rf /var/lib/apt/lists/*
# fileinfo is built-in on official PHP images; ensure it's enabled if needed.

COPY . /var/www/html/
RUN mkdir -p /var/www/html/uploads /var/www/html/images  && chown -R www-data:www-data /var/www/html

# Put a default placeholder to avoid 404s
# COPY images/default-item.webp /var/www/html/images/default-item.webp

Run:

docker build -t menu-app .
docker run -p 8080:80 -v $PWD/uploads:/var/www/html/uploads -v $PWD/menu.db:/var/www/html/menu.db menu-app

Open: http://localhost:8080/


πŸ”„ Backups & Restore

  • Backup: copy menu.db, uploads/ and (optionally) index.php & images/.
  • Restore: place files back; ensure permissions; done.

πŸ“„ License

Use freely for demos, test menus, or as a starting point. If you fork for production, audit and adapt to your needs.


🧭 At-a-glance URLs

  • Public menu: /?
  • Admin login: /?admin=1&page=dashboard
  • Admin sections:
    • /?admin=1&page=items
    • /?admin=1&page=item_add
    • /?admin=1&page=categories
    • /?admin=1&page=settings

βœ… Security & Design Notes (quick audit)

  • Good: CSRF tokens; escaped output; password hashing; basic rate-limit; session cookie flags; MIME validation; size checks; deletion only within uploads/.
  • Be aware: No user roles; single admin; images are served as-is (don’t place uploads/ under an exec-enabled path in custom setups); JSON import appends only (no dedupe beyond categories).
  • Tweakables: MAX_UPLOAD_BYTES, default image path, Bootstrap CDN, and page titles.

Happy shipping! 🍝

zaxwebs/sfa-restaurant-menu | GitHunt