return the (changed) message array, or throw new Exception('reason') to reject it // render_text($html, $msg) -> $html is the escaped message text; return it changed (still safe HTML) // msg_top($html, $msg) -> $html starts as ''; return it with HTML added above a message (e.g. a quote) // msg_bottom($html, $msg) -> same, for items shown below a message (e.g. buttons) // form_extra() -> echo HTML inside the send form // below_chat() -> echo HTML after the send form // JavaScript features can listen for the 'chat:updated' event on #chat (fired when the log changes). $chatHooks = []; function chat_on($hook, $fn) { global $chatHooks; $chatHooks[$hook][] = $fn; } function chat_run($hook, $value = null, ...$args) { global $chatHooks; foreach ($chatHooks[$hook] ?? [] as $fn) { $r = $fn($value, ...$args); if ($r !== null) $value = $r; } return $value; } function chat_who() { return substr(sha1($_SERVER['REMOTE_ADDR'] ?? ''), 0, 8); } // short per-device id function chat_files($ext) { return array_map('basename', glob(__DIR__ . '/features/*.' . $ext) ?: []); } function chat_load_features() { foreach (chat_files('php') as $f) include __DIR__ . '/features/' . $f; } // Returns the stored messages, [] if none yet, or null if the file exists but is not valid JSON. function chat_read() { if (!is_file(CHAT_FILE)) return []; $raw = trim((string)@file_get_contents(CHAT_FILE)); if ($raw === '') return []; $msgs = json_decode($raw, true); return is_array($msgs) ? $msgs : null; } // Lock, re-read, apply one change, write atomically. Returns an error message or null. function chat_update($change) { $lock = @fopen(__DIR__ . '/data/.lock', 'c'); if (!$lock || !flock($lock, LOCK_EX)) return 'Chat storage is not writable. Check the chat/data permissions.'; $msgs = chat_read(); if ($msgs === null) return 'chat/data/messages.json is malformed. Nothing was saved.'; $err = $change($msgs); if ($err) return $err; $json = json_encode(array_values(array_slice($msgs, -CHAT_KEEP)), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $tmp = @tempnam(__DIR__ . '/data', 'msg'); if ($json === false || !$tmp || file_put_contents($tmp, $json) !== strlen($json) || !@rename($tmp, CHAT_FILE)) { if ($tmp) @unlink($tmp); return 'Could not save. Check the chat/data permissions.'; } return null; } // Handles chat_action=send (anyone) and chat_action=delete (admin only). Returns [type, message] or null. function chat_handle() { global $isAdmin, $csrf; $act = $_POST['chat_action'] ?? null; if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !is_string($act)) return null; if ($act === 'send') { $name = (string)preg_replace('/\s+/u', ' ', trim((string)($_POST['chat_name'] ?? ''))); $text = trim((string)($_POST['chat_text'] ?? '')); if (!preg_match('/^[^\x00-\x1F\x7F]{1,20}$/u', $name)) return ['danger', 'Pick a name (1 to 20 characters).']; if (!preg_match('/^[^\x00-\x1F\x7F]{1,500}$/u', $text)) return ['danger', 'A message must be 1 to 500 characters on one line.']; $who = chat_who(); $err = chat_update(function (&$msgs) use ($name, $text, $who) { $recent = 0; // at most 5 messages per 10 seconds from one address foreach ($msgs as $m) { if (($m['who'] ?? '') === $who && ($m['t'] ?? 0) > time() - 10) $recent++; } if ($recent >= 5) return 'Slow down a little.'; $last = end($msgs); $msg = ['id' => ($last['id'] ?? 0) + 1, 't' => time(), 'name' => $name, 'text' => $text, 'who' => $who]; try { $msg = chat_run('before_send', $msg); } catch (Exception $e) { return $e->getMessage(); } if (!is_array($msg) || !is_string($msg['text'] ?? null) || !is_string($msg['name'] ?? null)) return 'Message rejected.'; $msgs[] = $msg; return null; }); return $err ? ['danger', $err] : null; } if ($act === 'delete') { if (!$isAdmin || !hash_equals($csrf, (string)($_POST['csrf'] ?? ''))) return ['danger', 'Not allowed.']; $id = (int)($_POST['chat_id'] ?? 0); $err = chat_update(function (&$msgs) use ($id) { $msgs = array_values(array_filter($msgs, function ($m) use ($id) { return ($m['id'] ?? 0) !== $id; })); return null; }); return $err ? ['danger', $err] : null; } return null; } chat_load_features(); $chatNotice = chat_handle(); $chatMsgs = chat_read(); if ($chatMsgs === null) { $chatNotice = ['danger', 'chat/data/messages.json is malformed. Fix or remove it.']; $chatMsgs = []; } $chatName = is_string($_POST['chat_name'] ?? null) ? $_POST['chat_name'] : ''; $chatText = ($chatNotice && is_string($_POST['chat_text'] ?? null)) ? $_POST['chat_text'] : ''; ?>

Chat

No messages yet. Say hello!

: