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 peuvent rester cacheables avec le TTL demandé. // Si un utilisateur est connecté, le layout dépend de la session // (navigation d'admin, déconnexion + 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, ]); // Mémoriser en session la valeur exposée à @CSRF pour que les // formulaires rendus pendant cette réponse puissent être vérifiés // lors du POST suivant par verifyCsrf(). $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'); } // La classe Session de F3 expose la valeur courante via « CSRF ». // Au rendu, on la recopie en SESSION.csrf ; le formulaire renvoie // ensuite le jeton affiché lors du rendu précédent, qu'on compare à // la valeur mémorisée 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; } }