diff --git a/TODO.md b/TODO.md index 31a8979..6cf560d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,13 @@ # TODO ## Done +- [x] Split search logic into search.php + - [x] `public/search.php` — new page for text-query search results + - [x] `public/repertoire.php` — stripped to répertoire index only + - [x] `SearchController::handle()` split into `handleSearch()` + `handleRepertoire()` + - [x] Search bar (`header.php`, `search-bar.php`) now POSTs to `/search.php` + - [x] `tfe.php` `?query=` links updated to `/search.php` + - [x] Filter links (`?or[]=`, `?ap[]=`, `?fy[]=`, `?kw[]=`) stay on `repertoire.php` - [x] TFE metadata values are hyperlinks to repertoire.php with correct filter/search params - orientation → `or[]`, ap_program → `ap[]`, year → `fy[]`, keywords → `kw[]` (per keyword) - languages, formats → `query=` (text search); jury members → `query=` (text search) diff --git a/public/repertoire.php b/public/repertoire.php index f23d16b..d2058ea 100644 --- a/public/repertoire.php +++ b/public/repertoire.php @@ -5,91 +5,17 @@ require_once APP_ROOT . '/src/SearchController.php'; // Build controller (performs rate-limit check; exits with HTTP 429 if exceeded) $ctrl = SearchController::create(); -// Collect all view variables (may exit early if HTMX partial request) -extract($ctrl->handle()); +// Collect all view variables for the répertoire index page +extract($ctrl->handleRepertoire()); ?> - -
- - - - - - -
- - - - - - - - - - Réinitialiser -
- -
- résultat 1 ? 's' : '' ?> - - - - - - - -

Aucun résultat pour cette recherche.

- -
- - -

Répertoire

- diff --git a/public/search.php b/public/search.php new file mode 100644 index 0000000..229e128 --- /dev/null +++ b/public/search.php @@ -0,0 +1,82 @@ +handleSearch()); +?> + + + + +
+ + + +
+ + + + + + + + + + Réinitialiser +
+ +
+ résultat 1 ? 's' : '' ?> + + + + + + + +

Aucun résultat pour cette recherche.

+ +
+ + diff --git a/public/tfe.php b/public/tfe.php index afe90ef..f429d38 100644 --- a/public/tfe.php +++ b/public/tfe.php @@ -53,7 +53,7 @@ extract($ctrl->handle());
Langue :
'' . htmlspecialchars($l) . '', $langs); + $langLinks = array_map(fn($l) => '' . htmlspecialchars($l) . '', $langs); echo implode(', ', $langLinks); ?>
@@ -64,7 +64,7 @@ extract($ctrl->handle());
Format :
'' . htmlspecialchars($f) . '', $fmts); + $fmtLinks = array_map(fn($f) => '' . htmlspecialchars($f) . '', $fmts); echo implode(', ', $fmtLinks); ?>
@@ -92,7 +92,7 @@ extract($ctrl->handle());
Promoteur·ice interne :
'' . htmlspecialchars($n) . '', $promoteursInternes); + $links = array_map(fn($n) => '' . htmlspecialchars($n) . '', $promoteursInternes); echo implode(', ', $links); ?>
@@ -102,7 +102,7 @@ extract($ctrl->handle());
Promoteur·ice externe :
'' . htmlspecialchars($n) . '', $promoteursExternes); + $links = array_map(fn($n) => '' . htmlspecialchars($n) . '', $promoteursExternes); echo implode(', ', $links); ?>
@@ -112,7 +112,7 @@ extract($ctrl->handle());
Président·e du jury :
'' . htmlspecialchars($n) . '', $juryPresidents); + $links = array_map(fn($n) => '' . htmlspecialchars($n) . '', $juryPresidents); echo implode(', ', $links); ?>
@@ -122,7 +122,7 @@ extract($ctrl->handle());
Lecteur·ices :
'' . htmlspecialchars($n) . '', $juryLecteurs); + $links = array_map(fn($n) => '' . htmlspecialchars($n) . '', $juryLecteurs); echo implode(', ', $links); ?>
diff --git a/src/SearchController.php b/src/SearchController.php index 7820204..a474ff6 100644 --- a/src/SearchController.php +++ b/src/SearchController.php @@ -2,9 +2,11 @@ /** * SearchController * - * Handles all data-fetching logic for the public search / répertoire page. - * The entry point (public/repertoire.php) delegates to this class and receives - * a plain array of view variables ready for template inclusion. + * Handles all data-fetching logic for the public search and répertoire pages. + * + * Entry points: + * - public/search.php calls handleSearch() — text-query results + * - public/repertoire.php calls handleRepertoire() — filter index + HTMX swaps * * Responsibilities: * - Rate-limit enforcement (returns early HTTP 429 response when needed) @@ -13,8 +15,8 @@ * - OG / meta tag assembly * - HTMX partial response for repertoire filter swaps * - * The class has NO output side-effects; all template rendering stays in - * public/repertoire.php so the view layer remains easy to inspect and modify. + * The class has NO output side-effects; all template rendering stays in the + * respective public/*.php files so the view layer remains easy to inspect. * Exception: renderRepertoirePartial() exits early for HTMX requests. */ class SearchController @@ -59,47 +61,93 @@ class SearchController return new self(Database::getInstance(), $rateLimit); } - // ── Main entry point ───────────────────────────────────────────────────── + // ── Entry points ────────────────────────────────────────────────────────── /** - * Process the current request and return all variables needed by the view. + * Handle the search results page (public/search.php). + * Requires a ?query= parameter; always returns search-result view variables. * * @return array */ - public function handle(): array + public function handleSearch(): array { - $isHtmx = !empty($_SERVER['HTTP_HX_REQUEST']); - $searchParams = $this->collectSearchParams(); - $hasSearch = !empty($searchParams); - - $activeFilters = $this->collectFilterParams(); - + $searchParams = $this->collectSearchParams(); $page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1; $offset = ($page - 1) * self::ITEMS_PER_PAGE; $validationError = null; - $results = []; - $totalItems = 0; - $totalPages = 0; - $repData = null; - - // For search filter dropdowns (text search mode only) + $results = []; + $totalItems = 0; + $totalPages = 0; $years = []; $orientations = []; $apPrograms = []; try { - if ($hasSearch) { - $results = $this->db->searchTheses($searchParams, self::ITEMS_PER_PAGE, $offset); - $totalItems = $this->db->countSearchResults($searchParams); - $totalPages = (int) ceil($totalItems / self::ITEMS_PER_PAGE); - $years = $this->db->getAvailableYears(); - $orientations = $this->db->getAllOrientations(); - $apPrograms = $this->db->getAllAPPrograms(); - } else { - // Repertoire index: compute filter data (all columns + matched flags) - $repData = $this->db->getRepertoireFilterData($activeFilters); - } + $results = $this->db->searchTheses($searchParams, self::ITEMS_PER_PAGE, $offset); + $totalItems = $this->db->countSearchResults($searchParams); + $totalPages = (int) ceil($totalItems / self::ITEMS_PER_PAGE); + $years = $this->db->getAvailableYears(); + $orientations = $this->db->getAllOrientations(); + $apPrograms = $this->db->getAllAPPrograms(); + } catch (InvalidArgumentException $e) { + $validationError = $e->getMessage(); + } catch (Exception $e) { + error_log('SearchController: ' . $e->getMessage()); + $validationError = 'Une erreur est survenue.'; + } + + // Preserve all active params, strip 'page' (pagination partial adds it) + $baseParams = array_diff_key($_GET, ['page' => '']); + + $query = $_GET['query'] ?? ''; + + return [ + 'searchParams' => $searchParams, + 'page' => $page, + 'totalItems' => $totalItems, + 'totalPages' => $totalPages, + 'results' => $results, + 'validationError' => $validationError, + 'baseParams' => $baseParams, + + // Filter dropdowns + 'years' => $years, + 'orientations' => $orientations, + 'apPrograms' => $apPrograms, + + // Page meta + 'searchBarValue' => $query, + 'pageTitle' => $query !== '' ? 'Recherche : ' . $query . ' – Posterg' : 'Recherche – Posterg', + 'metaDescription' => "Résultats de recherche dans le répertoire des TFE de l'erg.", + 'ogTags' => [ + 'type' => 'website', + 'title' => 'Recherche – Posterg', + 'description' => "Résultats de recherche dans le répertoire des TFE de l'erg.", + 'url' => 'https://posterg.erg.be/search.php', + 'site_name' => 'Posterg – ERG', + ], + 'currentNav' => 'repertoire', + 'extraCss' => ['/assets/css/search.css'], + 'bodyClass' => 'search-body', + ]; + } + + /** + * Handle the répertoire index page (public/repertoire.php). + * Serves the filter-column index; HTMX partial swaps are handled here too. + * + * @return array + */ + public function handleRepertoire(): array + { + $isHtmx = !empty($_SERVER['HTTP_HX_REQUEST']); + $activeFilters = $this->collectFilterParams(); + $repData = null; + $validationError = null; + + try { + $repData = $this->db->getRepertoireFilterData($activeFilters); } catch (InvalidArgumentException $e) { $validationError = $e->getMessage(); } catch (Exception $e) { @@ -108,36 +156,18 @@ class SearchController } // HTMX partial: render just the index div and exit - if ($isHtmx && !$hasSearch && $repData !== null) { + if ($isHtmx && $repData !== null) { $this->renderRepertoirePartial($repData, $activeFilters); } - // Preserve all active search/filter params, strip 'page' (pagination partial adds it) - $baseParams = array_diff_key($_GET, ['page' => '']); - return [ - // Search state - 'searchParams' => $searchParams, - 'hasSearch' => $hasSearch, - 'page' => $page, - 'totalItems' => $totalItems, - 'totalPages' => $totalPages, - 'results' => $results, - 'validationError' => $validationError, - 'baseParams' => $baseParams, - - // Repertoire filter state 'repData' => $repData, 'activeFilters' => $activeFilters, 'isHtmx' => $isHtmx, - - // Search filter dropdowns (text search mode only) - 'years' => $years, - 'orientations' => $orientations, - 'apPrograms' => $apPrograms, + 'validationError' => $validationError, // Page meta - 'searchBarValue' => $_GET['query'] ?? '', + 'searchBarValue' => '', 'pageTitle' => 'Répertoire – Posterg', 'metaDescription' => "Parcourez le répertoire des mémoires de fin d'études (TFE) de l'erg – École de Recherches Graphiques de Bruxelles.", 'ogTags' => [ diff --git a/templates/header.php b/templates/header.php index 237a187..abe1ae8 100644 --- a/templates/header.php +++ b/templates/header.php @@ -61,7 +61,7 @@ $_thesisId = $_GET['id'] ?? null; $searchBarValue = $searchBarValue ?? $_GET['query'] ?? ''; ?>
-
-