f3 = Base::instance(); $this->db = $this->f3->get('DB'); } protected function render(string $view, array $data = [], int $cacheTtl = 0): void { $user = $this->currentUser(); // Les pages publiques émettent un Cache-Control avec le TTL demandé. // Un utilisateur connecté voit un état de session (nav, CSRF) : // on force expire(0) pour ne pas servir ce rendu à d'autres visiteurs. $this->f3->expire($user !== null ? 0 : $cacheTtl); $flash = array_key_exists('flash', $data) && is_array($data['flash']) ? $data['flash'] : $this->pullFlash(); $this->f3->mset($data + [ 'view' => $view, 'currentUser' => $user, 'flash' => $flash, 'metaDescription' => null, ]); // Persister le jeton CSRF courant en session : le formulaire // affiche @CSRF (jeton de cette requête) ; à la soumission, // verifyCsrf() le comparera à SESSION.csrf. $this->f3->copy('CSRF', 'SESSION.csrf'); echo Template::instance()->render('layout.html'); } protected function currentUser(): ?array { if (!$this->userResolved) { $userId = (int) ($this->f3->get('SESSION.user_id') ?? 0); $this->resolvedUser = $userId > 0 ? (new User($this->db))->findById($userId) : null; $this->userResolved = true; } return $this->resolvedUser; } protected function requireAuth(): void { if ($this->currentUser() !== null) { return; } $this->flash('error', 'Connecte-toi pour continuer.'); $this->f3->reroute('@login'); } // Le jeton CSRF est fourni par la classe Session de F3 // (clé de ruche « CSRF », copiée en SESSION.csrf à chaque requête). // Le formulaire envoie le jeton affiché lors du GET précédent, // qu'on compare au jeton sauvegardé en session. protected function verifyCsrf(): void { $submitted = (string) ($this->f3->get('POST.csrf_token') ?? ''); $expected = (string) ($this->f3->get('SESSION.csrf') ?? ''); if ($submitted !== '' && $expected !== '' && hash_equals($expected, $submitted)) { return; } $this->f3->error(400, 'Jeton CSRF invalide.'); } protected function flash(string $type, string $message): void { $this->f3->set('SESSION.flash', ['type' => $type, 'message' => $message]); } private function pullFlash(): ?array { $flash = $this->f3->get('SESSION.flash'); $this->f3->clear('SESSION.flash'); return is_array($flash) ? $flash : null; } }