diff --git a/TODO.md b/TODO.md index 0d5f3bb..282f9c4 100644 --- a/TODO.md +++ b/TODO.md @@ -203,6 +203,120 @@ These must remain unbroken after the tag-management UI is live: - [ ] `storage/fixtures/CreateTestDatabase.php`: update to use `tags` / `thesis_tags` table names and `findOrCreateTag()` +## Feature: Mode Maintenance + +Adds "Déployer" / "Maintenance" buttons in admin. When maintenance mode is on, +the public site is unavailable (503) while the admin section keeps working. + +### Implementation + +- [ ] **Storage** — store maintenance flag as a file on disk (`storage/maintenance.flag`) + so it requires no DB and survives restarts; flag presence = maintenance mode ON +- [ ] **Public gate** — add a check at the top of every public-facing PHP entry-point + (`public/index.php`, `public/search.php`, `public/tfe.php`, `public/media.php`, + `public/apropos.php`) via a shared helper in `config/bootstrap.php`: + `checkMaintenanceMode()` — if flag exists, render a 503 maintenance page and exit +- [ ] **Maintenance page** — create `public/maintenance.php` (503 HTML page, styled + minimally, not imported via bootstrap so it never recurses) +- [ ] **Admin action** — create `public/admin/actions/maintenance.php` (POST handler): + CSRF-validated, two actions: `enable` (touch flag file) and `disable` (unlink flag) +- [ ] **Admin UI** — add a "Site en ligne / Maintenance" status bar in + `public/admin/index.php` with "Déployer" (→ disable) and "Maintenance" (→ enable) + buttons; display current state clearly (green = en ligne, orange = maintenance) +- [ ] **Admin exemption** — admin pages (`/admin/*`) must NOT be gated; the maintenance + check must only run on public routes; admin bootstrap already bypasses it by + not calling `checkMaintenanceMode()` +- [ ] **`src/config.php`** — add `MAINTENANCE_FLAG_PATH` constant pointing to + `storage/maintenance.flag` + +## Feature: TFE Visibility States (publique / interne / interdit) + +Replaces the binary `is_published` with a three-state `visibility` field on each TFE, +controlling what the public site exposes. Default: `interne`. + +### States + +| State | DB value | Public page: metadata | Public page: files | Note displayed | +|------------|----------|-----------------------|--------------------|----------------| +| `publique` | `public` | ✓ full | ✓ via media.php | — | +| `interne` | `intern` | ✓ full | ✗ hidden | "Copies physiques disponibles aux archives de l'école." | +| `interdit` | `forbid` | ✓ full | ✗ hidden | `context_note` from DB (jury note, editable in admin) | + +### Schema + +- [ ] **DB migration** — `storage/migrations/002_add_visibility.sql`: + - `ALTER TABLE theses ADD COLUMN visibility TEXT NOT NULL DEFAULT 'intern'` + - `UPDATE theses SET visibility = 'public' WHERE is_published = 1` (migrate existing data) + - update `v_theses_full` view to expose `visibility` (DROP + CREATE in migration) + - update `v_theses_public` view: change filter from `is_published = 1` to + `is_published = 1` (keep; `is_published` stays for "show in listings" gate) + - **Note**: `is_published` keeps its meaning (appears in public listings at all); + `visibility` controls *what* is accessible once found; a TFE can be + `is_published = 1` + `visibility = 'intern'` → appears in list, files hidden +- [ ] **`storage/schema.sql`** — add `visibility` column + update views + add + `idx_theses_visibility` index +- [ ] **`storage/fixtures/CreateTestDatabase.php`** — seed some TFEs with each visibility + state (default: `intern`) + +### Backend + +- [ ] **`src/Database.php`**: + - `getThesisById($id)` — already used by `tfe.php`; ensure it returns `visibility` + and `context_note` + - `getThesis($id)` (admin) — already returns all columns; ensure `visibility` and + `context_note` pass through + - `getThesesList()` — add `visibility` to SELECT for admin list display + - `setVisibility(int $thesisId, string $state): void` — UPDATE + `theses SET visibility = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; + validate `$state` ∈ `['public', 'intern', 'forbid']` + - `bulkSetVisibility(array $ids, string $state): void` — batch UPDATE for the + bulk-action bar + +### Public site + +- [ ] **`public/media.php`** — add visibility check: + 1. Load `thesis_files` row by `path` to get `thesis_id` + 2. Load `theses.visibility` for that `thesis_id` (one extra query) + 3. If `visibility !== 'public'` → return 403 + (This is the critical security gate; all other changes are UX) +- [ ] **`public/tfe.php`** — conditional rendering based on `$data['visibility']`: + - `publique`: render files normally (existing logic) + - `interne`: skip files block; show notice "Les copies physiques de ce travail + sont disponibles dans les archives de l'école." + - `interdit`: skip files block; show `$data['context_note']` (jury note) if set, + otherwise a generic access-restricted message +- [ ] **`public/index.php`** / **`public/search.php`** — no change needed (thumbnail + logic: if `visibility !== 'public'`, skip thumbnail even if file exists; adjust + the thumbnail-lookup block in index.php) + +### Admin UI + +- [ ] **`public/admin/edit.php`**: + - Replace the current `is_published` checkbox with a `