| Server IP : 10.200.247.200 / Your IP : 216.73.217.19 Web Server : Apache System : Linux synergy-usa-sites 6.8.0-138-generic #138-Ubuntu SMP PREEMPT_DYNAMIC Fri Jul 31 22:41:49 UTC 2026 x86_64 User : jeremy ( 1001) PHP Version : 8.4.25 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/development/callings.lvsaints.com/ |
Upload File : |
<?php
// Admin console — tabs: Board (publish/versions), Users, Wards (super admin only).
// Every action is scoped by role:
// super_admin -> all wards, all users, can create wards
// admin -> own ward's board + own ward's users (admins/viewers)
// viewer -> no admin access at all
require __DIR__ . '/lib.php';
require __DIR__ . '/releases.php';
$user = require_editor();
$tab = $_POST['tab'] ?? $_GET['tab'] ?? 'board';
if ($tab === 'wards' && !is_super($user)) {
$tab = 'board';
}
$msg = '';
$err = '';
// Can the actor create/modify this target user?
function can_manage_user(array $actor, array $target): bool
{
if (is_super($actor)) {
return true;
}
// Ward admins manage only non-super users inside their own ward.
return $target['role'] !== 'super_admin'
&& (int) $target['ward_id'] === (int) $actor['ward_id'];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_check();
$action = $_POST['action'] ?? '';
$pdo = db();
// ---- Board: upload & extract a new version ---------------------------
if ($action === 'upload') {
$note = trim($_POST['note'] ?? '');
$makeLive = !empty($_POST['make_live']);
$up = $_FILES['zip'] ?? null;
if (!$up || ($up['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
$err = 'Choose a .zip file to upload.';
} elseif (($up['error'] ?? 0) !== UPLOAD_ERR_OK || !is_uploaded_file($up['tmp_name'])) {
$err = ($up['error'] === UPLOAD_ERR_INI_SIZE || $up['error'] === UPLOAD_ERR_FORM_SIZE)
? 'That file is larger than the server upload limit.'
: 'Upload failed — please try again.';
} else {
$name = next_version_name();
try {
extract_zip($up['tmp_name'], $name);
write_meta($name, [
'note' => $note,
'uploaded_by' => $user['email'],
'uploaded_at' => date('Y-m-d H:i:s'),
'orig' => $up['name'],
]);
if ($makeLive) {
set_active($name);
}
$msg = "Uploaded version “$name”" . ($makeLive ? ' and made it live.' : ' (not yet live).');
} catch (Throwable $ex) {
$err = $ex->getMessage();
}
}
$tab = 'board';
}
// ---- Board: make a version live (re-point the symlink) ---------------
if ($action === 'set_live') {
$name = basename((string) ($_POST['name'] ?? ''));
try {
set_active($name);
$msg = "Version “$name” is now live.";
} catch (Throwable $ex) {
$err = $ex->getMessage();
}
$tab = 'board';
}
// ---- Board: delete a (non-live) version ------------------------------
if ($action === 'delete_version') {
$name = basename((string) ($_POST['name'] ?? ''));
try {
delete_version($name);
$msg = "Deleted version “$name”.";
} catch (Throwable $ex) {
$err = $ex->getMessage();
}
$tab = 'board';
}
// ---- Users: add -------------------------------------------------------
if ($action === 'add_user') {
$email = trim($_POST['email'] ?? '');
$pass = $_POST['password'] ?? '';
$role = $_POST['role'] ?? 'viewer';
$wardId = ($_POST['ward_id'] ?? '') === '' ? null : (int) $_POST['ward_id'];
// Restrict what a ward admin may create.
if (!is_super($user)) {
$role = in_array($role, ['admin', 'viewer'], true) ? $role : 'viewer';
$wardId = (int) $user['ward_id'];
}
// Super admins have no ward; everyone else must have one.
if ($role === 'super_admin') {
$wardId = null;
}
if ($email === '' || strlen($pass) < 8) {
$err = 'Provide an email and a password of at least 8 characters.';
} elseif ($role !== 'super_admin' && !$wardId) {
$err = 'Choose a ward for this user.';
} else {
try {
$pdo->prepare(
'INSERT INTO users (email, password_hash, role, ward_id) VALUES (?, ?, ?, ?)'
)->execute([$email, password_hash($pass, PASSWORD_DEFAULT), $role, $wardId]);
$msg = "Added user $email.";
} catch (PDOException $ex) {
$err = ($ex->getCode() === '23000')
? "A user with the email $email already exists."
: 'Could not add user.';
}
}
$tab = 'users';
}
// ---- Users: reset password -------------------------------------------
if ($action === 'reset_password') {
$id = (int) ($_POST['id'] ?? 0);
$pass = $_POST['password'] ?? '';
$stmt = $pdo->prepare('SELECT id, role, ward_id FROM users WHERE id = ?');
$stmt->execute([$id]);
$target = $stmt->fetch();
if (!$target || !can_manage_user($user, $target)) {
$err = 'You cannot manage that user.';
} elseif (strlen($pass) < 8) {
$err = 'New password must be at least 8 characters.';
} else {
$pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?')
->execute([password_hash($pass, PASSWORD_DEFAULT), $id]);
$msg = 'Password reset.';
}
$tab = 'users';
}
// ---- Users: delete ----------------------------------------------------
if ($action === 'delete_user') {
$id = (int) ($_POST['id'] ?? 0);
$stmt = $pdo->prepare('SELECT id, role, ward_id FROM users WHERE id = ?');
$stmt->execute([$id]);
$target = $stmt->fetch();
if (!$target || !can_manage_user($user, $target)) {
$err = 'You cannot manage that user.';
} elseif ((int) $target['id'] === (int) $user['id']) {
$err = 'You cannot delete your own account.';
} elseif ($target['role'] === 'super_admin'
&& (int) $pdo->query("SELECT COUNT(*) c FROM users WHERE role='super_admin'")->fetch()['c'] <= 1) {
$err = 'Cannot delete the last super admin.';
} else {
$pdo->prepare('DELETE FROM users WHERE id = ?')->execute([$id]);
$msg = 'User deleted.';
}
$tab = 'users';
}
// ---- Wards: add (super admin only) -----------------------------------
if ($action === 'add_ward' && is_super($user)) {
$name = trim($_POST['name'] ?? '');
$slug = trim($_POST['slug'] ?? '');
$slug = $slug !== '' ? slugify($slug) : slugify($name);
if ($name === '') {
$err = 'Enter a ward name.';
} else {
try {
$pdo->prepare('INSERT INTO wards (name, slug) VALUES (?, ?)')->execute([$name, $slug]);
$msg = "Added ward “$name”.";
} catch (PDOException $ex) {
$err = ($ex->getCode() === '23000')
? "A ward with the slug “$slug” already exists."
: 'Could not add ward.';
}
}
$tab = 'wards';
}
}
$wards = all_wards();
// Data for the active tab.
$versions = [];
$preview = $_SESSION['preview_version'] ?? '';
if ($tab === 'board') {
$versions = list_versions();
}
$userList = [];
if ($tab === 'users') {
if (is_super($user)) {
$userList = db()->query(
'SELECT u.id, u.email, u.role, u.ward_id, w.name AS ward_name
FROM users u LEFT JOIN wards w ON w.id = u.ward_id
ORDER BY (u.role="super_admin") DESC, w.name, u.email'
)->fetchAll();
} else {
$stmt = db()->prepare(
'SELECT u.id, u.email, u.role, u.ward_id, w.name AS ward_name
FROM users u LEFT JOIN wards w ON w.id = u.ward_id
WHERE u.ward_id = ? ORDER BY u.role, u.email'
);
$stmt->execute([(int) $user['ward_id']]);
$userList = $stmt->fetchAll();
}
}
function tab_url(string $tab): string
{
return 'admin.php?tab=' . urlencode($tab);
}
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Admin — Calling Board</title>
<style>
body{font-family:Arial,sans-serif;background:#f5f7fb;color:#101828;margin:0;padding:24px}
.wrap{max-width:960px;margin:0 auto}
h1{font-size:24px;margin:0 0 4px}
h2{font-size:17px;margin:0 0 4px}
.sub{color:#667085;font-size:13px;margin-bottom:20px}
.card{background:#fff;border:1px solid #d9e2ee;border-radius:14px;padding:18px;margin-bottom:20px;
box-shadow:0 8px 22px rgba(16,24,40,.06)}
label{display:block;font-size:13px;font-weight:700;margin:12px 0 4px;color:#344054}
textarea{width:100%;height:180px;padding:10px;border:1px solid #d9e2ee;border-radius:9px;font-family:monospace;font-size:12px}
input[type=text],input[type=email],input[type=password],input[type=file],select{
width:100%;padding:9px;border:1px solid #d9e2ee;border-radius:9px;font-size:14px}
.row{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:wrap}
.row > div{flex:1;min-width:160px}
button{padding:9px 14px;border:0;border-radius:9px;background:#2563d9;color:#fff;font-weight:800;font-size:13px;cursor:pointer}
button.secondary{background:#fff;color:#344054;border:1px solid #d9e2ee}
button.danger{background:#fff;color:#b42318;border:1px solid #f2c2bd}
table{width:100%;border-collapse:collapse;font-size:13px}
th,td{text-align:left;padding:9px 8px;border-bottom:1px solid #eef2f7;vertical-align:middle}
.live{background:#e9f7ed;color:#15803d;font-weight:800;border-radius:999px;padding:2px 9px;font-size:12px}
.pill{background:#eef2f7;color:#344054;border-radius:999px;padding:2px 9px;font-size:12px;font-weight:700}
.pill.super{background:#f3e8ff;color:#6d28d9}
.msg{background:#e9f7ed;color:#15803d;border:1px solid #bfe6cc;border-radius:9px;padding:10px;margin-bottom:16px;font-size:13px}
.err{background:#fdecea;color:#b42318;border:1px solid #f2c2bd;border-radius:9px;padding:10px;margin-bottom:16px;font-size:13px}
a{color:#2563d9}
.top{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:8px}
.tabs{display:flex;gap:6px;margin-bottom:20px}
.tabs a{padding:8px 14px;border-radius:10px;text-decoration:none;font-weight:800;font-size:13px;color:#344054;background:#fff;border:1px solid #d9e2ee}
.tabs a.active{background:#2563d9;color:#fff;border-color:#2563d9}
.switcher{display:flex;align-items:center;gap:8px;margin-bottom:16px;font-size:13px;font-weight:700;color:#344054}
.switcher select{width:auto;min-width:200px}
.muted{color:#98a2b3}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<div>
<h1>Calling Board — Admin</h1>
<div class="sub">
Signed in as <?= e($user['email']) ?>
<span class="pill <?= is_super($user) ? 'super' : '' ?>"><?= e(str_replace('_', ' ', $user['role'])) ?></span>
· <a href="logout.php">Sign out</a>
· <a href="index.php" target="_blank">View live board ↗</a>
</div>
</div>
</div>
<div class="tabs">
<a href="<?= tab_url('board') ?>" class="<?= $tab === 'board' ? 'active' : '' ?>">Board</a>
<a href="<?= tab_url('users') ?>" class="<?= $tab === 'users' ? 'active' : '' ?>">Users</a>
<?php if (is_super($user)): ?>
<a href="<?= tab_url('wards') ?>" class="<?= $tab === 'wards' ? 'active' : '' ?>">Wards</a>
<?php endif; ?>
</div>
<?php if ($msg): ?><div class="msg"><?= e($msg) ?></div><?php endif; ?>
<?php if ($err): ?><div class="err"><?= e($err) ?></div><?php endif; ?>
<?php /* =============================== BOARD =============================== */ ?>
<?php if ($tab === 'board'): ?>
<?php if ($preview): ?>
<div class="msg" style="display:flex;justify-content:space-between;align-items:center;gap:12px">
<span>You are previewing <strong><?= e($preview) ?></strong> (only you see this — it is not live).</span>
<a href="site.php?preview=" style="font-weight:700">Exit preview</a>
</div>
<?php endif; ?>
<div class="card">
<h2>Upload a new version</h2>
<div class="sub" style="margin:0">
Choose a <strong>.zip</strong> of the whole site (its <code>index.html</code> plus any
css / js / images). We unzip it into its own folder and, if you tick the box, point the
live site at it. Nothing older is overwritten — you can switch back anytime.
</div>
<form method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="upload">
<input type="hidden" name="tab" value="board">
<label>Site zip</label>
<input type="file" name="zip" accept=".zip,application/zip" required>
<label>Note (optional — what changed)</label>
<input type="text" name="note" placeholder="e.g. New Primary presidency + updated colors">
<div class="row">
<label style="margin:0;display:flex;align-items:center;gap:6px;font-weight:400">
<input type="checkbox" name="make_live" value="1" checked style="width:auto"> Make this version live now
</label>
</div>
<div class="row"><button type="submit">Upload version</button></div>
</form>
</div>
<div class="card">
<h2 style="margin-bottom:12px">Version history</h2>
<table>
<tr><th>Version</th><th>Uploaded</th><th>By</th><th>Note</th><th>Status</th><th></th></tr>
<?php foreach ($versions as $v): ?>
<tr>
<td><?= e($v['name']) ?><?= $v['has_index'] ? '' : ' <span class="muted" title="no index.html">⚠</span>' ?></td>
<td><?= e($v['uploaded_at']) ?></td>
<td><?= e($v['uploaded_by'] ?: '—') ?></td>
<td><?= e($v['note']) ?></td>
<td><?= $v['is_live'] ? '<span class="live">LIVE</span>' : '' ?></td>
<td style="white-space:nowrap">
<a href="site.php?preview=<?= urlencode($v['name']) ?>" target="_blank">Preview</a>
<?php if (!$v['is_live']): ?>
<form method="post" style="display:inline">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="set_live">
<input type="hidden" name="tab" value="board">
<input type="hidden" name="name" value="<?= e($v['name']) ?>">
<button class="secondary" type="submit">Make live</button>
</form>
<form method="post" style="display:inline" onsubmit="return confirm('Delete <?= e($v['name']) ?>? This cannot be undone.')">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="delete_version">
<input type="hidden" name="tab" value="board">
<input type="hidden" name="name" value="<?= e($v['name']) ?>">
<button class="danger" type="submit">Delete</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$versions): ?><tr><td colspan="6" class="muted">No versions yet — upload one above.</td></tr><?php endif; ?>
</table>
</div>
<?php /* =============================== USERS =============================== */ ?>
<?php elseif ($tab === 'users'): ?>
<div class="card">
<h2>Add a user</h2>
<div class="sub" style="margin:0">
<?= is_super($user)
? 'You can create users in any ward, including other super admins.'
: 'You can create admins and viewers for ' . e(ward_name((int) $user['ward_id'])) . '.' ?>
</div>
<form method="post">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="add_user">
<input type="hidden" name="tab" value="users">
<div class="row">
<div><label>Email</label><input type="email" name="email" required></div>
<div><label>Temporary password</label><input type="password" name="password" required></div>
</div>
<div class="row">
<div>
<label>Role</label>
<select name="role" id="roleSel" onchange="document.getElementById('wardWrap').style.display = this.value==='super_admin' ? 'none' : 'block'">
<option value="viewer">Viewer (view only)</option>
<option value="admin">Admin (edit this ward)</option>
<?php if (is_super($user)): ?><option value="super_admin">Super admin (all wards)</option><?php endif; ?>
</select>
</div>
<div id="wardWrap">
<label>Ward</label>
<?php if (is_super($user)): ?>
<select name="ward_id">
<?php foreach ($wards as $w): ?>
<option value="<?= (int) $w['id'] ?>"><?= e($w['name']) ?></option>
<?php endforeach; ?>
</select>
<?php else: ?>
<input type="text" value="<?= e(ward_name((int) $user['ward_id'])) ?>" disabled>
<input type="hidden" name="ward_id" value="<?= (int) $user['ward_id'] ?>">
<?php endif; ?>
</div>
</div>
<div class="row"><button type="submit">Add user</button></div>
</form>
</div>
<div class="card">
<h2 style="margin-bottom:12px">Users</h2>
<table>
<tr><th>Email</th><th>Role</th><th>Ward</th><th>Reset password</th><th></th></tr>
<?php foreach ($userList as $u): ?>
<tr>
<td><?= e($u['email']) ?><?= (int) $u['id'] === (int) $user['id'] ? ' <span class="muted">(you)</span>' : '' ?></td>
<td><span class="pill <?= $u['role'] === 'super_admin' ? 'super' : '' ?>"><?= e(str_replace('_', ' ', $u['role'])) ?></span></td>
<td><?= e($u['role'] === 'super_admin' ? 'All wards' : ($u['ward_name'] ?? '—')) ?></td>
<td>
<form method="post" style="display:flex;gap:6px">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="reset_password">
<input type="hidden" name="tab" value="users">
<input type="hidden" name="id" value="<?= (int) $u['id'] ?>">
<input type="password" name="password" placeholder="new password" style="max-width:150px">
<button class="secondary" type="submit">Set</button>
</form>
</td>
<td style="white-space:nowrap">
<?php if ((int) $u['id'] !== (int) $user['id']): ?>
<form method="post" style="display:inline" onsubmit="return confirm('Delete <?= e($u['email']) ?>?')">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="delete_user">
<input type="hidden" name="tab" value="users">
<input type="hidden" name="id" value="<?= (int) $u['id'] ?>">
<button class="danger" type="submit">Delete</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (!$userList): ?><tr><td colspan="5" class="muted">No users.</td></tr><?php endif; ?>
</table>
</div>
<?php /* =============================== WARDS =============================== */ ?>
<?php elseif ($tab === 'wards' && is_super($user)): ?>
<div class="card">
<h2>Add a ward</h2>
<form method="post">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<input type="hidden" name="action" value="add_ward">
<input type="hidden" name="tab" value="wards">
<div class="row">
<div><label>Ward name</label><input type="text" name="name" placeholder="Southgate Ward" required></div>
<div><label>URL slug (optional)</label><input type="text" name="slug" placeholder="southgate"></div>
</div>
<div class="row"><button type="submit">Add ward</button></div>
</form>
</div>
<div class="card">
<h2 style="margin-bottom:12px">Wards</h2>
<table>
<tr><th>Name</th><th>Slug</th></tr>
<?php foreach ($wards as $w): ?>
<tr><td><?= e($w['name']) ?></td><td class="muted"><?= e($w['slug']) ?></td></tr>
<?php endforeach; ?>
<?php if (!$wards): ?><tr><td colspan="2" class="muted">No wards yet.</td></tr><?php endif; ?>
</table>
</div>
<?php endif; ?>
</div>
</body>
</html>