Globinours/public/index.php

263 lines
11 KiB
PHP

<?php
declare(strict_types=1);
umask(0077);
require_once __DIR__ . '/../app/Services/DB.php';
require_once __DIR__ . '/../app/Services/Migrations.php';
require_once __DIR__ . '/../app/Services/Ids.php';
require_once __DIR__ . '/../app/Services/IcadService.php';
require_once __DIR__ . '/../app/Services/DocumentsService.php';
require_once __DIR__ . '/../app/Services/Auth.php';
require_once __DIR__ . '/../app/Services/TrustedDeviceService.php';
require_once __DIR__ . '/../app/Services/PermissionService.php';
require_once __DIR__ . '/../app/Services/AuditService.php';
require_once __DIR__ . '/../app/Services/BackupService.php';
require_once __DIR__ . '/../app/Services/AppSettings.php';
require_once __DIR__ . '/../app/Services/InstallationService.php';
require_once __DIR__ . '/../app/Services/SystemHealthService.php';
require_once __DIR__ . '/../app/Services/NotificationService.php';
require_once __DIR__ . '/../app/Services/PrivacyService.php';
require_once __DIR__ . '/../app/Services/I18n.php';
require_once __DIR__ . '/../app/Services/PricingService.php';
require_once __DIR__ . '/../app/Services/ImageService.php';
require_once __DIR__ . '/../app/Services/PrivateMediaService.php';
require_once __DIR__ . '/../app/Services/SecurityService.php';
require_once __DIR__ . '/../app/Services/ExportService.php';
require_once __DIR__ . '/../app/Services/AssociationBrand.php';
require_once __DIR__ . '/../app/Services/GrantService.php';
require_once __DIR__ . '/../app/Services/StatisticsService.php';
require_once __DIR__ . '/../app/Services/LocationHistoryService.php';
require_once __DIR__ . '/../app/Services/GeoService.php';
require_once __DIR__ . '/../app/Services/Asm3Analyzer.php';
require_once __DIR__ . '/../app/Services/Asm3MigrationPlanner.php';
require_once __DIR__ . '/../app/Services/Asm3AnimalImporter.php';
require_once __DIR__ . '/../app/Services/Asm3ContactPlanner.php';
require_once __DIR__ . '/../app/Services/Asm3ContactImporter.php';
require_once __DIR__ . '/../app/Services/Asm3MovementImporter.php';
require_once __DIR__ . '/../app/Services/Asm3ClinicalImporter.php';
require_once __DIR__ . '/../app/Services/Asm3LitterImporter.php';
require_once __DIR__ . '/../app/Services/Asm3ResetService.php';
require_once __DIR__ . '/../app/Services/DemoDataService.php';
require_once __DIR__ . '/../app/Services/SpeciesService.php';
require_once __DIR__ . '/../app/Services/ShelterRoomService.php';
require_once __DIR__ . '/../app/Services/LabReferenceService.php';
require_once __DIR__ . '/../app/Services/InventoryService.php';
require_once __DIR__ . '/../app/Services/AgendaService.php';
require_once __DIR__ . '/../app/Services/CalendarFeedService.php';
require_once __DIR__ . '/../app/Services/GlobalSearchService.php';
require_once __DIR__ . '/../app/Controllers/AnimalsController.php';
require_once __DIR__ . '/../app/Controllers/DevController.php';
require_once __DIR__ . '/../app/Controllers/DashboardController.php';
require_once __DIR__ . '/../app/Controllers/AdminController.php';
require_once __DIR__ . '/../app/Controllers/AdoptionsController.php';
require_once __DIR__ . '/../app/Controllers/DirectoryController.php';
require_once __DIR__ . '/../app/Controllers/CareRoundsController.php';
require_once __DIR__ . '/../app/Controllers/DocumentsController.php';
require_once __DIR__ . '/../app/Controllers/AuthController.php';
require_once __DIR__ . '/../app/Controllers/SettingsController.php';
require_once __DIR__ . '/../app/Controllers/DeathController.php';
require_once __DIR__ . '/../app/Controllers/ProjectController.php';
require_once __DIR__ . '/../app/Controllers/GrantsController.php';
require_once __DIR__ . '/../app/Controllers/StatisticsController.php';
require_once __DIR__ . '/../app/Controllers/PlacementsController.php';
require_once __DIR__ . '/../app/Controllers/Asm3ImportController.php';
require_once __DIR__ . '/../app/Controllers/DewormingController.php';
require_once __DIR__ . '/../app/Controllers/MedicalDocumentsController.php';
require_once __DIR__ . '/../app/Controllers/PublicSiteController.php';
require_once __DIR__ . '/../app/Controllers/PrivacyController.php';
require_once __DIR__ . '/../app/Controllers/InventoryController.php';
require_once __DIR__ . '/../app/Controllers/AgendaController.php';
require_once __DIR__ . '/../app/Controllers/DevicesController.php';
require_once __DIR__ . '/../app/Controllers/SurgeriesController.php';
require_once __DIR__ . '/../app/Controllers/AccountingController.php';
require_once __DIR__ . '/../app/Controllers/SearchController.php';
require_once __DIR__ . '/../app/Views/_helpers.php';
function loadEnv(string $path): void
{
if (!file_exists($path)) {
return;
}
foreach (file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
if (str_starts_with(trim($line), '#')) {
continue;
}
[$name, $value] = array_map('trim', explode('=', $line, 2));
if (!array_key_exists($name, $_ENV)) {
putenv("$name=$value");
$_ENV[$name] = $value;
}
}
}
loadEnv(__DIR__ . '/../.env');
$applicationLogDir = dirname(__DIR__) . '/storage/logs';
if (!is_dir($applicationLogDir)) {
@mkdir($applicationLogDir, 0775, true);
}
if (is_dir($applicationLogDir) && is_writable($applicationLogDir)) {
ini_set('log_errors', '1');
ini_set('error_log', $applicationLogDir . '/php-errors.log');
}
if (PHP_SAPI !== 'cli' && is_file(dirname(__DIR__) . '/storage/maintenance.flag')) {
http_response_code(503);
header('Retry-After: 120');
header('Content-Type: text/html; charset=utf-8');
echo '<!doctype html><html lang="fr"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>Maintenance — Globinours</title><body style="font:18px system-ui;max-width:700px;margin:12vh auto;padding:24px"><h1>Globinours fait une petite pause 🐾</h1><p>Une mise à jour sécurisée est en cours. Réessayez dans quelques instants.</p></body></html>';
exit();
}
$bootstrapPath = rtrim((string) (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/'), '/') ?: '/';
$bootstrapIssues = InstallationService::blockingRuntimeIssues();
if ($bootstrapIssues) {
I18n::setLocale('fr');
Auth::start();
if ($bootstrapPath !== '/setup') {
header('Location: /setup');
exit();
}
AuthController::setupUnavailable(InstallationService::runtimeChecks(true));
exit();
}
function render(string $view, array $vars = []): void
{
extract($vars, EXTR_SKIP);
$viewFile = __DIR__ . '/../app/Views/' . $view;
require __DIR__ . '/../app/Views/layout.php';
}
Migrations::run(__DIR__ . '/../migrations');
PrivateMediaService::migrateLegacy();
SecurityService::hardenRuntimeFiles();
I18n::setLocale(AppSettings::get('app_language'));
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?? '/';
$path = rtrim($path, '/') ?: '/';
Auth::start();
header_remove('X-Powered-By');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(self), geolocation=(self), microphone=()');
header(
"Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https://tile.openstreetmap.org https://*.tile.openstreetmap.org; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; manifest-src 'self'; worker-src 'self'",
);
header('Cross-Origin-Opener-Policy: same-origin');
header('Cross-Origin-Resource-Policy: same-origin');
if (Auth::isSecureRequest()) {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}
if ($path === '/gestion') {
header('Location: ' . (Auth::user() ? '/dashboard' : '/login?next=%2Fdashboard'));
exit();
}
if (PublicSiteController::handles($path)) {
PublicSiteController::dispatch($path);
exit();
}
if ($path === '/auth/logo') {
AuthController::logo();
exit();
}
if ($path === '/pwa-icon-192.png') {
AuthController::pwaIcon(192);
exit();
}
if ($path === '/pwa-icon-512.png') {
AuthController::pwaIcon(512);
exit();
}
if ($path === '/setup') {
AuthController::setup();
exit();
}
if ($path === '/login') {
AuthController::login();
exit();
}
if ($path === '/logout') {
AuthController::logout();
exit();
}
if ($path === '/agenda/feed.ics') {
AgendaController::feed();
exit();
}
if (Auth::countUsers() === 0) {
header('Location: /setup');
exit();
}
if (!Auth::user()) {
header('Location: /login?next=' . rawurlencode($_SERVER['REQUEST_URI'] ?? '/dashboard'));
exit();
}
header('Cache-Control: private, no-store');
if ($path === '/account/devices') {
DevicesController::index();
exit();
}
if ($path === '/account/devices/revoke') {
DevicesController::revoke();
exit();
}
if ($path === '/account/devices/revoke-all') {
DevicesController::revokeAll();
exit();
}
if ($path === '/agenda/feed/regenerate') {
AgendaController::regenerateFeed();
exit();
}
if ($path === '/agenda/feed/revoke') {
AgendaController::revokeFeed();
exit();
}
if ($path === '/' && !PermissionService::can('dashboard')) {
header('Location: ' . PermissionService::firstAllowedPath());
exit();
}
PermissionService::enforce($path, (string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
if (str_starts_with($path, '/dev/')) {
$remote = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
if (getenv('APP_ENV') !== 'development' || !in_array($remote, ['127.0.0.1', '::1'], true) || !Auth::is('admin')) {
http_response_code(404);
echo 'Page introuvable.';
exit();
}
}
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
if (!Auth::validCsrf($_POST['csrf'] ?? null)) {
http_response_code(419);
echo 'Session expirée. Rechargez la page puis réessayez.';
exit();
}
if (!Auth::canWrite($path)) {
http_response_code(403);
echo 'Votre rôle ne permet pas cette action.';
exit();
}
if (!in_array($path, ['/settings/users/save', '/settings/backups/restore'], true)) {
$entityId = (int) ($_POST['animal_id'] ?? ($_POST['id'] ?? 0)) ?: null;
$fields = implode(',', array_keys($_POST));
register_shutdown_function(static function () use ($path, $entityId, $fields): void {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
return;
}
$code = http_response_code();
if ($code === false || $code < 400) {
AuditService::log('post', $path, 'Action effectuée', $entityId ? 'animal' : null, $entityId, [
'fields' => $fields,
]);
}
});
}
}
require __DIR__ . '/../app/Routes/web.php';