Add contact form DB storage and hidden staff inbox

- contact.php now inserts submissions into MySQL via PDO prepared
  statements; raw values stored (htmlspecialchars moved to output only)
- www/includes/db.php: shared PDO helper with auto-migration that adds
  the is_read column to existing deployments without a full DB reset
- docker/mysql/init.sql: added is_read TINYINT column to contacts table
  for fresh deploys
- www/pages/admin-inbox.php: self-contained staff inbox at /staff-portal
  with session-based password login, per-message mark-as-read, and
  mark-all-read; unread count shown in browser tab title
- index.php: routes /staff-portal before public header/footer so the
  admin page is fully standalone
- docker-compose.yml: ADMIN_PASS env var wired to web container

Set ADMIN_PASS in .env (gitignored) before deploying.
If the DB volume already exists, the auto-migration in db.php will
add the is_read column automatically on first request.

https://claude.ai/code/session_015wpwmheufcxkBuXivrSHhd
This commit is contained in:
Claude
2026-03-01 03:05:18 +00:00
parent 6b44fe8b4c
commit 40e3f73aaf
6 changed files with 304 additions and 10 deletions

View File

@@ -1,21 +1,33 @@
<?php
require_once __DIR__ . '/../includes/db.php';
$success = false;
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim(htmlspecialchars($_POST['name'] ?? '', ENT_QUOTES));
$email = trim(htmlspecialchars($_POST['email'] ?? '', ENT_QUOTES));
$phone = trim(htmlspecialchars($_POST['phone'] ?? '', ENT_QUOTES));
$subject = trim(htmlspecialchars($_POST['subject'] ?? '', ENT_QUOTES));
$message = trim(htmlspecialchars($_POST['message'] ?? '', ENT_QUOTES));
// Store raw values; htmlspecialchars is applied only at output time
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$subject = trim($_POST['subject'] ?? '');
$message = trim($_POST['message'] ?? '');
if (!$name) $errors[] = 'Name is required.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'A valid email is required.';
if (!$message) $errors[] = 'Message is required.';
if (!$name) $errors[] = 'Name is required.';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'A valid email is required.';
if (!$message) $errors[] = 'Message is required.';
if (empty($errors)) {
// TODO: swap for DB insert + email once credentials are configured
$success = true;
try {
$db = get_db();
$stmt = $db->prepare(
"INSERT INTO contacts (name, email, phone, subject, message)
VALUES (?, ?, ?, ?, ?)"
);
$stmt->execute([$name, $email, $phone, $subject, $message]);
$success = true;
} catch (PDOException $e) {
$errors[] = 'Sorry, we could not save your message right now. Please try again.';
}
}
}
?>