Files
radio-free-elsewhere/admin/show_edit.php
T
Daniel MesserandClaude Opus 5.5 6e61fc6bda Add show management and going-live controls to admin
- admin/shows.php: list shows
- admin/show_edit.php: add, edit and delete shows, with validated
  background image uploads saved under generated names
- admin/index.php: queue the next show, switch the live show, or clear
  the queue
- includes/broadcasts.php: setBroadcastShow() and clearQueuedShow(),
  shared by the dashboard and cli/set_show.php
- includes/admin_layout.php: flash messages and Shows nav link

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 13:48:14 -05:00

163 lines
7.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require __DIR__ . '/../includes/auth.php';
require __DIR__ . '/../includes/admin_layout.php';
$admin = requireAdmin();
$db = getDB();
const BACKGROUND_DIR = __DIR__ . '/../images/backgrounds/';
const MAX_BACKGROUND_BYTES = 5 * 1024 * 1024;
// Uploads are identified by their content, not their filename, and saved under a generated name
const BACKGROUND_TYPES = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
function slugify(string $name): string {
$slug = strtolower(str_replace(["'", '’'], '', $name));
return trim(preg_replace('/[^a-z0-9]+/', '-', $slug), '-');
}
function deleteBackground(?string $filename): void {
if ($filename && is_file(BACKGROUND_DIR . basename($filename))) {
unlink(BACKGROUND_DIR . basename($filename));
}
}
$id = (int)($_GET['id'] ?? 0);
$show = ['id' => 0, 'name' => '', 'slug' => '', 'background_image' => null];
if ($id) {
$stmt = $db->prepare('SELECT id, name, slug, background_image FROM shows WHERE id = ?');
$stmt->execute([$id]);
$show = $stmt->fetch();
if (!$show) {
http_response_code(404);
exit('Show not found.');
}
}
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// PHP discards the whole form when a request exceeds post_max_size, so catch that before the CSRF check
if (empty($_POST) && (int)($_SERVER['CONTENT_LENGTH'] ?? 0) > 0) {
$errors[] = 'That upload is too large. Background images can be up to ' . (MAX_BACKGROUND_BYTES / 1024 / 1024) . ' MB.';
} else {
verifyCsrf();
if (($_POST['action'] ?? '') === 'delete' && $id) {
$db->prepare('DELETE FROM shows WHERE id = ?')->execute([$id]);
deleteBackground($show['background_image']);
flash("Deleted {$show['name']}.");
header('Location: shows.php');
exit;
}
$show['name'] = trim((string)($_POST['name'] ?? ''));
$show['slug'] = trim((string)($_POST['slug'] ?? '')) ?: slugify($show['name']);
if ($show['name'] === '' || mb_strlen($show['name']) > 255) {
$errors[] = 'Name is required (up to 255 characters).';
}
if (!preg_match('/^[a-z0-9]+(-[a-z0-9]+)*$/', $show['slug']) || strlen($show['slug']) > 64) {
$errors[] = 'Slug may only contain lowercase letters, numbers and single dashes (up to 64 characters).';
} else {
$stmt = $db->prepare('SELECT COUNT(*) FROM shows WHERE slug = ? AND id != ?');
$stmt->execute([$show['slug'], $id]);
if ($stmt->fetchColumn() > 0) $errors[] = "Another show already uses the slug '{$show['slug']}'.";
}
$upload = $_FILES['background'] ?? null;
$uploadExt = null;
if ($upload && $upload['error'] !== UPLOAD_ERR_NO_FILE) {
if (in_array($upload['error'], [UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE], true) || $upload['size'] > MAX_BACKGROUND_BYTES) {
$errors[] = 'Background images can be up to ' . (MAX_BACKGROUND_BYTES / 1024 / 1024) . ' MB.';
} elseif ($upload['error'] !== UPLOAD_ERR_OK) {
$errors[] = 'The background image failed to upload. Please try again.';
} else {
$mime = (new finfo(FILEINFO_MIME_TYPE))->file($upload['tmp_name']);
$uploadExt = BACKGROUND_TYPES[$mime] ?? null;
if ($uploadExt === null || getimagesize($upload['tmp_name']) === false) {
$errors[] = 'Background images must be JPEG, PNG or WebP.';
}
}
}
if (!$errors) {
$oldBackground = $show['background_image'];
if ($uploadExt !== null) {
$filename = $show['slug'] . '-' . bin2hex(random_bytes(4)) . '.' . $uploadExt;
if (move_uploaded_file($upload['tmp_name'], BACKGROUND_DIR . $filename)) {
$show['background_image'] = $filename;
} else {
$errors[] = 'The background image could not be saved. Check that the web server can write to images/backgrounds/.';
}
} elseif (!empty($_POST['remove_background'])) {
$show['background_image'] = null;
}
}
if (!$errors) {
if ($id) {
$db->prepare('UPDATE shows SET name = ?, slug = ?, background_image = ? WHERE id = ?')
->execute([$show['name'], $show['slug'], $show['background_image'], $id]);
} else {
$db->prepare('INSERT INTO shows (name, slug, background_image) VALUES (?, ?, ?)')
->execute([$show['name'], $show['slug'], $show['background_image']]);
}
if ($oldBackground !== $show['background_image']) deleteBackground($oldBackground);
flash("Saved {$show['name']}.");
header('Location: shows.php');
exit;
}
}
}
adminHeader($id ? 'Edit Show' : 'Add a Show', $admin);
?>
<?php foreach ($errors as $error): ?>
<p class="admin-message error"><?= e($error) ?></p>
<?php endforeach; ?>
<form method="post" enctype="multipart/form-data" action="show_edit.php<?= $id ? '?id=' . $id : '' ?>">
<?= csrfField() ?>
<input type="hidden" name="MAX_FILE_SIZE" value="<?= MAX_BACKGROUND_BYTES ?>" />
<div class="row gtr-uniform gtr-50">
<div class="col-12">
<label for="name">Name</label>
<input type="text" name="name" id="name" value="<?= e($show['name']) ?>" maxlength="255" required />
</div>
<div class="col-12">
<label for="slug">Slug</label>
<input type="text" name="slug" id="slug" value="<?= e($show['slug']) ?>" maxlength="64" pattern="[a-z0-9]+(-[a-z0-9]+)*" placeholder="Leave blank to generate from the name" />
<p><small>Short name used by <code>cli/set_show.php</code>. Lowercase letters, numbers and dashes.</small></p>
</div>
<div class="col-12">
<label for="background">Background image</label>
<?php if ($show['background_image']): ?>
<p><img src="../images/backgrounds/<?= e(rawurlencode($show['background_image'])) ?>" alt="Current background" style="max-width:320px;border-radius:4px;display:block;"></p>
<?php endif; ?>
<input type="file" name="background" id="background" accept="image/jpeg,image/png,image/webp" />
<p><small>JPEG, PNG or WebP, up to <?= MAX_BACKGROUND_BYTES / 1024 / 1024 ?> MB. Shown behind the player on the live page during this show.</small></p>
<?php if ($show['background_image']): ?>
<input type="checkbox" name="remove_background" id="remove_background" value="1" />
<label for="remove_background">Remove background (use the default)</label>
<?php endif; ?>
</div>
<div class="col-12">
<ul class="actions">
<li><input type="submit" class="primary" value="Save" /></li>
<li><a href="shows.php" class="button">Cancel</a></li>
</ul>
</div>
</div>
</form>
<?php if ($id): ?>
<hr />
<form method="post" action="show_edit.php?id=<?= $id ?>" onsubmit="return confirm('Delete <?= e(addslashes($show['name'])) ?>? This cannot be undone.');">
<?= csrfField() ?>
<input type="hidden" name="action" value="delete" />
<input type="submit" class="small" value="Delete this show" />
</form>
<?php endif; ?>
<?php
adminFooter();