Refactor admin pages to use shared head and navigation components

- Created `admin-head.php` for consistent <head> markup across admin pages.
- Created `admin-nav.php` for shared navigation bar, reducing duplication.
- Updated all admin pages (account, hours, index, login, menu, users) to utilize the new components.
- Added security headers to `auth.php` and `index.php` for improved security.
- Introduced `security-headers.php` to manage security headers across the application.
- Moved inline styles from admin pages to SCSS files to comply with Content Security Policy (CSP).
- Normalized file permissions during build process to prevent permission issues on deployment.
This commit is contained in:
David Dyer committed 2026-10-25 16:06:15 -04:00
1 parent 3286bce6b1
commit c7b836bcf2
21 files changed
+447 -231

No files matched your search

+4 -31
View File
@@ -3,6 +3,8 @@ declare(strict_types=1);
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
require_once __DIR__ . '/../inc/admin-nav.php';
require_once __DIR__ . '/../inc/users.php';
// Any signed-in user can change their own username and/or password here,
@@ -80,46 +82,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$justSaved = !$_POST && isset($_GET['saved']);
$isAdmin = ($user['role'] ?? '') === 'admin';
// After a failed save, keep the username they typed in the field.
$usernameValue = $_POST ? trim(account_post_str('username')) : $user['username'];
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Account | The Old Fitz Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Karla:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../styles.css<?= asset_version('styles.css') ?>">
</head>
<?php render_admin_head('My Account'); ?>
<body>
<nav class="admin-nav">
<div class="container admin-nav-inner">
<a href="index.php" class="admin-brand">
<img src="../assets/knot.png" alt="" class="knot">
<span>The Old Fitz Admin</span>
</a>
<div class="admin-nav-links">
<a href="index.php">Announcements</a>
<a href="menu.php">Menu</a>
<?php if ($isAdmin): ?>
<a href="users.php">Users</a>
<?php endif; ?>
<a href="hours.php">Hours</a>
</div>
<div class="admin-user">
<a href="../" target="_blank" rel="noopener">View Public Site ↗</a>
<a href="account.php" class="admin-account-link active" title="My Account"><?= htmlspecialchars($user['username']) ?></a>
<a href="logout.php">Sign Out</a>
</div>
</div>
</nav>
<?php render_admin_nav($user, 'account'); ?>
<main class="dashboard-layout">
<div class="container">
+6 -32
View File
@@ -3,6 +3,8 @@ declare(strict_types=1);
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
require_once __DIR__ . '/../inc/admin-nav.php';
require_once __DIR__ . '/../inc/hours.php';
require_auth();
@@ -66,39 +68,11 @@ $justSaved = !$_POST && isset($_GET['saved']);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hours | The Old Fitz Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Karla:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../styles.css<?= asset_version('styles.css') ?>">
</head>
<?php render_admin_head('Hours'); ?>
<body>
<nav class="admin-nav">
<div class="container admin-nav-inner">
<a href="index.php" class="admin-brand">
<img src="../assets/knot.png" alt="" class="knot">
<span>The Old Fitz Admin</span>
</a>
<div class="admin-nav-links">
<a href="index.php">Announcements</a>
<a href="menu.php">Menu</a>
<?php if (($user['role'] ?? '') === 'admin'): ?>
<a href="users.php">Users</a>
<?php endif; ?>
<a href="hours.php" class="active">Hours</a>
</div>
<div class="admin-user">
<a href="../" target="_blank" rel="noopener">View Public Site ↗</a>
<a href="account.php" class="admin-account-link" title="My Account"><?= htmlspecialchars($user['username']) ?></a>
<a href="logout.php">Sign Out</a>
</div>
</div>
</nav>
<?php render_admin_nav($user, 'hours'); ?>
<main class="dashboard-layout">
<div class="container">
@@ -157,7 +131,7 @@ $justSaved = !$_POST && isset($_GET['saved']);
</div>
</div>
<p class="muted field-hint" style="margin-top: 1.25rem;">Leave "Kitchen Closes" blank on a day if the kitchen doesn't have a separate cutoff from the bar. Special closures (holidays, weather) are handled on the <a href="index.php">Announcements</a> page instead of here.</p>
<p class="muted field-hint hours-note">Leave "Kitchen Closes" blank on a day if the kitchen doesn't have a separate cutoff from the bar. Special closures (holidays, weather) are handled on the <a href="index.php">Announcements</a> page instead of here.</p>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save Hours</button>
@@ -171,7 +145,7 @@ $justSaved = !$_POST && isset($_GET['saved']);
<span>👀 Public Site Preview</span>
</h2>
</div>
<p class="muted" style="margin-bottom: 1.25rem;">This is how the schedule above will read on the site once saved.</p>
<p class="muted hours-preview-intro">This is how the schedule above will read on the site once saved.</p>
<div class="hours-preview">
<div class="hours-preview-card">
+10 -36
View File
@@ -1,6 +1,8 @@
<?php
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
require_once __DIR__ . '/../inc/admin-nav.php';
require_once __DIR__ . '/../inc/announcements.php';
require_auth();
@@ -102,38 +104,10 @@ $announcements = get_all_announcements();
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pub Management Dashboard | The Old Fitz</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Karla:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../styles.css<?= asset_version('styles.css') ?>">
</head>
<?php render_admin_head('Announcements'); ?>
<body>
<nav class="admin-nav">
<div class="container admin-nav-inner">
<a href="index.php" class="admin-brand">
<img src="../assets/knot.png" alt="" class="knot">
<span>The Old Fitz Admin</span>
</a>
<div class="admin-nav-links">
<a href="index.php" class="active">Announcements</a>
<a href="menu.php">Menu</a>
<?php if (($user['role'] ?? '') === 'admin'): ?>
<a href="users.php">Users</a>
<?php endif; ?>
<a href="hours.php">Hours</a>
</div>
<div class="admin-user">
<a href="../" target="_blank" rel="noopener">View Public Site ↗</a>
<a href="account.php" class="admin-account-link" title="My Account"><?= htmlspecialchars($user['username']) ?></a>
<a href="logout.php">Sign Out</a>
</div>
</div>
</nav>
<?php render_admin_nav($user, 'announcements'); ?>
<main class="dashboard-layout">
<div class="container">
@@ -194,21 +168,21 @@ $announcements = get_all_announcements();
</div>
<div data-label="Headline">
<strong><?= htmlspecialchars($ann['headline']) ?></strong>
<div class="muted" style="font-size: 0.82rem; margin-top: 0.2rem; max-width: 32ch; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
<div class="muted ann-summary">
<?= htmlspecialchars($ann['message']) ?>
</div>
</div>
<div data-label="Type">
<span class="eyebrow"><?= htmlspecialchars($ann['type']) ?></span>
</div>
<div class="font-mono" data-label="Schedule" style="font-size: 0.8rem; color: var(--ink-muted);">
<div class="font-mono ann-schedule" data-label="Schedule">
<?php if (!empty($ann['start_date']) || !empty($ann['end_date'])): ?>
<?= htmlspecialchars($ann['start_date'] ?: 'Now') ?> → <?= htmlspecialchars($ann['end_date'] ?: 'Ongoing') ?>
<?php else: ?>
Always (while on)
<?php endif; ?>
</div>
<div data-label="Placements" style="font-size: 0.82rem; color: var(--ink-muted);">
<div class="ann-placements" data-label="Placements">
<?php
$locs = [];
if (!empty($ann['show_banner'])) $locs[] = 'Top Banner';
@@ -221,7 +195,7 @@ $announcements = get_all_announcements();
<div class="action-stack">
<a href="index.php?edit=<?= (int)$ann['id'] ?>" class="btn btn-outline btn-sm">Edit</a>
<form method="POST" action="index.php" style="display: inline;" class="js-confirm-submit" data-confirm="Are you sure you want to delete this announcement?">
<form method="POST" action="index.php" class="inline-form js-confirm-submit" data-confirm="Are you sure you want to delete this announcement?">
<input type="hidden" name="action" value="delete_announcement">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(get_csrf_token()) ?>">
<input type="hidden" name="announcement_id" value="<?= (int)$ann['id'] ?>">
@@ -286,14 +260,14 @@ $announcements = get_all_announcements();
<label for="start_date">Start Date (Optional Auto-Schedule)</label>
<input type="date" id="start_date" name="start_date" class="form-control"
value="<?= htmlspecialchars($editItem['start_date'] ?? '') ?>">
<span class="muted font-mono" style="font-size: 0.78rem;">Leave blank to show immediately while enabled.</span>
<span class="muted font-mono ann-field-hint">Leave blank to show immediately while enabled.</span>
</div>
<div class="form-group">
<label for="end_date">End Date (Optional Expiration)</label>
<input type="date" id="end_date" name="end_date" class="form-control"
value="<?= htmlspecialchars($editItem['end_date'] ?? '') ?>">
<span class="muted font-mono" style="font-size: 0.78rem;">Auto-hides after 11:59 PM on this date.</span>
<span class="muted font-mono ann-field-hint">Auto-hides after 11:59 PM on this date.</span>
</div>
<div class="form-group full-width">
+2 -9
View File
@@ -2,6 +2,7 @@
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/login-throttle.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
$error = '';
@@ -27,15 +28,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin Login | The Old Fitz Public House</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@600&family=Karla:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../styles.css<?= asset_version('styles.css') ?>">
</head>
<?php render_admin_head('Sign In'); ?>
<body class="admin-login-body">
<div class="login-card">
<div class="login-header">
+9 -3
View File
@@ -71,10 +71,16 @@ function closeCategoryModal() {
catModal.close();
}
// Escapes for both text and quoted-attribute positions. The old version
// (textContent → innerHTML) didn't escape quotes, so a name like
// The "Big" Burger broke out of an aria-label="..." attribute.
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text ?? '';
return div.innerHTML;
return String(text ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
async function postForm(formData) {
+4 -30
View File
@@ -3,6 +3,8 @@ declare(strict_types=1);
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
require_once __DIR__ . '/../inc/admin-nav.php';
require_once __DIR__ . '/../inc/menu.php';
require_auth();
@@ -180,39 +182,11 @@ $menuEnabled = is_menu_enabled();
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Menu Management | The Old Fitz Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Karla:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../styles.css<?= asset_version('styles.css') ?>">
</head>
<?php render_admin_head('Menu'); ?>
<body data-csrf-token="<?= htmlspecialchars(get_csrf_token(), ENT_QUOTES, 'UTF-8') ?>">
<nav class="admin-nav">
<div class="container admin-nav-inner">
<a href="index.php" class="admin-brand">
<img src="../assets/knot.png" alt="" class="knot">
<span>The Old Fitz Admin</span>
</a>
<div class="admin-nav-links">
<a href="index.php">Announcements</a>
<a href="menu.php" class="active">Menu</a>
<?php if (($user['role'] ?? '') === 'admin'): ?>
<a href="users.php">Users</a>
<?php endif; ?>
<a href="hours.php">Hours</a>
</div>
<div class="admin-user">
<a href="../" target="_blank" rel="noopener">View Public Site ↗</a>
<a href="account.php" class="admin-account-link" title="My Account"><?= htmlspecialchars($user['username']) ?></a>
<a href="logout.php">Sign Out</a>
</div>
</div>
</nav>
<?php render_admin_nav($user, 'menu'); ?>
<main class="dashboard-layout">
<div class="container">
+9 -3
View File
@@ -9,10 +9,16 @@ const userModal = document.getElementById('modal-user');
const passwordModal = document.getElementById('modal-password');
const editModal = document.getElementById('modal-edit');
// Escapes for both text and quoted-attribute positions. The old version
// (textContent → innerHTML) didn't escape quotes, so a name like
// The "Big" Burger broke out of an aria-label="..." attribute.
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text ?? '';
return div.innerHTML;
return String(text ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
async function postForm(formData) {
+4 -28
View File
@@ -3,6 +3,8 @@ declare(strict_types=1);
require_once __DIR__ . '/../inc/auth.php';
require_once __DIR__ . '/../inc/assets.php';
require_once __DIR__ . '/../inc/admin-head.php';
require_once __DIR__ . '/../inc/admin-nav.php';
require_once __DIR__ . '/../inc/users.php';
// User management is admin-only. Other pages don't check roles yet —
@@ -190,37 +192,11 @@ $users = get_all_users();
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>User Management | The Old Fitz Admin</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;1,9..144,400&family=Karla:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="../styles.css<?= asset_version('styles.css') ?>">
</head>
<?php render_admin_head('Users'); ?>
<body data-csrf-token="<?= htmlspecialchars(get_csrf_token(), ENT_QUOTES, 'UTF-8') ?>" data-current-user-id="<?= (int) $user['id'] ?>">
<nav class="admin-nav">
<div class="container admin-nav-inner">
<a href="index.php" class="admin-brand">
<img src="../assets/knot.png" alt="" class="knot">
<span>The Old Fitz Admin</span>
</a>
<div class="admin-nav-links">
<a href="index.php">Announcements</a>
<a href="menu.php">Menu</a>
<a href="users.php" class="active">Users</a>
<a href="hours.php">Hours</a>
</div>
<div class="admin-user">
<a href="../" target="_blank" rel="noopener">View Public Site ↗</a>
<a href="account.php" class="admin-account-link" title="My Account"><?= htmlspecialchars($user['username']) ?></a>
<a href="logout.php">Sign Out</a>
</div>
</div>
</nav>
<?php render_admin_nav($user, 'users'); ?>
<main class="dashboard-layout">
<div class="container">