get('app.timezone')); return ($timezone !== '' && in_array($timezone, DateTimeZone::listIdentifiers(), true)) ? $timezone : 'UTC'; } function app_now(): string { return gmdate('Y-m-d H:i:s'); } function app_is_prod(): bool { return Base::instance()->get('app.env') === 'prod'; } // ── Fichiers et chemins ───────────────────────────────────────────── function app_ensure_dir(string $path): void { if (!is_dir($path)) { mkdir($path, 0775, true); } } function app_db_path(): string { return app_root() . '/db/app.sqlite'; } function app_logs_dir(): string { return app_root() . '/logs'; } function app_public_media_dir(): string { return app_root() . '/public/uploads/media'; } function app_media_url(string $fileName): string { return rtrim((string) Base::instance()->get('BASE'), '/') . '/uploads/media/' . rawurlencode($fileName); } // ── Texte ─────────────────────────────────────────────────────────── function app_unique_slug(string $value, callable $exists): string { $base = Web::instance()->slug(trim($value)); if ($base === '') { $base = 'article'; } if (!$exists($base)) { return $base; } for ($i = 2; $i <= 1000; $i++) { $candidate = $base . '-' . $i; if (!$exists($candidate)) { return $candidate; } } throw new RuntimeException('Impossible de générer un slug unique.'); } function app_format_datetime_fr(string $value): string { static $utc, $formatter; $value = trim($value); if ($value === '') { return ''; } try { $utc ??= new DateTimeZone('UTC'); $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s', $value, $utc); if (!$date instanceof DateTimeImmutable) { $date = new DateTimeImmutable($value, $utc); } $date = $date->setTimezone(new DateTimeZone(date_default_timezone_get())); $formatter ??= new IntlDateFormatter( 'fr_FR', IntlDateFormatter::LONG, IntlDateFormatter::SHORT, date_default_timezone_get(), IntlDateFormatter::GREGORIAN, "d MMMM yyyy 'à' HH:mm" ); $formatted = $formatter->format($date); return is_string($formatted) && $formatted !== '' ? $formatted : $value; } catch (Throwable) { return $value; } }