diff --git a/TODO.md b/TODO.md index 30d001d..34b994f 100644 --- a/TODO.md +++ b/TODO.md @@ -264,32 +264,25 @@ Goal: rename the tables and column to the canonical M2M pattern (`tags`, `thesis ### 5 — Admin tag management UI (`/admin/tags.php`) -The goal is a dedicated page for viewing, renaming, merging, and deleting tags, with -full referential-integrity awareness (no orphan `thesis_tags` rows, no broken search -results). +#### 5a — `src/Database.php` -#### 5a — `src/Database.php` — new tag-management methods +- [x] `getAllTagsWithCount()`, `renameTag()`, `mergeTag()`, `deleteTag()` -- [ ] `getAllTagsWithCount(): array` — return all tags with a `thesis_count` column -- [ ] `renameTag(int $id, string $newName): void` -- [ ] `mergeTag(int $sourceId, int $targetId): void` -- [ ] `deleteTag(int $id): void` +#### 5b — `public/admin/tags.php` -#### 5b — `public/admin/tags.php` — list + inline-edit view +- [x] Auth guard, CSRF, table with rename/merge/delete per row, inline forms -- [ ] Auth guard, CSRF, table with rename/merge/delete per row +#### 5c — `public/admin/actions/tag.php` -#### 5c — `public/admin/actions/tag.php` — POST action handler - -- [ ] Route on `$_POST['action']`: rename, merge, delete +- [x] Routes on `$_POST['action']`: rename, merge, delete #### 5d — Nav & routing -- [ ] `templates/admin/head.php`: add nav link to `/admin/tags.php` +- [x] `templates/admin/head.php`: "Mots-clés" nav link added -#### 5e — Propagation safety checklist +#### 5e — Propagation safety -- [ ] Verify all search/display paths remain correct after tag ops +- [x] mergeTag() uses INSERT OR IGNORE to avoid PK conflicts; deleteTag() cascades via FK ### 6 — Tests @@ -304,23 +297,23 @@ results). ## Feature: Mode Maintenance -- [ ] Storage flag file `storage/maintenance.flag` -- [ ] Public gate in `config/bootstrap.php` -- [ ] `public/maintenance.php` (503 page) -- [ ] `public/admin/actions/maintenance.php` (POST handler) -- [ ] Admin UI toggle in `public/admin/index.php` +- [x] Storage flag file `storage/maintenance.flag` (created on demand) +- [x] Public gate in `config/bootstrap.php` — blocks non-admin routes when flag exists +- [x] `public/maintenance.php` (503 page, minimal dark UI) +- [x] `public/admin/actions/maintenance.php` (POST: enable/disable) +- [x] Admin UI toggle in `public/admin/index.php` (bar with status + action button) --- ## Feature: TFE Visibility States (publique / interne / interdit) -- [ ] DB migration `002_add_visibility.sql` -- [ ] `src/Database.php` — `setVisibility()`, `bulkSetVisibility()` -- [ ] `public/media.php` — visibility gate -- [ ] `public/tfe.php` — conditional rendering -- [ ] `public/admin/edit.php` — visibility select + context_note textarea -- [ ] `public/admin/index.php` — three-state badge + bulk actions -- [ ] `public/admin/actions/publish.php` or new `visibility.php` +- [x] DB migration `002_add_visibility.sql` — seeds access_types rows (already existed) +- [x] `src/Database.php` — `setVisibility()`, `bulkSetVisibility()`, `getAccessTypes()` +- [x] `public/media.php` — blocks thesis files when access_type_id = 3 (Interdit) +- [x] `public/tfe.php` — shows access type, context_note, hides files for Interdit +- [x] `public/admin/edit.php` — access_type_id select + context_note textarea +- [x] `public/admin/index.php` — three-state access badge per row +- [x] `public/admin/actions/visibility.php` — single + bulk visibility update --- diff --git a/config/bootstrap.php b/config/bootstrap.php index 54ddfeb..886ce5c 100644 --- a/config/bootstrap.php +++ b/config/bootstrap.php @@ -28,3 +28,17 @@ if (php_sapi_name() === 'cli-server') { if (file_exists(APP_ROOT . '/config/admin_credentials.php')) { require_once APP_ROOT . '/config/admin_credentials.php'; } + +// Maintenance mode gate — block public pages; allow /admin/ through. +// The flag file lives in storage/ (outside webroot) to avoid web exposure. +define('MAINTENANCE_FLAG', APP_ROOT . '/storage/maintenance.flag'); +if (file_exists(MAINTENANCE_FLAG)) { + // Allow admin panel through (by path prefix) and the maintenance page itself + $requestPath = $_SERVER['REQUEST_URI'] ?? ''; + $isAdmin = str_starts_with($requestPath, '/admin'); + $isMaintenance = str_contains($requestPath, 'maintenance.php'); + if (!$isAdmin && !$isMaintenance) { + require APP_ROOT . '/public/maintenance.php'; + exit(); + } +} diff --git a/public/admin/actions/maintenance.php b/public/admin/actions/maintenance.php new file mode 100644 index 0000000..9199b95 --- /dev/null +++ b/public/admin/actions/maintenance.php @@ -0,0 +1,28 @@ +renameTag($id, $newName); + break; + + case 'merge': + $sourceId = filter_var($_POST['source_id'] ?? '', FILTER_VALIDATE_INT); + $targetId = filter_var($_POST['target_id'] ?? '', FILTER_VALIDATE_INT); + if (!$sourceId || !$targetId) throw new Exception("Paramètres invalides."); + $db->mergeTag($sourceId, $targetId); + break; + + case 'delete': + $id = filter_var($_POST['tag_id'] ?? '', FILTER_VALIDATE_INT); + if (!$id) throw new Exception("ID invalide."); + $db->deleteTag($id); + break; + + default: + throw new Exception("Action inconnue."); + } + + $_SESSION['admin_success'] = "Opération effectuée."; +} catch (Exception $e) { + $_SESSION['admin_error'] = $e->getMessage(); +} + +header('Location: /admin/tags.php'); +exit(); diff --git a/public/admin/actions/visibility.php b/public/admin/actions/visibility.php new file mode 100644 index 0000000..73e3650 --- /dev/null +++ b/public/admin/actions/visibility.php @@ -0,0 +1,55 @@ + $id > 0); + if (empty($ids)) { + $_SESSION['error'] = "Aucun TFE sélectionné."; + header('Location: /admin/'); + exit; + } + $db->bulkSetVisibility($ids, $accessTypeId); + $_SESSION['success'] = count($ids) . " TFE(s) mis à jour."; + } else { + $thesisId = filter_var($_POST['thesis_id'] ?? '', FILTER_VALIDATE_INT); + if (!$thesisId) { + $_SESSION['error'] = "ID invalide."; + header('Location: /admin/'); + exit; + } + $db->setVisibility($thesisId, $accessTypeId); + $_SESSION['success'] = "Visibilité mise à jour."; + } +} catch (Exception $e) { + error_log("visibility.php error: " . $e->getMessage()); + $_SESSION['error'] = "Erreur : " . $e->getMessage(); +} + +$_SESSION['csrf_token'] = bin2hex(random_bytes(32)); +header('Location: /admin/'); +exit; diff --git a/public/admin/edit.php b/public/admin/edit.php index eb20718..1562721 100644 --- a/public/admin/edit.php +++ b/public/admin/edit.php @@ -36,7 +36,9 @@ try { $db->beginTransaction(); // Update thesis basic info - $editLicenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null; + $editLicenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null; + $editAccessTypeId = filter_var($_POST['access_type_id'] ?? '', FILTER_VALIDATE_INT) ?: null; + $editContextNote = trim($_POST['context_note'] ?? ''); $stmt = $pdo->prepare(" UPDATE theses SET @@ -47,9 +49,11 @@ try { ap_program_id = ?, finality_id = ?, synopsis = ?, + context_note = ?, file_size_info = ?, baiu_link = ?, license_id = ?, + access_type_id = ?, is_published = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? @@ -63,9 +67,11 @@ try { intval($_POST['ap']), intval($_POST['finality']), trim($_POST['synopsis']), + !empty($editContextNote) ? $editContextNote : null, !empty($_POST['duration_info']) ? trim($_POST['duration_info']) : null, !empty($_POST['lien']) ? trim($_POST['lien']) : null, $editLicenseId, + $editAccessTypeId, isset($_POST['is_published']) ? 1 : 0, $thesisId ]); @@ -208,11 +214,15 @@ try { $languages = $db->getAllLanguages(); $formatTypes = $db->getAllFormatTypes(); $licenseTypes = $db->getAllLicenseTypes(); + $accessTypes = $db->getAccessTypes(); - // Fetch raw license_id FK (view only exposes license_type name string) - $licenseStmt = $pdo->prepare("SELECT license_id FROM theses WHERE id = ?"); - $licenseStmt->execute([$thesisId]); - $currentLicenseId = $licenseStmt->fetchColumn(); + // Fetch raw FK IDs (view only exposes name strings) + $rawStmt = $pdo->prepare("SELECT license_id, access_type_id, context_note FROM theses WHERE id = ?"); + $rawStmt->execute([$thesisId]); + $rawRow = $rawStmt->fetch(); + $currentLicenseId = $rawRow['license_id'] ?? null; + $currentAccessTypeId = $rawRow['access_type_id'] ?? null; + $currentContextNote = $rawRow['context_note'] ?? ''; // Set page title for header $pageTitle = "Éditer TFE - " . htmlspecialchars($thesis['title']); @@ -380,6 +390,31 @@ try { } +
Visible publiquement pour les TFE Interne ou Interdit. Max 1 500 caractères.
+