290 lines
6.8 KiB
PHP
290 lines
6.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Generate a pretty dope token.
|
|
*/
|
|
function token($length = 32)
|
|
{
|
|
return bin2hex(random_bytes($length));
|
|
}
|
|
|
|
/**
|
|
* Redirect to a new location.
|
|
*/
|
|
function redirect($location)
|
|
{
|
|
header("Location: $location");
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Flash data to the session, or retrieve an existing flash value. Returns false if the key does not exist.
|
|
*/
|
|
function flash($key, $value = '')
|
|
{
|
|
if ($value === '') return $_SESSION["flash_$key"] ?? false;
|
|
$_SESSION["flash_$key"] = $value;
|
|
return $value;
|
|
}
|
|
|
|
/**
|
|
* Clear a specific flash message.
|
|
*/
|
|
function unflash($key)
|
|
{
|
|
unset($_SESSION["flash_$key"]);
|
|
}
|
|
|
|
/**
|
|
* Clear all flash messages.
|
|
*/
|
|
function clear_flashes()
|
|
{
|
|
foreach ($_SESSION as $key => $_) {
|
|
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 '<input type="hidden" name="csrf" value="' . csrf() . '">';
|
|
}
|
|
|
|
/**
|
|
* 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 array from SESSION if it exists. Specify a key to get a specific value.
|
|
*/
|
|
function user($field = '')
|
|
{
|
|
if (empty($_SESSION['user'])) return false;
|
|
if ($field === '') return $_SESSION['user'];
|
|
return $_SESSION['user'][$field] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Check whether the user has selected a character.
|
|
*/
|
|
function user_selected_char()
|
|
{
|
|
return user('char_id') > 0 ? true : false;
|
|
}
|
|
|
|
/**
|
|
* If the current user has a selected char and the data is in the session, retrieve either the full array of data
|
|
* or a specific field. If there is no character data, populate it.
|
|
*/
|
|
function char($field = '')
|
|
{
|
|
// If there is no user, return false
|
|
if (empty($_SESSION['user'])) return false;
|
|
|
|
if (empty($GLOBALS['char'])) {
|
|
$GLOBALS['char'] = db_query(
|
|
db_live(),
|
|
"SELECT * FROM characters WHERE id = :c",
|
|
[':c' => user('char_id')]
|
|
)->fetchArray(SQLITE3_ASSOC);
|
|
}
|
|
|
|
if ($field === '') return $GLOBALS['char'];
|
|
return $GLOBALS['char'][$field] ?? false;
|
|
}
|
|
|
|
/**
|
|
* Shorthand to 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($char_id)
|
|
{
|
|
// If the character does not exist, return false
|
|
if (($char = char_find($char_id)) === false) return false;
|
|
$GLOBALS['char'] = $char;
|
|
|
|
// If the character ID is different, update the session and database
|
|
if ($_SESSION['user']['char_id'] !== $char_id) {
|
|
$_SESSION['user']['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($num, $denom, $precision = 4): int
|
|
{
|
|
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 field does not exist, or the entire wallet array if no field is specified.
|
|
*/
|
|
function wallet($field = '')
|
|
{
|
|
if (empty($GLOBALS['wallet'])) {
|
|
$GLOBALS['wallet'] = db_query(
|
|
db_live(),
|
|
"SELECT * FROM wallets WHERE user_id = :u",
|
|
[':u' => user('id')]
|
|
)->fetchArray(SQLITE3_ASSOC);
|
|
}
|
|
|
|
if ($field === '') return $GLOBALS['wallet'];
|
|
return $GLOBALS['wallet'][$field] ?? false;
|
|
}
|
|
|
|
/**
|
|
* 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 .= "<li>$item</li>";
|
|
return "<ul>$html</ul>";
|
|
}
|
|
|
|
/**
|
|
* Start a keyed stopwatch to measure the time between two points in the code.
|
|
*/
|
|
function stopwatch_start($key)
|
|
{
|
|
if (!env('debug', false)) return;
|
|
$GLOBALS['stopwatch'][$key] = microtime(true);
|
|
}
|
|
|
|
/**
|
|
* Stop a keyed stopwatch. Stores the time in the global $stopwatch array under the key.
|
|
*/
|
|
function stopwatch_stop($key)
|
|
{
|
|
if (!env('debug', false)) return;
|
|
if (empty($GLOBALS['stopwatch'][$key])) return 0;
|
|
$GLOBALS['stopwatch'][$key] = microtime(true) - $GLOBALS['stopwatch'][$key];
|
|
}
|
|
|
|
/**
|
|
* Get the stopwatch value and format it to within 10 digits.
|
|
*/
|
|
function stopwatch_get($key)
|
|
{
|
|
if (!env('debug', false)) return;
|
|
if (empty($GLOBALS['stopwatch'][$key])) return 0;
|
|
return number_format($GLOBALS['stopwatch'][$key], 10);
|
|
}
|
|
|
|
/**
|
|
* Conditional Echo; if the condition is true, echo the value. If the condition is false, echo the $or value.
|
|
*/
|
|
function ce($condition, $value, $or = '')
|
|
{
|
|
echo $condition ? $value : $or;
|
|
}
|
|
|
|
/**
|
|
* Get whether the request is an HTMX request.
|
|
*/
|
|
function is_htmx()
|
|
{
|
|
return isset($_SERVER['HTTP_HX_REQUEST']);
|
|
}
|
|
|
|
/**
|
|
* Get whether the request is an AJAX (fetch) request.
|
|
*/
|
|
function is_ajax()
|
|
{
|
|
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
|
|
}
|
|
|
|
/**
|
|
* Limit a request to AJAX only.
|
|
*/
|
|
function ajax_only()
|
|
{
|
|
if (!is_ajax()) router_error(418);
|
|
}
|
|
|
|
/**
|
|
* Return a JSON response with the given data.
|
|
*/
|
|
function json_response($data)
|
|
{
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
echo json_encode($data);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Return a title's [name, lore].
|
|
*/
|
|
function title($title_id)
|
|
{
|
|
return db_query(db_live(), 'SELECT * FROM titles WHERE id = :i', [':i' => $title_id])->fetchArray();
|
|
}
|