Files
xamxam/public/search.php
Pontoporeia c352a392a1 search.php: semantic HTML overhaul of répertoire index and results view
- Replace 4x <div class="repertoire-col"> with <section>; remove
  .repertoire-col__header class, CSS now targets section > h2
- Wrap all index link groups in <ul>/<li>; delete the four per-column
  link classes (year-index-item, cat-index-item, student-index-item,
  keyword-index-item); active state switches from .active to
  aria-current="page" on the <a>
- Add <h1 class="sr-only">Répertoire</h1> so the index view has a
  page-level heading (WCAG 2.4.6)
- Remove redundant <div class="search-results-view"> wrapper; padding
  moved to .results-grid and .search-results-header directly
- Replace <div class="results-grid"> with <ul class="results-grid">;
  each result card becomes <li><a class="result-card">
- Replace <span class="result-card__meta"> with <small> (ancillary
  metadata per HTML spec)
- Replace result-count <p> with <output role="status"> (computed value)
- Replace 3x <div class="search-filter-group"><label>…</label><select>
  with <label> directly wrapping <select> (implicit association,
  removes .search-filter-group divs); CSS updated to display:flex on
  the label itself
- Pagination wrapper changed to <nav aria-label="Pagination">;
  page-info span gets aria-current="page"
- search.css: delete .search-results-view, four index-item classes,
  .cat-index-group, .search-filter-group; consolidate years/other
  column link styles under .repertoire-col:first-child ul a and
  .repertoire-col:not(:first-child) ul a selectors; add ul reset rule
2026-03-29 16:07:37 +02:00

298 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/../config/bootstrap.php';
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/RateLimit.php';
// Rate limiting
$rateLimit = new RateLimit(30, 60);
if (!$rateLimit->check()) {
http_response_code(429);
header('Retry-After: ' . $rateLimit->getResetTime());
$retrySeconds = (int)$rateLimit->getResetTime();
echo <<<HTML
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Trop de requêtes Posterg</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #0d0d0d;
color: #e0e0e0;
font-family: 'Helvetica Neue', Arial, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.box { max-width: 520px; text-align: center; }
.box__logo {
font-size: 1.1rem; font-weight: 700;
letter-spacing: .12em; text-transform: uppercase;
color: #fff; margin-bottom: 2.5rem;
}
.box__title { font-size: 1.6rem; font-weight: 300; margin-bottom: 1rem; }
.box__text { font-size: .95rem; color: #999; line-height: 1.7; }
</style>
</head>
<body>
<div class="box">
<div class="box__logo">POSTERG</div>
<h1 class="box__title">Trop de requêtes</h1>
<p class="box__text">Vous avez effectué trop de recherches en peu de temps.<br>
Réessayez dans {$retrySeconds} secondes.</p>
</div>
</body>
</html>
HTML;
exit;
}
$rateLimit->sendHeaders();
if (rand(1, 100) === 1) $rateLimit->cleanup();
// Collect search/filter params
$searchParams = [];
if (!empty($_GET['query'])) $searchParams['query'] = trim($_GET['query']);
if (!empty($_GET['year'])) $searchParams['year'] = intval($_GET['year']);
if (!empty($_GET['orientation'])) $searchParams['orientation'] = $_GET['orientation'];
if (!empty($_GET['ap_program'])) $searchParams['ap_program'] = $_GET['ap_program'];
if (!empty($_GET['keyword'])) $searchParams['keyword'] = $_GET['keyword'];
$hasSearch = !empty($searchParams);
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$itemsPerPage = 30;
$validationError = null;
try {
$db = Database::getInstance();
$offset = ($page - 1) * $itemsPerPage;
if ($hasSearch) {
$results = $db->searchTheses($searchParams, $itemsPerPage, $offset);
$totalItems = $db->countSearchResults($searchParams);
$totalPages = ceil($totalItems / $itemsPerPage);
} else {
$results = [];
$totalItems = 0;
$totalPages = 0;
}
$years = $db->getAvailableYears();
$orientations = $db->getAllOrientations();
$apPrograms = $db->getAllAPPrograms();
$keywords = $db->getUsedTags();
// Fetch id+authors only — lean query bypassing the fat v_theses_public view
$students = $db->getPublishedAuthors();
} catch (InvalidArgumentException $e) {
$validationError = $e->getMessage();
$results = []; $totalItems = 0; $totalPages = 0;
$years = []; $orientations = []; $apPrograms = []; $keywords = []; $students = [];
} catch (Exception $e) {
error_log("Search error: " . $e->getMessage());
$validationError = "Une erreur est survenue.";
$results = []; $totalItems = 0; $totalPages = 0;
$years = []; $orientations = []; $apPrograms = []; $keywords = []; $students = [];
}
$currentNav = 'repertoire';
$searchBarValue = $_GET['query'] ?? '';
$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. Recherche par année, orientation, atelier et mots-clés.';
$ogTags = [
'type' => 'website',
'title' => $pageTitle,
'description' => $metaDescription,
'url' => 'https://posterg.erg.be/search.php',
'site_name' => 'Posterg ERG',
];
$extraCss = ['assets/search.css'];
?>
<?php include APP_ROOT . '/templates/public/head.php'; ?>
<body class="search-body">
<a href="#main-content" class="skip-link">Aller au contenu principal</a>
<?php include APP_ROOT . '/templates/nav.php'; ?>
<?php include APP_ROOT . '/templates/search-bar.php'; ?>
<?php if ($validationError): ?>
<div class="search-error">⚠ <?= htmlspecialchars($validationError) ?></div>
<?php endif; ?>
<?php if ($hasSearch): ?>
<!-- ── RESULTS VIEW ─────────────────────────────────── -->
<!-- Filter controls -->
<form class="search-controls" method="GET" action="search.php">
<input type="hidden" name="query" value="<?= htmlspecialchars($_GET['query'] ?? '') ?>">
<label class="search-filter-label" for="filter-year">Année
<select class="search-filter-select" name="year" id="filter-year">
<option value="">Toutes</option>
<?php foreach ($years as $y): ?>
<option value="<?= (int)$y ?>" <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'selected' : '' ?>>
<?= (int)$y ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label class="search-filter-label" for="filter-orientation">Orientation
<select class="search-filter-select" name="orientation" id="filter-orientation">
<option value="">Toutes</option>
<?php foreach ($orientations as $o): ?>
<option value="<?= htmlspecialchars($o['name']) ?>"
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'selected' : '' ?>>
<?= htmlspecialchars($o['name']) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<label class="search-filter-label" for="filter-ap">AP
<select class="search-filter-select" name="ap_program" id="filter-ap">
<option value="">Tous</option>
<?php foreach ($apPrograms as $ap): ?>
<option value="<?= htmlspecialchars($ap['name']) ?>"
<?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
<?= htmlspecialchars($ap['name']) ?>
</option>
<?php endforeach; ?>
</select>
</label>
<button type="submit" class="search-apply-btn">Filtrer</button>
<a href="search.php" class="search-reset-link">Réinitialiser</a>
</form>
<main class="search-main" id="main-content">
<output class="search-results-header" role="status"><?= $totalItems ?> résultat<?= $totalItems > 1 ? 's' : '' ?></output>
<?php if (!empty($results)): ?>
<ul class="results-grid">
<?php foreach ($results as $item): ?>
<li><a href="tfe.php?id=<?= (int)$item['id'] ?>" class="result-card">
<span class="result-card__authors"><?= htmlspecialchars($item['authors'] ?? '') ?></span>
<span class="result-card__title"><?= htmlspecialchars($item['title']) ?></span>
<small class="result-card__meta"><?= htmlspecialchars($item['year']) ?><?php if (!empty($item['orientation'])): ?> · <?= htmlspecialchars($item['orientation']) ?><?php endif; ?></small>
</a></li>
<?php endforeach; ?>
</ul>
<?php if ($totalPages > 1): ?>
<nav class="pagination-wrap" aria-label="Pagination">
<a href="?<?= http_build_query(array_merge($_GET, ['page' => max(1, $page - 1)])) ?>"
class="pagination-btn<?= $page <= 1 ? ' disabled' : '' ?>"
<?= $page <= 1 ? 'aria-disabled="true" tabindex="-1"' : '' ?>
aria-label="Page précédente"></a>
<span class="pagination-info" aria-current="page"><?= $page ?> / <?= $totalPages ?></span>
<a href="?<?= http_build_query(array_merge($_GET, ['page' => min($totalPages, $page + 1)])) ?>"
class="pagination-btn<?= $page >= $totalPages ? ' disabled' : '' ?>"
<?= $page >= $totalPages ? 'aria-disabled="true" tabindex="-1"' : '' ?>
aria-label="Page suivante"></a>
</nav>
<?php endif; ?>
<?php else: ?>
<p class="search-empty">Aucun résultat pour cette recherche.</p>
<?php endif; ?>
</main>
<?php else: ?>
<!-- ── RÉPERTOIRE INDEX VIEW ─────────────────────────── -->
<main class="search-main" id="main-content">
<h1 class="sr-only">Répertoire</h1>
<div class="repertoire-index">
<!-- ANNÉES -->
<section class="repertoire-col">
<h2>Années</h2>
<ul>
<?php foreach ($years as $y): ?>
<li><a href="search.php?year=<?= (int)$y ?>"
<?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'aria-current="page"' : '' ?>>
<?= (int)$y ?>
</a></li>
<?php endforeach; ?>
</ul>
</section>
<!-- CATÉGORIES -->
<section class="repertoire-col">
<h2>Catégories</h2>
<?php if (!empty($orientations)): ?>
<span class="cat-index-label">Orientation</span>
<ul>
<?php foreach ($orientations as $o): ?>
<li><a href="search.php?orientation=<?= urlencode($o['name']) ?>"
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'aria-current="page"' : '' ?>>
<?= htmlspecialchars($o['name']) ?>
</a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (!empty($apPrograms)): ?>
<span class="cat-index-label">Ateliers Pluridisciplinaires</span>
<ul>
<?php foreach ($apPrograms as $ap): ?>
<li><a href="search.php?ap_program=<?= urlencode($ap['name']) ?>"
<?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'aria-current="page"' : '' ?>>
<?= htmlspecialchars($ap['name']) ?>
</a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</section>
<!-- ÉTUDIANTES -->
<section class="repertoire-col">
<h2>Étudiantes</h2>
<?php
// Build unique author → thesis list
$authorMap = [];
foreach ($students as $s) {
if (empty($s['authors'])) continue;
$names = explode(',', $s['authors']);
foreach ($names as $name) {
$name = trim($name);
if ($name && !isset($authorMap[$name])) {
$authorMap[$name] = $s['id'];
}
}
}
ksort($authorMap);
?>
<ul>
<?php foreach ($authorMap as $name => $id): ?>
<li><a href="tfe.php?id=<?= (int)$id ?>">
<?= htmlspecialchars($name) ?>
</a></li>
<?php endforeach; ?>
</ul>
</section>
<!-- MOTS-CLÉS -->
<section class="repertoire-col">
<h2>Mots-clés</h2>
<ul>
<?php foreach ($keywords as $kw): ?>
<li><a href="search.php?keyword=<?= urlencode($kw['name']) ?>"
<?= (isset($_GET['keyword']) && $_GET['keyword'] == $kw['name']) ? 'aria-current="page"' : '' ?>>
<?= htmlspecialchars($kw['name']) ?>
</a></li>
<?php endforeach; ?>
</ul>
</section>
</div>
</main>
<?php endif; ?>
</body>
</html>