88 lines
4.5 KiB
PHP
Executable file
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; ?>
|