|
- <?php
-
- declare(strict_types=1);
-
- use Core\App;
- use Core\Database;
- use Core\MigrationManager;
- use Core\Request;
- use Core\Response;
- use Core\View;
-
- function app(): App
- {
- static $app = null;
-
- if ($app === null) {
- $app = new App();
- }
-
- return $app;
- }
-
- function view(string $view, array $data = []): Response
- {
- return View::render($view, $data);
- }
-
- function redirect(string $url): Response
- {
- return Response::redirect($url);
- }
-
- function request(): Request
- {
- return Request::capture();
- }
-
- function database(): Database
- {
- static $database = null;
-
- global $databaseOverride;
-
- if ($databaseOverride instanceof Database) {
- return $databaseOverride;
- }
-
- if ($database instanceof Database) {
- return $database;
- }
-
- /** @var array<string, mixed> $config */
- $config = require __DIR__ . '/../config/database.php';
-
- prepareSqliteDatabase($config['dsn'] ?? '');
-
- $database = new Database($config);
-
- return $database;
- }
-
- function set_database(Database $database): void
- {
- global $databaseOverride;
-
- $databaseOverride = $database;
- }
-
- function reset_database(): void
- {
- global $databaseOverride;
-
- $databaseOverride = null;
- }
-
- function migration_manager(): MigrationManager
- {
- static $migrationManager = null;
-
- if ($migrationManager === null) {
- $migrationManager = new MigrationManager(database(), __DIR__ . '/../database/migrations');
- }
-
- return $migrationManager;
- }
-
- function ensureSessionStarted(): void
- {
- if (session_status() === PHP_SESSION_NONE) {
- session_start();
- }
- }
-
- function prepareSqliteDatabase(string $dsn): void
- {
- if (!str_starts_with($dsn, 'sqlite:')) {
- return;
- }
-
- $path = substr($dsn, 7);
-
- if ($path === false || $path === '') {
- return;
- }
-
- $directory = dirname($path);
-
- if (!is_dir($directory)) {
- mkdir($directory, 0777, true);
- }
-
- if (!is_writable($directory)) {
- @chmod($directory, 0777);
- }
-
- if (!file_exists($path)) {
- touch($path);
- }
-
- if (!is_writable($path)) {
- @chmod($path, 0666);
- }
- }
-
- function e(?string $value): string
- {
- return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- }
-
- function asset(string $path): string
- {
- return '/' . ltrim($path, '/');
- }
-
- function csrf_token(): string
- {
- ensureSessionStarted();
-
- if (!isset($_SESSION['_csrf_token']) || !is_string($_SESSION['_csrf_token'])) {
- $_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
- }
-
- return $_SESSION['_csrf_token'];
- }
-
- function csrf_field(): string
- {
- return '<input type="hidden" name="_token" value="' . e(csrf_token()) . '">';
- }
-
- function verify_csrf_token(?string $token): bool
- {
- ensureSessionStarted();
-
- if (!is_string($token) || $token === '') {
- return false;
- }
-
- $sessionToken = $_SESSION['_csrf_token'] ?? null;
-
- return is_string($sessionToken) && hash_equals($sessionToken, $token);
- }
-
- function h(string $value): string
- {
- return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
- }
-
- function format_date(?string $value, string $format = 'M j, Y'): string
- {
- if (!is_string($value) || trim($value) === '') {
- return '—';
- }
-
- try {
- $date = new DateTimeImmutable($value);
- } catch (Throwable) {
- return $value;
- }
-
- return $date->format($format);
- }
-
- function format_number(int|float|null $value): string
- {
- if ($value === null) {
- return '0';
- }
-
- return number_format((float) $value);
- }
-
- function money_cents(int|null $value): string
- {
- if ($value === null) {
- return '$0';
- }
-
- return '$' . number_format($value / 100, 0);
- }
-
- function project_status_label(string $status): string
- {
- return match ($status) {
- 'planned' => 'Planned',
- 'active' => 'Active',
- 'at-risk' => 'At risk',
- 'paused' => 'Paused',
- 'done' => 'Done',
- default => ucfirst($status),
- };
- }
-
- function task_status_label(string $status): string
- {
- return match ($status) {
- 'backlog', 'todo' => 'Backlog',
- 'in-progress', 'doing' => 'In progress',
- 'blocked' => 'Blocked',
- 'review' => 'In review',
- 'done' => 'Done',
- default => ucfirst($status),
- };
- }
-
- function status_class(string $status): string
- {
- return match ($status) {
- 'planned', 'backlog', 'todo' => 'is-neutral',
- 'active', 'in-progress', 'doing' => 'is-blue',
- 'blocked', 'at-risk' => 'is-red',
- 'review' => 'is-amber',
- 'done' => 'is-green',
- 'paused' => 'is-slate',
- default => 'is-neutral',
- };
- }
|