A project management app derived from Mind-Vision-Code
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

238 linhas
4.6KB

  1. <?php
  2. declare(strict_types=1);
  3. use Core\App;
  4. use Core\Database;
  5. use Core\MigrationManager;
  6. use Core\Request;
  7. use Core\Response;
  8. use Core\View;
  9. function app(): App
  10. {
  11. static $app = null;
  12. if ($app === null) {
  13. $app = new App();
  14. }
  15. return $app;
  16. }
  17. function view(string $view, array $data = []): Response
  18. {
  19. return View::render($view, $data);
  20. }
  21. function redirect(string $url): Response
  22. {
  23. return Response::redirect($url);
  24. }
  25. function request(): Request
  26. {
  27. return Request::capture();
  28. }
  29. function database(): Database
  30. {
  31. static $database = null;
  32. global $databaseOverride;
  33. if ($databaseOverride instanceof Database) {
  34. return $databaseOverride;
  35. }
  36. if ($database instanceof Database) {
  37. return $database;
  38. }
  39. /** @var array<string, mixed> $config */
  40. $config = require __DIR__ . '/../config/database.php';
  41. prepareSqliteDatabase($config['dsn'] ?? '');
  42. $database = new Database($config);
  43. return $database;
  44. }
  45. function set_database(Database $database): void
  46. {
  47. global $databaseOverride;
  48. $databaseOverride = $database;
  49. }
  50. function reset_database(): void
  51. {
  52. global $databaseOverride;
  53. $databaseOverride = null;
  54. }
  55. function migration_manager(): MigrationManager
  56. {
  57. static $migrationManager = null;
  58. if ($migrationManager === null) {
  59. $migrationManager = new MigrationManager(database(), __DIR__ . '/../database/migrations');
  60. }
  61. return $migrationManager;
  62. }
  63. function ensureSessionStarted(): void
  64. {
  65. if (session_status() === PHP_SESSION_NONE) {
  66. session_start();
  67. }
  68. }
  69. function prepareSqliteDatabase(string $dsn): void
  70. {
  71. if (!str_starts_with($dsn, 'sqlite:')) {
  72. return;
  73. }
  74. $path = substr($dsn, 7);
  75. if ($path === false || $path === '') {
  76. return;
  77. }
  78. $directory = dirname($path);
  79. if (!is_dir($directory)) {
  80. mkdir($directory, 0777, true);
  81. }
  82. if (!is_writable($directory)) {
  83. @chmod($directory, 0777);
  84. }
  85. if (!file_exists($path)) {
  86. touch($path);
  87. }
  88. if (!is_writable($path)) {
  89. @chmod($path, 0666);
  90. }
  91. }
  92. function e(?string $value): string
  93. {
  94. return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
  95. }
  96. function asset(string $path): string
  97. {
  98. return '/' . ltrim($path, '/');
  99. }
  100. function csrf_token(): string
  101. {
  102. ensureSessionStarted();
  103. if (!isset($_SESSION['_csrf_token']) || !is_string($_SESSION['_csrf_token'])) {
  104. $_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
  105. }
  106. return $_SESSION['_csrf_token'];
  107. }
  108. function csrf_field(): string
  109. {
  110. return '<input type="hidden" name="_token" value="' . e(csrf_token()) . '">';
  111. }
  112. function verify_csrf_token(?string $token): bool
  113. {
  114. ensureSessionStarted();
  115. if (!is_string($token) || $token === '') {
  116. return false;
  117. }
  118. $sessionToken = $_SESSION['_csrf_token'] ?? null;
  119. return is_string($sessionToken) && hash_equals($sessionToken, $token);
  120. }
  121. function h(string $value): string
  122. {
  123. return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
  124. }
  125. function format_date(?string $value, string $format = 'M j, Y'): string
  126. {
  127. if (!is_string($value) || trim($value) === '') {
  128. return '—';
  129. }
  130. try {
  131. $date = new DateTimeImmutable($value);
  132. } catch (Throwable) {
  133. return $value;
  134. }
  135. return $date->format($format);
  136. }
  137. function format_number(int|float|null $value): string
  138. {
  139. if ($value === null) {
  140. return '0';
  141. }
  142. return number_format((float) $value);
  143. }
  144. function money_cents(int|null $value): string
  145. {
  146. if ($value === null) {
  147. return '$0';
  148. }
  149. return '$' . number_format($value / 100, 0);
  150. }
  151. function project_status_label(string $status): string
  152. {
  153. return match ($status) {
  154. 'planned' => 'Planned',
  155. 'active' => 'Active',
  156. 'at-risk' => 'At risk',
  157. 'paused' => 'Paused',
  158. 'done' => 'Done',
  159. default => ucfirst($status),
  160. };
  161. }
  162. function task_status_label(string $status): string
  163. {
  164. return match ($status) {
  165. 'backlog', 'todo' => 'Backlog',
  166. 'in-progress', 'doing' => 'In progress',
  167. 'blocked' => 'Blocked',
  168. 'review' => 'In review',
  169. 'done' => 'Done',
  170. default => ucfirst($status),
  171. };
  172. }
  173. function status_class(string $status): string
  174. {
  175. return match ($status) {
  176. 'planned', 'backlog', 'todo' => 'is-neutral',
  177. 'active', 'in-progress', 'doing' => 'is-blue',
  178. 'blocked', 'at-risk' => 'is-red',
  179. 'review' => 'is-amber',
  180. 'done' => 'is-green',
  181. 'paused' => 'is-slate',
  182. default => 'is-neutral',
  183. };
  184. }

Powered by TurnKey Linux.