DK2/src/helpers.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();
}