Remove typing, work on stopwatches and debug
This commit is contained in:
parent
36d8f3405f
commit
3c0e7590ce
BIN
database/live.db
BIN
database/live.db
Binary file not shown.
|
@ -38,7 +38,10 @@ router_post($r, '/character/delete', 'char_controller_delete_post');
|
||||||
Router
|
Router
|
||||||
*/
|
*/
|
||||||
// [code, handler, params]
|
// [code, handler, params]
|
||||||
|
stopwatch_start('router');
|
||||||
$l = router_lookup($r, $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
|
$l = router_lookup($r, $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
|
||||||
|
stopwatch_stop('router');
|
||||||
|
|
||||||
if ($l['code'] !== 200) router_error($l['code']);
|
if ($l['code'] !== 200) router_error($l['code']);
|
||||||
$l['handler'](...$l['params'] ?? []);
|
$l['handler'](...$l['params'] ?? []);
|
||||||
|
|
||||||
|
@ -46,3 +49,8 @@ $l['handler'](...$l['params'] ?? []);
|
||||||
Cleanup
|
Cleanup
|
||||||
*/
|
*/
|
||||||
clear_flashes();
|
clear_flashes();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Stopwatch
|
||||||
|
*/
|
||||||
|
if (env('debug')) echo c_debug_stopwatch();
|
||||||
|
|
|
@ -76,3 +76,12 @@ function must_have_character()
|
||||||
change_user_character($char['id']);
|
change_user_character($char['id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The user must be authenticated and have a character.
|
||||||
|
*/
|
||||||
|
function auth_only_and_must_have_character()
|
||||||
|
{
|
||||||
|
auth_only();
|
||||||
|
must_have_character();
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,9 @@ require_once SRC . '/models/char.php';
|
||||||
require_once SRC . '/controllers/char.php';
|
require_once SRC . '/controllers/char.php';
|
||||||
require_once SRC . '/controllers/auth.php';
|
require_once SRC . '/controllers/auth.php';
|
||||||
|
|
||||||
|
// Track the start time of the request
|
||||||
|
define('START_TIME', microtime(true));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Load env, set error reporting, etc.
|
Load env, set error reporting, etc.
|
||||||
*/
|
*/
|
||||||
|
@ -34,14 +37,19 @@ if (env('debug') === 'true') {
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopwatch_start('bootstrap'); // Start the bootstrap stopwatch
|
||||||
|
|
||||||
// Generate a new CSRF token. (if one doesn't exist, that is)
|
// Generate a new CSRF token. (if one doesn't exist, that is)
|
||||||
csrf();
|
csrf();
|
||||||
|
|
||||||
// Have a global counter for queries
|
// Have global counters for queries
|
||||||
$GLOBALS['queries'] = 0;
|
$GLOBALS['queries'] = 0;
|
||||||
|
$GLOBALS['query_time'] = 0;
|
||||||
|
|
||||||
// Set the default page layout
|
// Set the default page layout
|
||||||
page_layout('basic');
|
page_layout('basic');
|
||||||
|
|
||||||
// Run auth_check to see if we're logged in, since it populates the user data in SESSION
|
// Run auth_check to see if we're logged in, since it populates the user data in SESSION
|
||||||
auth_check();
|
auth_check();
|
||||||
|
|
||||||
|
stopwatch_stop('bootstrap'); // Stop the bootstrap stopwatch
|
||||||
|
|
|
@ -49,7 +49,7 @@ function c_debug_query_log()
|
||||||
/**
|
/**
|
||||||
* Render the character select radio buttons.
|
* Render the character select radio buttons.
|
||||||
*/
|
*/
|
||||||
function c_char_select_box($id, array $char)
|
function c_char_select_box($id, $char)
|
||||||
{
|
{
|
||||||
return render('components/char_select_box', ['id' => $id, 'char' => $char]);
|
return render('components/char_select_box', ['id' => $id, 'char' => $char]);
|
||||||
}
|
}
|
||||||
|
@ -88,3 +88,11 @@ function c_form_field($type, $name, $placeholder, $required = false, $autocomple
|
||||||
$html .= $errors !== false && !empty($errors[$name]) ? ' class="form control error"' : ' class="form control"';
|
$html .= $errors !== false && !empty($errors[$name]) ? ' class="form control error"' : ' class="form control"';
|
||||||
return $html . " autocomplete=\"$autocomplete\">";
|
return $html . " autocomplete=\"$autocomplete\">";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the stopwatch debug component.
|
||||||
|
*/
|
||||||
|
function c_debug_stopwatch()
|
||||||
|
{
|
||||||
|
return render('components/debug_stopwatch');
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
function char_controller_list_get()
|
function char_controller_list_get()
|
||||||
{
|
{
|
||||||
auth_only(); must_have_character();
|
auth_only_and_must_have_character();
|
||||||
|
|
||||||
$GLOBALS['active_nav_tab'] = 'chars';
|
$GLOBALS['active_nav_tab'] = 'chars';
|
||||||
echo page('chars/list', ['chars' => char_list(user('id'))]);
|
echo page('chars/list', ['chars' => char_list(user('id'))]);
|
||||||
|
@ -16,7 +16,7 @@ function char_controller_list_get()
|
||||||
*/
|
*/
|
||||||
function char_controller_list_post()
|
function char_controller_list_post()
|
||||||
{
|
{
|
||||||
auth_only(); must_have_character(); csrf_ensure();
|
auth_only_and_must_have_character(); csrf_ensure();
|
||||||
|
|
||||||
$GLOBALS['active_nav_tab'] = 'chars';
|
$GLOBALS['active_nav_tab'] = 'chars';
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ function char_controller_list_post()
|
||||||
*/
|
*/
|
||||||
function char_controller_delete_post()
|
function char_controller_delete_post()
|
||||||
{
|
{
|
||||||
auth_only(); must_have_character(); csrf_ensure();
|
auth_only_and_must_have_character(); csrf_ensure();
|
||||||
|
|
||||||
$char_id = (int) ($_POST['char_id'] ?? 0);
|
$char_id = (int) ($_POST['char_id'] ?? 0);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
/**
|
/**
|
||||||
* Open a connection to a database.
|
* Open a connection to a database.
|
||||||
*/
|
*/
|
||||||
function db_open($path): SQLite3
|
function db_open($path)
|
||||||
{
|
{
|
||||||
$db = new SQLite3($path);
|
$db = new SQLite3($path);
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ function db_open($path): SQLite3
|
||||||
/**
|
/**
|
||||||
* Return a connection to the auth database.
|
* Return a connection to the auth database.
|
||||||
*/
|
*/
|
||||||
function db_auth(): SQLite3
|
function db_auth()
|
||||||
{
|
{
|
||||||
return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db');
|
return $GLOBALS['db_auth'] ??= db_open(__DIR__ . '/../database/auth.db');
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ function db_auth(): SQLite3
|
||||||
/**
|
/**
|
||||||
* Return a connection to the live database.
|
* Return a connection to the live database.
|
||||||
*/
|
*/
|
||||||
function db_live(): SQLite3
|
function db_live()
|
||||||
{
|
{
|
||||||
return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db');
|
return $GLOBALS['db_live'] ??= db_open(__DIR__ . '/../database/live.db');
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ function db_live(): SQLite3
|
||||||
/**
|
/**
|
||||||
* Return a connection to the fights database.
|
* Return a connection to the fights database.
|
||||||
*/
|
*/
|
||||||
function db_fights(): SQLite3
|
function db_fights()
|
||||||
{
|
{
|
||||||
return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db');
|
return $GLOBALS['db_fights'] ??= db_open(__DIR__ . '/../database/fights.db');
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ function db_fights(): SQLite3
|
||||||
/**
|
/**
|
||||||
* Return a connection to the blueprints database.
|
* Return a connection to the blueprints database.
|
||||||
*/
|
*/
|
||||||
function db_blueprints(): SQLite3
|
function db_blueprints()
|
||||||
{
|
{
|
||||||
return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db');
|
return $GLOBALS['db_blueprints'] ??= db_open(__DIR__ . '/../database/blueprints.db');
|
||||||
}
|
}
|
||||||
|
@ -55,28 +55,32 @@ function db_blueprints(): SQLite3
|
||||||
* Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and
|
* Take a SQLite3 database connection, a query string, and an array of parameters. Prepare the query and
|
||||||
* bind the parameters with proper type casting. Then execute the query and return the result.
|
* bind the parameters with proper type casting. Then execute the query and return the result.
|
||||||
*/
|
*/
|
||||||
function db_query(SQLite3 $db, $query, array $params = [])
|
function db_query($db, $query, $params = [])
|
||||||
{
|
{
|
||||||
$stmt = $db->prepare($query);
|
$stmt = $db->prepare($query);
|
||||||
if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value));
|
if (!empty($params)) foreach ($params as $key => $value) $stmt->bindValue($key, $value, getSQLiteType($value));
|
||||||
db_log($query);
|
$start = microtime(true);
|
||||||
return $stmt->execute();
|
$r = $stmt->execute();
|
||||||
|
db_log($query, microtime(true) - $start);
|
||||||
|
return $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a SQLite3 database connection and a query string. Execute the query and return the result.
|
* Take a SQLite3 database connection and a query string. Execute the query and return the result.
|
||||||
*/
|
*/
|
||||||
function db_exec(SQLite3 $db, $query)
|
function db_exec($db, $query)
|
||||||
{
|
{
|
||||||
db_log($query);
|
$start = microtime(true);
|
||||||
return $db->exec($query);
|
$r = $db->exec($query);
|
||||||
|
db_log($query, microtime(true) - $start);
|
||||||
|
return $r;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a SQLite3 database connection, a column name, and a value. Execute a COUNT query to see if the value
|
* Take a SQLite3 database connection, a column name, and a value. Execute a COUNT query to see if the value
|
||||||
* exists in the column. Return true if the value exists, false otherwise.
|
* exists in the column. Return true if the value exists, false otherwise.
|
||||||
*/
|
*/
|
||||||
function db_exists(SQLite3 $db, $table, $column, $value, $caseInsensitive = true)
|
function db_exists($db, $table, $column, $value, $caseInsensitive = true)
|
||||||
{
|
{
|
||||||
if ($caseInsensitive) {
|
if ($caseInsensitive) {
|
||||||
$query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1";
|
$query = "SELECT 1 FROM $table WHERE $column = :v COLLATE NOCASE LIMIT 1";
|
||||||
|
@ -104,8 +108,9 @@ function getSQLiteType($value): int
|
||||||
/**
|
/**
|
||||||
* Log the given query string to the db debug log.
|
* Log the given query string to the db debug log.
|
||||||
*/
|
*/
|
||||||
function db_log($query)
|
function db_log($query, $timeTaken = 0)
|
||||||
{
|
{
|
||||||
$GLOBALS['queries']++;
|
$GLOBALS['queries']++;
|
||||||
if (env('debug', false)) $GLOBALS['query_log'][] = $query;
|
$GLOBALS['query_time'] += $timeTaken;
|
||||||
|
if (env('debug', false)) $GLOBALS['query_log'][] = [$query, $timeTaken];
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,5 +36,12 @@ function env_load($filePath)
|
||||||
*/
|
*/
|
||||||
function env($key, $default = null)
|
function env($key, $default = null)
|
||||||
{
|
{
|
||||||
return $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default);
|
$v = $_ENV[$key] ?? $_SERVER[$key] ?? (getenv($key) ?: $default);
|
||||||
|
return match(true) {
|
||||||
|
$v === 'true' => true,
|
||||||
|
$v === 'false' => false,
|
||||||
|
is_numeric($v) => (int) $v,
|
||||||
|
is_float($v) => (float) $v,
|
||||||
|
default => $v
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,7 @@ function percent($num, $denom, $precision = 4): int
|
||||||
* the data is up to date with every request without having to query the database every use within, for example, a
|
* 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.
|
* template. Will return false if the field does not exist, or the entire wallet array if no field is specified.
|
||||||
*/
|
*/
|
||||||
function wallet($field = ''): array|int
|
function wallet($field = '')
|
||||||
{
|
{
|
||||||
if (empty($GLOBALS['wallet'])) {
|
if (empty($GLOBALS['wallet'])) {
|
||||||
$GLOBALS['wallet'] = db_query(
|
$GLOBALS['wallet'] = db_query(
|
||||||
|
@ -188,9 +188,38 @@ function wallet($field = ''): array|int
|
||||||
/**
|
/**
|
||||||
* Format an array of strings to a ul element.
|
* Format an array of strings to a ul element.
|
||||||
*/
|
*/
|
||||||
function array_to_ul(array $array)
|
function array_to_ul($array)
|
||||||
{
|
{
|
||||||
$html = '';
|
$html = '';
|
||||||
foreach ($array as $item) $html .= "<li>$item</li>";
|
foreach ($array as $item) $html .= "<li>$item</li>";
|
||||||
return "<ul>$html</ul>";
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ const currently = [
|
||||||
* of overrides to set additional fields. A character's name must be unique, but this function does not check for
|
* of overrides to set additional fields. A character's name must be unique, but this function does not check for
|
||||||
* that. Returns the created character's ID.
|
* that. Returns the created character's ID.
|
||||||
*/
|
*/
|
||||||
function char_create($user_id, $name, array $overrides = []): int
|
function char_create($user_id, $name, $overrides = []): int
|
||||||
{
|
{
|
||||||
// Prep the data and merge in any overrides
|
// Prep the data and merge in any overrides
|
||||||
$data = ['user_id' => $user_id, 'name' => $name];
|
$data = ['user_id' => $user_id, 'name' => $name];
|
||||||
|
@ -65,7 +65,7 @@ function char_location_create($char_id, $x = 0, $y = 0, $currently = 0)
|
||||||
* Create the character's gear table. A character's gear is where they store their equipped items.
|
* Create the character's gear table. A character's gear is where they store their equipped items.
|
||||||
* @TODO: implement initial gear
|
* @TODO: implement initial gear
|
||||||
*/
|
*/
|
||||||
function char_gear_create($char_id, array $initialGear = [])
|
function char_gear_create($char_id, $initialGear = [])
|
||||||
{
|
{
|
||||||
if (db_query(db_live(), "INSERT INTO char_gear (char_id) VALUES (:p)", [':p' => $char_id]) === false) {
|
if (db_query(db_live(), "INSERT INTO char_gear (char_id) VALUES (:p)", [':p' => $char_id]) === false) {
|
||||||
throw new Exception('Failed to create character gear. (cgc)');
|
throw new Exception('Failed to create character gear. (cgc)');
|
||||||
|
@ -94,7 +94,7 @@ function char_count($user_id): int
|
||||||
/**
|
/**
|
||||||
* Get a an array of id => [name, level] for all characters associated with an account ID.
|
* Get a an array of id => [name, level] for all characters associated with an account ID.
|
||||||
*/
|
*/
|
||||||
function char_list($user_id): array
|
function char_list($user_id)
|
||||||
{
|
{
|
||||||
$stmt = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = :u", [':u' => $user_id]);
|
$stmt = db_query(db_live(), "SELECT id, name, level FROM characters WHERE user_id = :u", [':u' => $user_id]);
|
||||||
if ($stmt === false) throw new Exception('Failed to list characters. (cl)');
|
if ($stmt === false) throw new Exception('Failed to list characters. (cl)');
|
||||||
|
@ -110,7 +110,7 @@ function char_list($user_id): array
|
||||||
/**
|
/**
|
||||||
* Get a character's location info by their character ID. Returns the location's data as an associative array.
|
* Get a character's location info by their character ID. Returns the location's data as an associative array.
|
||||||
*/
|
*/
|
||||||
function char_get_location($char_id): array
|
function char_get_location($char_id)
|
||||||
{
|
{
|
||||||
// Get the location
|
// Get the location
|
||||||
$location = db_query(db_live(), "SELECT * FROM char_locations WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);
|
$location = db_query(db_live(), "SELECT * FROM char_locations WHERE char_id = :p", [':p' => $char_id])->fetchArray(SQLITE3_ASSOC);
|
||||||
|
|
|
@ -57,10 +57,3 @@ const item_qualities = [
|
||||||
5 => 'Excellent',
|
5 => 'Excellent',
|
||||||
6 => 'Masterwork',
|
6 => 'Masterwork',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an item
|
|
||||||
*/
|
|
||||||
function create_item($name, array $type, array $opts) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ function page_layout($layout = '')
|
||||||
/**
|
/**
|
||||||
* Shorthand to render a page with the current layout.
|
* Shorthand to render a page with the current layout.
|
||||||
*/
|
*/
|
||||||
function page($view, array $data = [])
|
function page($view, $data = [])
|
||||||
{
|
{
|
||||||
return render("layouts/" . page_layout(), ['view' => "pages/$view"] + $data);
|
return render("layouts/" . page_layout(), ['view' => "pages/$view"] + $data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* Example:
|
* Example:
|
||||||
* `router_add($routes, 'GET', '/posts/:id', function($id) { echo "Viewing post $id"; });`
|
* `router_add($routes, 'GET', '/posts/:id', function($id) { echo "Viewing post $id"; });`
|
||||||
*/
|
*/
|
||||||
function router_add(array &$routes, $method, $route, callable $handler)
|
function router_add(&$routes, $method, $route, $handler)
|
||||||
{
|
{
|
||||||
// Expand the route into segments and make dynamic segments into a common placeholder
|
// Expand the route into segments and make dynamic segments into a common placeholder
|
||||||
$segments = array_map(function($segment) {
|
$segments = array_map(function($segment) {
|
||||||
|
@ -33,7 +33,7 @@ function router_add(array &$routes, $method, $route, callable $handler)
|
||||||
*
|
*
|
||||||
* @return array ['code', 'handler', 'params']
|
* @return array ['code', 'handler', 'params']
|
||||||
*/
|
*/
|
||||||
function router_lookup(array $routes, $method, $uri): array
|
function router_lookup($routes, $method, $uri)
|
||||||
{
|
{
|
||||||
// node is a reference to our current location in the node tree
|
// node is a reference to our current location in the node tree
|
||||||
$node = $routes;
|
$node = $routes;
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<div id="debug-query-log">
|
<div id="debug-query-log">
|
||||||
<h3>Query Log</h3>
|
<h3>Query Log</h3>
|
||||||
<p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p>
|
<p class="mb-2"><?= $GLOBALS['queries'] ?> queries were executed.</p>
|
||||||
<?php if (!empty($GLOBALS['query_log'])) foreach ($GLOBALS['query_log'] as $query) echo "<p>$query</p>"; ?>
|
<?php
|
||||||
|
if (!empty($GLOBALS['query_log']))
|
||||||
|
foreach ($GLOBALS['query_log'] as $query) {
|
||||||
|
$time = number_format($query[1], 6);
|
||||||
|
echo "<p>({$time}s) {$query[0]}</p>";
|
||||||
|
}
|
||||||
|
?>
|
||||||
</div>
|
</div>
|
||||||
|
|
6
templates/components/debug_stopwatch.php
Normal file
6
templates/components/debug_stopwatch.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<div id="debug-query-log">
|
||||||
|
<h3>Stopwatches</h3>
|
||||||
|
<p class="mb-2">Page execution took <?= number_format((microtime(true) - START_TIME), 10) ?> seconds.</p>
|
||||||
|
<p>Bootstrap: <?= stopwatch_get('bootstrap') ?> seconds</p>
|
||||||
|
<p>Router: <?= stopwatch_get('router') ?> seconds</p>
|
||||||
|
</div>
|
|
@ -46,10 +46,16 @@
|
||||||
<footer>
|
<footer>
|
||||||
<p>© <?= date('Y') ?> Dragon Knight</p>
|
<p>© <?= date('Y') ?> Dragon Knight</p>
|
||||||
<p>q<?= $GLOBALS['queries'] ?></p>
|
<p>q<?= $GLOBALS['queries'] ?></p>
|
||||||
|
<p>qt<?= number_format($GLOBALS['query_time'], env('debug', false) === true ? 6 : 2) ?></p>
|
||||||
|
<p>t<?= number_format((microtime(true) - START_TIME), env('debug', false) === true ? 6 : 2) ?></p>
|
||||||
<p>v<?= env('version') ?></p>
|
<p>v<?= env('version') ?></p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<?php if (env('debug', false)) echo c_debug_query_log(); ?>
|
<?php
|
||||||
|
if (env('debug', false)) {
|
||||||
|
echo c_debug_query_log();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Tooltip from '/assets/scripts/tooltip.js';
|
import Tooltip from '/assets/scripts/tooltip.js';
|
||||||
|
|
Loading…
Reference in New Issue
Block a user