# XAMXAM TODO ## Completed - [x] TDD analysis + new test suites - [x] **Bug fixed**: `SearchController::handleSearch()` — `$coverMap` undefined variable + never populated for search results - [x] `ShareLinkTest` (13 tests) — `generateSlug`, all `validateLink` branches, `verifyPassword`, `incrementUsage`, `objet_restriction` - [x] `PureLogicTest` (31 tests) — `TfeController` helpers (meta, OG image, jury split, captions), `ThesisCreateController` helpers (autofocus, detectFileType, authorSlug), `ThesisEditController::buildFileSizeInfo`, `ExportController` CSV column consistency, `SearchController` coverMap regression - [x] Private helpers promoted to `protected` in `TfeController`, `ThesisCreateController`, `ThesisEditController` to enable subclass-based testing without reflection - [x] Form save audit + TDD - [x] `createThesis()` missing `duration_pages`/`duration_minutes` columns — fixed - [x] `ThesisCreateController` not passing raw page/minute values to `createThesis()` — fixed (`durationPages`, `durationMinutes` extracted and passed) - [x] `FormSaveTest.php` — 14 red-green tests covering create+edit round-trips for all fields - [x] Language form improvements - [x] Add Néerlandais as default language option (schema + migration 017) - [x] `language_autre` conditionally required via HTMX fragment (replaced custom JS) - [x] `language_autre` saved via `getOrCreateLanguage()` in both create and edit controllers - [x] `formData['languages']` wired in edit.php so checkboxes are pre-checked - [x] `duration_pages`/`duration_minutes` saved in `updateThesis()` and read back in `getThesisRawFields()` - [x] `beforeunload-guard` applied to add and partage forms too - [x] Audit + fix direct PHP URL references blocked by nginx catch-all `deny all` - [x] `/request-access.php` fetch in `tfe.php` → `/request-access` - [x] `/media.php?path=` in `form.php` (×2) and `admin/recapitulatif.php` → `/media?path=` - [x] Fix 403 on `/language-autre-fragment.php` from `edit.php` - [x] Root cause: standalone root-level PHP file blocked by nginx catch-all `deny all` - [x] Moved logic to `partage/language-autre-fragment.php` (shared include) - [x] Added route `/partage/language-autre-fragment` in `partage/index.php` - [x] Added `admin/language-autre-fragment.php` (AdminAuth gated, includes shared logic) - [x] `form.php` picks URL based on `$mode` (`partage` vs admin) - [x] Deleted `public/language-autre-fragment.php`; nginx unchanged - [x] Merge banner images into cover images - [x] Migration 016: copy `storage/banners/*` → `storage/covers/`, insert `thesis_files` cover records, clear `banner_path`, remove banners dir - [x] Remove banner fieldset from edit form (`form.php`) - [x] Remove banner fieldset from student submission form (`fieldset-files.php`: rename to couverture) - [x] Update `ThesisEditController::save()` — remove banner upload/removal logic - [x] Update `ThesisCreateController::submit()` — remove `handleBannerUpload` call - [x] Update `Database::handleCoverUpload()` — add webp support, raise limit to 20 MB - [x] Remove `Database::setBannerPath()`, `handleBannerUpload()`, `getThesisBannerPath()` - [x] Update `Database::deleteThesis()` / `bulkDeleteTheses()` — remove banner file cleanup - [x] `HomeController`: batch-load covers for all items, remove banner_path fallback - [x] `SearchController::handleSearch()`: batch-load covers, pass `$coverMap` to view - [x] `SearchController::handleStudentPreview()`: load covers, pass `$coverMap` to partial - [x] `TfeController::resolveOgImage()`: use cover file_type instead of banner_path - [x] `home.php`: use only `$coverMap` (no banner_path fallback) - [x] `search.php`: show cover thumbnail on result cards - [x] `student-preview.php`: use `$coverMap` instead of `banner_path` - [x] Migration applied and file moved to `applied/`