Clean code
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class AssetController extends BaseController
|
||||
class AssetController
|
||||
{
|
||||
private const ALLOWED = [
|
||||
'app.css' => 'text/css',
|
||||
@@ -11,13 +11,15 @@ class AssetController extends BaseController
|
||||
|
||||
public function serve(): void
|
||||
{
|
||||
$file = basename((string) $this->f3->get('PARAMS.file'));
|
||||
$f3 = Base::instance();
|
||||
$file = basename((string) $f3->get('PARAMS.file'));
|
||||
|
||||
if (!array_key_exists($file, self::ALLOWED)) {
|
||||
$this->f3->error(404);
|
||||
$f3->error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$f3->expire(86400);
|
||||
echo Web::instance()->minify(
|
||||
$file,
|
||||
self::ALLOWED[$file],
|
||||
|
||||
@@ -4,11 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
public function beforeRoute(): void
|
||||
{
|
||||
$this->disableCache();
|
||||
}
|
||||
|
||||
public function show(): void
|
||||
{
|
||||
if ($this->currentUser() !== null) {
|
||||
@@ -16,7 +11,7 @@ class AuthController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderSession('auth/login.html', ['pageTitle' => 'Connexion'], true);
|
||||
$this->render('auth/login.html', ['pageTitle' => 'Connexion']);
|
||||
}
|
||||
|
||||
public function login(): void
|
||||
@@ -36,7 +31,6 @@ class AuthController extends BaseController
|
||||
|
||||
session_regenerate_id(true);
|
||||
$this->f3->set('SESSION.user_id', $user['id']);
|
||||
$this->refreshCsrfToken();
|
||||
$this->flash('success', 'Connexion réussie.');
|
||||
$this->f3->reroute('@dashboard');
|
||||
}
|
||||
@@ -45,7 +39,6 @@ class AuthController extends BaseController
|
||||
{
|
||||
$this->verifyCsrf();
|
||||
$this->f3->clear('SESSION.user_id');
|
||||
$this->f3->clear('SESSION.csrf_token');
|
||||
session_regenerate_id(true);
|
||||
$this->flash('success', 'Déconnexion effectuée.');
|
||||
$this->f3->reroute('@login');
|
||||
|
||||
@@ -7,46 +7,51 @@ abstract class BaseController
|
||||
protected Base $f3;
|
||||
protected DB\SQL $db;
|
||||
|
||||
private ?array $resolvedUser = null;
|
||||
private bool $userResolved = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->f3 = Base::instance();
|
||||
$this->db = $this->f3->get('DB');
|
||||
}
|
||||
|
||||
protected function renderPublic(string $view, array $data = []): void
|
||||
protected function render(string $view, array $data = [], int $cacheTtl = 0): void
|
||||
{
|
||||
$user = $this->currentUser();
|
||||
|
||||
$this->f3->mset($data + [
|
||||
'view' => $view,
|
||||
'currentUser' => $user,
|
||||
'flash' => array_key_exists('flash', $data) && is_array($data['flash']) ? $data['flash'] : null,
|
||||
'csrfToken' => $user !== null ? $this->csrfToken() : null,
|
||||
]);
|
||||
// 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);
|
||||
|
||||
echo Template::instance()->render('layout.html');
|
||||
}
|
||||
|
||||
protected function renderSession(string $view, array $data = [], bool $withCsrf = false): void
|
||||
{
|
||||
$flash = array_key_exists('flash', $data) && is_array($data['flash'])
|
||||
? $data['flash']
|
||||
: $this->pullFlash();
|
||||
|
||||
$this->f3->mset($data + [
|
||||
'view' => $view,
|
||||
'currentUser' => $this->currentUser(),
|
||||
'currentUser' => $user,
|
||||
'flash' => $flash,
|
||||
'csrfToken' => $withCsrf ? $this->csrfToken() : null,
|
||||
'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
|
||||
{
|
||||
$userId = (int) ($this->f3->get('SESSION.user_id') ?? 0);
|
||||
return $userId > 0 ? (new User($this->db))->findById($userId) : null;
|
||||
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
|
||||
@@ -59,33 +64,14 @@ abstract class BaseController
|
||||
$this->f3->reroute('@login');
|
||||
}
|
||||
|
||||
protected function disableCache(): void
|
||||
{
|
||||
$this->f3->expire(0);
|
||||
}
|
||||
|
||||
protected function csrfToken(): string
|
||||
{
|
||||
$token = (string) ($this->f3->get('SESSION.csrf_token') ?? '');
|
||||
if ($token === '') {
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$this->f3->set('SESSION.csrf_token', $token);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
protected function refreshCsrfToken(): string
|
||||
{
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$this->f3->set('SESSION.csrf_token', $token);
|
||||
return $token;
|
||||
}
|
||||
|
||||
// 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_token') ?? '');
|
||||
$expected = (string) ($this->f3->get('SESSION.csrf') ?? '');
|
||||
|
||||
if ($submitted !== '' && $expected !== '' && hash_equals($expected, $submitted)) {
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,6 @@ class DashboardController extends BaseController
|
||||
public function beforeRoute(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$this->disableCache();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
@@ -15,11 +14,11 @@ class DashboardController extends BaseController
|
||||
$page = max(1, (int) ($this->f3->get('GET.page') ?? 1));
|
||||
$result = (new Post($this->db))->paginateList($page, 24);
|
||||
|
||||
$this->renderSession('admin/dashboard.html', [
|
||||
$this->render('admin/dashboard.html', [
|
||||
'pageTitle' => 'Tableau de bord',
|
||||
'posts' => $result['posts'],
|
||||
'pagination' => $result,
|
||||
'paginationAlias' => 'dashboard',
|
||||
], true);
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ declare(strict_types=1);
|
||||
class MediaController extends BaseController
|
||||
{
|
||||
private const UPLOAD_MAX_BYTES = 10 * 1024 * 1024; // 10 Mo
|
||||
private const ACCEPTED_TYPES = ['image/jpeg', 'image/png', 'image/webp'];
|
||||
private const PER_PAGE = 24;
|
||||
|
||||
public function beforeRoute(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$this->disableCache();
|
||||
}
|
||||
|
||||
public function index(): void
|
||||
@@ -18,12 +18,12 @@ class MediaController extends BaseController
|
||||
$page = max(1, (int) ($this->f3->get('GET.page') ?? 1));
|
||||
$result = (new Media($this->db))->paginateLibrary($page, self::PER_PAGE);
|
||||
|
||||
$this->renderSession('admin/media.html', [
|
||||
$this->render('admin/media.html', [
|
||||
'pageTitle' => 'Médiathèque',
|
||||
'items' => $result['items'],
|
||||
'pagination' => $result,
|
||||
'paginationAlias' => 'media_index',
|
||||
], true);
|
||||
]);
|
||||
}
|
||||
|
||||
public function upload(): void
|
||||
@@ -36,7 +36,8 @@ class MediaController extends BaseController
|
||||
|
||||
$received = Web::instance()->receive(
|
||||
fn(array $file): bool => (int) ($file['size'] ?? 0) > 0
|
||||
&& (int) ($file['size'] ?? 0) <= self::UPLOAD_MAX_BYTES,
|
||||
&& (int) ($file['size'] ?? 0) <= self::UPLOAD_MAX_BYTES
|
||||
&& in_array($file['type'] ?? '', self::ACCEPTED_TYPES, true),
|
||||
overwrite: false,
|
||||
slug: true
|
||||
);
|
||||
|
||||
@@ -9,7 +9,6 @@ class PostController extends BaseController
|
||||
public function beforeRoute(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$this->disableCache();
|
||||
}
|
||||
|
||||
public function create(): void
|
||||
@@ -25,7 +24,6 @@ class PostController extends BaseController
|
||||
|
||||
try {
|
||||
(new Post($this->db))->create($input);
|
||||
Cache::instance()->reset('.url'); // invalide le cache des pages publiques
|
||||
$this->flash('success', 'Article créé.');
|
||||
$this->f3->reroute('@dashboard');
|
||||
} catch (RuntimeException $e) {
|
||||
@@ -58,7 +56,6 @@ class PostController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
Cache::instance()->reset('.url'); // invalide le cache des pages publiques
|
||||
$this->flash('success', 'Article mis à jour.');
|
||||
$this->f3->reroute('@dashboard');
|
||||
} catch (RuntimeException $e) {
|
||||
@@ -69,9 +66,14 @@ class PostController extends BaseController
|
||||
public function delete(): void
|
||||
{
|
||||
$this->verifyCsrf();
|
||||
(new Post($this->db))->delete((int) $this->f3->get('PARAMS.id'));
|
||||
Cache::instance()->reset('.url'); // invalide le cache des pages publiques
|
||||
$this->flash('success', 'Article supprimé.');
|
||||
|
||||
try {
|
||||
(new Post($this->db))->delete((int) $this->f3->get('PARAMS.id'));
|
||||
$this->flash('success', 'Article supprimé.');
|
||||
} catch (RuntimeException $e) {
|
||||
$this->flash('error', $e->getMessage());
|
||||
}
|
||||
|
||||
$this->f3->reroute('@dashboard');
|
||||
}
|
||||
|
||||
@@ -84,10 +86,10 @@ class PostController extends BaseController
|
||||
|
||||
$media = new Media($this->db);
|
||||
$mediaItems = $media->latest(self::MEDIA_PICKER_LIMIT);
|
||||
$mediaCount = $media->countAll();
|
||||
$mediaCount = $media->count();
|
||||
$flash = $error !== null ? ['type' => 'error', 'message' => $error] : null;
|
||||
|
||||
$this->renderSession('admin/post_form.html', [
|
||||
$this->render('admin/post_form.html', [
|
||||
'pageTitle' => $pageTitle,
|
||||
'formAction' => $formAction,
|
||||
'post' => $post,
|
||||
@@ -99,7 +101,7 @@ class PostController extends BaseController
|
||||
'titleMax' => Post::TITLE_MAX_LENGTH,
|
||||
'excerptMax' => Post::EXCERPT_MAX_LENGTH,
|
||||
'flash' => $flash,
|
||||
], true);
|
||||
]);
|
||||
}
|
||||
|
||||
private function postInput(): array
|
||||
|
||||
@@ -9,12 +9,12 @@ class SiteController extends BaseController
|
||||
$page = max(1, (int) ($this->f3->get('GET.page') ?? 1));
|
||||
$result = (new Post($this->db))->paginateList($page);
|
||||
|
||||
$this->renderPublic('site/home.html', [
|
||||
$this->render('site/home.html', [
|
||||
'pageTitle' => 'Accueil',
|
||||
'posts' => $result['posts'],
|
||||
'pagination' => $result,
|
||||
'paginationAlias' => 'home',
|
||||
]);
|
||||
], 300);
|
||||
}
|
||||
|
||||
public function show(): void
|
||||
@@ -25,9 +25,10 @@ class SiteController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
$this->renderPublic('site/post.html', [
|
||||
$this->render('site/post.html', [
|
||||
'pageTitle' => $post['title'],
|
||||
'metaDescription' => $post['excerpt'],
|
||||
'post' => $post,
|
||||
]);
|
||||
], 3600);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user