= 1024 && $i < count($units) - 1) { $value /= 1024; $i++; } return ($value >= 10 || $i === 0 ? round($value) : round($value, 1)) . ' ' . $units[$i]; } /** Start the session with hardened cookie settings (idempotent). */ function session_boot(): void { if (session_status() === PHP_SESSION_ACTIVE) { return; } session_set_cookie_params([ 'lifetime' => 0, 'path' => '/', 'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'httponly' => true, 'samesite' => 'Lax', ]); session_name('fpsid'); session_start(); } /** * URL of a script in this installation, for the server to call itself. * * site.base_url is preferred when it has been filled in, because it does not * depend on the request's Host header. Otherwise the URL is derived from the * current request, mapping APP_ROOT against DOCUMENT_ROOT so an app installed * in a subdirectory still resolves. Null means the URL cannot be determined — * the caller then has to do the work inline instead of dispatching it. */ function self_url(string $pathAndQuery): ?string { $configured = rtrim((string)config('site.base_url', ''), '/'); if ($configured !== '' && !str_contains($configured, 'example.com')) { return $configured . $pathAndQuery; } $host = (string)($_SERVER['HTTP_HOST'] ?? ''); $root = rtrim(str_replace('\\', '/', (string)($_SERVER['DOCUMENT_ROOT'] ?? '')), '/'); $app = str_replace('\\', '/', APP_ROOT); if ($host === '' || $root === '' || !str_starts_with($app, $root)) { return null; } $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; return $scheme . '://' . $host . rtrim(substr($app, strlen($root)), '/') . $pathAndQuery; } /** * Fire a request at one of this installation's own background scripts and hang * up without waiting for it. Those scripts set ignore_user_abort(), so they run * on regardless. * * A timeout is the expected, successful outcome: it means the request was * delivered and the script is busy with it. A *completed* response only counts * as success if it is the script's own 204 — anything else (a 404 from a wrong * key or a misconfigured base URL) means nothing is running, and saying so lets * the caller fall back to doing the work inline instead of silently stalling. */ function self_dispatch(?string $url, int $timeoutMs = 1000): bool { if ($url === null || !function_exists('curl_init')) { return false; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_NOSIGNAL => true, CURLOPT_CONNECTTIMEOUT_MS => $timeoutMs, CURLOPT_TIMEOUT_MS => $timeoutMs, ]); curl_exec($ch); $errno = curl_errno($ch); $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE); return $errno === CURLE_OPERATION_TIMEOUTED || ($errno === 0 && $status === 204); } /** Redirect and stop. */ function redirect(string $url): never { header('Location: ' . $url); exit; } /** Send a JSON response and stop (used by admin/api.php). */ function json_response(array $payload, int $status = 200): never { http_response_code($status); header('Content-Type: application/json; charset=utf-8'); echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); exit; }