$_) { if (str_starts_with($key, 'flash_')) unset($_SESSION[$key]); } } /** * Create a CSRF token. */ function csrf() { if (empty($_SESSION['csrf'])) $_SESSION['csrf'] = token(); return $_SESSION['csrf']; } /** * Verify a CSRF token. */ function csrf_verify($token) { return hash_equals($_SESSION['csrf'] ?? '', $token); } /** * Create a hidden input field for CSRF tokens. */ function csrf_field() { return ''; } /** * Kill the current request with a 418 error, if $_POST['csrf'] is invalid. */ function csrf_ensure() { if (!csrf_verify($_POST['csrf'] ?? '')) router_error(418); } /** * Set a cookie with secure and HTTP-only flags. */ function set_cookie($name, $value, $expires) { setcookie($name, $value, [ 'expires' => $expires, 'path' => '/', 'domain' => '', // Defaults to the current domain 'secure' => true, // Ensure the cookie is only sent over HTTPS 'httponly' => true, // Prevent access to cookie via JavaScript 'samesite' => 'Strict' // Enforce SameSite=Strict ]); } /** * Get the current user's object from SESSION if it exists. */ function user(): User|false { if (empty($_SESSION['user'])) return false; return unserialize($_SESSION['user']); } /** * Modify a field in the user session object. Returns true on success and false on failure. */ function modify_user_session(string $field, mixed $value): bool { $user = user(); if ($user === false || !property_exists($user, $field)) return false; $user->{$field} = $value; $_SESSION['user'] = serialize($user); return true; } /** * If the current user has a selected char and the data is in the session, retrieve the character object. If there * is no character data, populate it. */ function char(): Character|false { if (empty($_SESSION['user'])) return false; if (empty($GLOBALS['char'])) $GLOBALS['char'] = serialize(user()->current_char()); return unserialize($GLOBALS['char']); } /** * Update the user's selected character. Returns true on success, false on failure. Database * is updated if the character ID is different from the current session. */ function change_user_character(int $char_id): bool { // If the character does not exist, return false if (($char = Character::find($char_id)) === false) return false; $GLOBALS['char'] = serialize($char); // If the character ID is different, update the session and database if (user()->char_id !== $char_id) { modify_user_session('char_id', $char_id); db_query(db_auth(), "UPDATE users SET char_id = :c WHERE id = :u", [':c' => $char_id, ':u' => user()->id]); } return true; } /** * Get a percent between two ints, rounded to the nearest whole number or return 0. */ function percent(float $num, float $denom, int $precision = 4): float { if ($denom === 0) return 0; $p = ($num / $denom) * 100; return $p < 0 ? 0 : round($p, $precision); } /** * Access the account wallet. On first execution it will populate $GLOBALS['wallet'] with the wallet data. This way * the data is up to date with every request without having to query the database every use within, for example, a * template. Will return false if the user or wallet does not exist. */ function wallet(): Wallet|false { if (user() === false) return false; if (empty($GLOBALS['wallet'])) $w = user()->wallet(); if ($w === false) return false; return $GLOBALS['wallet'] = $w; } /** * Access the character location. On first execution it will populate $GLOBALS['location'] with the location data. This * way the data is up to date with every request without having to query the database every use within, for example, a * template. Will return false if the field does not exist, or the entire location array if no field is specified. */ function location($field = '') { if (empty($GLOBALS['location'])) { $GLOBALS['location'] = db_query( db_live(), "SELECT * FROM char_locations WHERE char_id = :c", [':c' => user()->char_id] )->fetchArray(SQLITE3_ASSOC); } if ($field === '') return $GLOBALS['location']; return $GLOBALS['location'][$field] ?? false; } /** * Format an array of strings to a ul element. */ function array_to_ul($array) { $html = ''; foreach ($array as $item) $html .= "
$1', '/\[code\](.*?)\[\/code\]/is' => '
$1
',
'/\[list\](.*?)\[\/list\]/is' => '