/ tag builders (CI4 html helper).
function script_tag(string $src): string {
return '' . "\n";
}
function link_tag(string $href, string $rel = 'stylesheet'): string {
return '' . "\n";
}
function site_url(string $path = ''): string { return base_url($path); }
function esc($value, string $context = 'html'): string {
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
}
// Render a partial like CI4's view(): extract $data + the shared page vars
// (info / navbar / current_uri_2 via $GLOBALS['_view_shared']) and return the
// buffered HTML. Lets partials read $info without threading it through callers.
function view(string $name, array $data = []): string {
$shared = $GLOBALS['_view_shared'] ?? [];
extract($shared);
extract($data);
ob_start();
include SITE_INCLUDES . '/' . $name . '.php';
return ob_get_clean();
}
// Minimal session() shim — the templates only read getFlashdata().
// Per-request singleton: the constructor's flash sweep therefore runs once per
// request, which is what makes flashdata actually "flash".
function session(): _FitSession {
static $s = null;
if ($s === null) $s = new _FitSession();
return $s;
}
class _FitSession {
// CI4-style flashdata lifecycle. A value set with setFlashdata() in request N
// (the POST handler, just before its redirect) must be readable in request
// N+1 (the redirect target) and then disappear — otherwise the banner sticks
// around forever, surviving reloads, because nothing ever clears it.
//
// We track an age marker per key: setFlashdata() marks it 'new'; on the next
// request the sweep ages 'new' → 'old' (still readable), and on the request
// after that it deletes anything 'old'. The sweep runs once, here, before any
// read or write this request (setFlashdata() is itself the first session()
// call in a POST handler, so the constructor always precedes it).
public function __construct() {
foreach ($_SESSION['_flash_age'] ?? [] as $k => $age) {
if ($age === 'old') {
unset($_SESSION['_flash'][$k], $_SESSION['_flash_age'][$k]);
} else {
$_SESSION['_flash_age'][$k] = 'old';
}
}
// Sweep orphans left by the previous (never-expiring) shim: any flash
// value with no age marker is stale and would otherwise stick forever.
foreach (array_keys($_SESSION['_flash'] ?? []) as $k) {
if (!isset($_SESSION['_flash_age'][$k])) unset($_SESSION['_flash'][$k]);
}
if (empty($_SESSION['_flash'])) unset($_SESSION['_flash']);
if (empty($_SESSION['_flash_age'])) unset($_SESSION['_flash_age']);
}
public function getFlashdata(string $key) { return $_SESSION['_flash'][$key] ?? null; }
public function setFlashdata(string $key, $val): void {
$_SESSION['_flash'][$key] = $val;
$_SESSION['_flash_age'][$key] = 'new';
}
public function get(string $key) { return $_SESSION[$key] ?? null; }
public function set(string $key, $val): void { $_SESSION[$key] = $val; }
}