LAN_Portal/announcement/announcement.php
2026-10-16 16:22:59 -04:00

88 lines
4.5 KiB
PHP
Executable file

<?php
if (!defined('PORTAL')) { http_response_code(403); exit; }
// Announcement bar shown above the portal title. The admin edits it in place.
// Stored in announcement/data/announcement.json as {"text": "...", "level": "info"}.
const ANN_FILE = __DIR__ . '/data/announcement.json';
const ANN_LEVELS = ['info', 'warning', 'danger']; // Bootstrap alert colours
// Returns the saved announcement, an empty one if none yet, or null if the file is not valid JSON.
function ann_read() {
$empty = ['text' => '', 'level' => 'info'];
if (!is_file(ANN_FILE)) return $empty;
$raw = trim((string)@file_get_contents(ANN_FILE));
if ($raw === '') return $empty;
$d = json_decode($raw, true);
return is_array($d) ? $d + $empty : null;
}
// Writes the announcement atomically. Returns an error message or null.
function ann_save($text, $level) {
$lock = @fopen(__DIR__ . '/data/.lock', 'c');
if (!$lock || !flock($lock, LOCK_EX)) return 'Cannot write to announcement/data. Check its permissions.';
if (ann_read() === null) return 'announcement.json is malformed. Nothing was saved; fix or delete the file.';
$json = json_encode(['text' => $text, 'level' => $level], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$tmp = @tempnam(__DIR__ . '/data', 'ann');
if ($json === false || !$tmp || file_put_contents($tmp, $json) !== strlen($json) || !@rename($tmp, ANN_FILE)) {
if ($tmp) @unlink($tmp);
return 'Could not save. Check the announcement/data permissions.';
}
return null;
}
// Save or clear (admin only, with CSRF token).
$annNotice = null;
$annSaved = false;
$annAct = $_POST['announcement_action'] ?? null;
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($annAct, ['save', 'clear'], true)) {
if (!$isAdmin || !hash_equals($csrf, (string)($_POST['csrf'] ?? ''))) {
$annNotice = ['danger', 'Not allowed.'];
} else {
$annText = $annAct === 'clear' ? '' : trim((string)($_POST['announcement_text'] ?? ''));
$annLevel = $annAct === 'clear' ? 'info' : ($_POST['announcement_level'] ?? '');
if (!in_array($annLevel, ANN_LEVELS, true)) {
$annNotice = ['danger', 'Invalid colour choice.'];
} elseif (!preg_match('/^[^\x00-\x1F\x7F]{0,300}$/u', $annText)) {
$annNotice = ['danger', 'The announcement must be one line, up to 300 characters.'];
} else {
$annErr = ann_save($annText, $annLevel);
$annNotice = $annErr ? ['danger', $annErr] : ['success', 'Announcement saved.'];
$annSaved = !$annErr;
}
}
}
$ann = ann_read();
if ($ann === null) {
$ann = ['text' => '', 'level' => 'info'];
if ($isAdmin && !$annNotice) $annNotice = ['danger', 'announcement.json is malformed. Fix or delete it.'];
}
$annLevelOk = in_array($ann['level'], ANN_LEVELS, true) ? $ann['level'] : 'info';
$annEditing = $isAdmin && $edit === 'announcement' && !$annSaved;
?>
<?php if ($annNotice): ?><div class="alert alert-<?= e($annNotice[0]) ?>"><?= e($annNotice[1]) ?></div><?php endif; ?>
<?php if ($annEditing): ?>
<form method="post" class="card card-body mb-3">
<input type="hidden" name="csrf" value="<?= e($csrf) ?>">
<label class="form-label">Announcement (clear it to hide the bar)</label>
<input name="announcement_text" class="form-control mb-2" maxlength="300" value="<?= e($ann['text']) ?>">
<div class="d-flex flex-wrap gap-2 align-items-center">
<select name="announcement_level" class="form-select w-auto" aria-label="Colour">
<option value="info"<?= $annLevelOk === 'info' ? ' selected' : '' ?>>Info (blue)</option>
<option value="warning"<?= $annLevelOk === 'warning' ? ' selected' : '' ?>>Warning (yellow)</option>
<option value="danger"<?= $annLevelOk === 'danger' ? ' selected' : '' ?>>Urgent (red)</option>
</select>
<button class="btn btn-accent" name="announcement_action" value="save">Save</button>
<a class="btn btn-outline-secondary" href="?">Cancel</a>
<button class="btn btn-outline-danger ms-auto" name="announcement_action" value="clear" formnovalidate>Clear</button>
</div>
</form>
<?php elseif ($ann['text'] !== ''): ?>
<div class="alert alert-<?= e($annLevelOk) ?> d-flex justify-content-between gap-2" role="alert">
<span class="text-break"><?= e($ann['text']) ?></span>
<?php if ($isAdmin): ?><a class="edit" href="?edit=announcement" title="Edit announcement" aria-label="Edit announcement">✎</a><?php endif; ?>
</div>
<?php elseif ($isAdmin): ?>
<p class="small"><a href="?edit=announcement">+ Add announcement</a></p>
<?php endif; ?>