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());
?>
-
-
⚠ = htmlspecialchars($validationError) ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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());
+?>
+
+
+
+
+ ⚠ = htmlspecialchars($validationError) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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());
@@ -102,7 +102,7 @@ extract($ctrl->handle());
@@ -112,7 +112,7 @@ extract($ctrl->handle());
@@ -122,7 +122,7 @@ extract($ctrl->handle());
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'] ?? '';
?>