accept the sid from the query string. ini_set('session.use_only_cookies', '0'); ini_set('session.use_trans_sid', '0'); session_name('WAPSID'); if (!empty($_REQUEST['WAPSID']) && preg_match('/^[A-Za-z0-9,\-]{8,64}$/', $_REQUEST['WAPSID'])) { session_id($_REQUEST['WAPSID']); } session_start(); } function csrf_token(): string { if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = bin2hex(random_bytes(16)); return $_SESSION['csrf']; } function csrf_ok(): bool { $t = $_POST['csrf'] ?? $_GET['csrf'] ?? ''; return $t !== '' && !empty($_SESSION['csrf']) && hash_equals($_SESSION['csrf'], $t); } function require_csrf(): void { if (!csrf_ok()) bail('Error', 'Session expired or bad token. Please try again.'); } function current_user(): ?array { static $cache = null; static $done = false; if ($done) return $cache; $done = true; if (empty($_SESSION['uid'])) return $cache = null; $s = db()->prepare("SELECT * FROM users WHERE id=?"); $s->execute([$_SESSION['uid']]); $u = $s->fetch(); if (!$u || $u['is_banned']) { $_SESSION['uid'] = null; return $cache = null; } // throttle last_seen writes to once a minute if (time() - (int)$u['last_seen'] > 60) { db()->prepare("UPDATE users SET last_seen=? WHERE id=?")->execute([time(), $u['id']]); } return $cache = $u; } function require_login(): array { $u = current_user(); if (!$u) { page_start('Login needed', ['back' => '/index.php']); p_para('You must log in to use this feature.'); p_links([['/login.php', 'Login'], ['/signup.php', 'Sign up'], ['/index.php', 'Home']]); page_end(); exit; } return $u; } function require_admin(): array { $u = require_login(); if (!$u['is_admin']) bail('Denied', 'Admin only.'); return $u; } function user_login(string $name, string $pass): ?array { $s = db()->prepare("SELECT * FROM users WHERE username=?"); $s->execute([$name]); $u = $s->fetch(); if (!$u || !password_verify($pass, $u['pass_hash'])) return null; if ($u['is_banned']) return null; $_SESSION['uid'] = (int)$u['id']; return $u; } function user_create(string $name, string $pass, int $admin = 0): int { $s = db()->prepare("INSERT INTO users (username,pass_hash,is_admin,created_at) VALUES (?,?,?,?)"); $s->execute([$name, password_hash($pass, PASSWORD_DEFAULT), $admin, time()]); return (int)db()->lastInsertId(); } function username_taken(string $n): bool { $s = db()->prepare("SELECT 1 FROM users WHERE username=?"); $s->execute([$n]); return (bool)$s->fetchColumn(); } function valid_username(string $n): bool { return (bool)preg_match('/^[A-Za-z0-9_]{3,16}$/', $n); } function user_by_id(int $id): ?array { $s = db()->prepare("SELECT * FROM users WHERE id=?"); $s->execute([$id]); return $s->fetch() ?: null; } function user_by_name(string $n): ?array { $s = db()->prepare("SELECT * FROM users WHERE username=?"); $s->execute([$n]); return $s->fetch() ?: null; } function unread_count(int $uid): int { $s = db()->prepare("SELECT COUNT(*) FROM messages WHERE to_id=? AND is_read=0"); $s->execute([$uid]); return (int)$s->fetchColumn(); } function ago(int $ts): string { $d = max(0, time() - $ts); if ($d < 60) return $d . 's'; if ($d < 3600) return floor($d / 60) . 'm'; if ($d < 86400) return floor($d / 3600) . 'h'; if ($d < 2592000) return floor($d / 86400) . 'd'; return date('d/m/y', $ts); }