Files
xamxam/public/search.php
Pontoporeia 640d37936f css: fix nav active state, deduplicate .site-nav__right, add font-display, clean up search pagination
- common.css: add font-display: swap to Combinedd.otf @font-face (eliminates FOIT)
- common.css: remove duplicate .site-nav__right block (identical to .site-nav__link);
  update nav.php to use .site-nav__link on the À Propos link
- common.css: add .site-nav__link--active rule (opacity:1 + white underline); the class
  was already applied in nav.php but had no CSS definition, making it invisible
- search.php: replace fully inline-styled pagination with .pagination-wrap / .pagination-btn
  / .pagination-info classes; add aria-disabled + tabindex=-1 on disabled links;
  add aria-label on prev/next links
- search.css: add pagination rule block to match, keeping styles co-located with the page
2026-03-28 16:44:35 +01:00

261 lines
11 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());
echo '<!DOCTYPE html><html><body><h1>Trop de requêtes</h1><p>Réessayez dans ' . $rateLimit->getResetTime() . ' secondes.</p></body></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'] ?? '';
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Répertoire Posterg</title>
<link rel="icon" type="image/svg+xml" href="/assets/admin_favicon.svg">
<link rel="stylesheet" href="assets/modern-normalize.min.css">
<link rel="stylesheet" href="assets/common.css">
<link rel="stylesheet" href="assets/search.css">
<?php if (php_sapi_name() === 'cli-server'): ?>
<script>
(function poll() {
fetch('/live-reload.php').then(r=>r.json()).then(d=>{
if(d.changed) location.reload(); else setTimeout(poll,1000);
}).catch(()=>setTimeout(poll,2000));
})();
</script>
<?php endif; ?>
</head>
<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'] ?? '') ?>">
<div class="search-filter-group">
<span class="search-filter-label">Année</span>
<select class="search-filter-select" name="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>
</div>
<div class="search-filter-group">
<span class="search-filter-label">Orientation</span>
<select class="search-filter-select" name="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>
</div>
<div class="search-filter-group">
<span class="search-filter-label">AP</span>
<select class="search-filter-select" name="ap_program">
<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>
</div>
<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">
<div class="search-results-view">
<p class="search-results-header"><?= $totalItems ?> résultat<?= $totalItems > 1 ? 's' : '' ?></p>
<?php if (!empty($results)): ?>
<div class="results-grid">
<?php foreach ($results as $item): ?>
<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>
<span class="result-card__meta"><?= htmlspecialchars($item['year']) ?><?php if (!empty($item['orientation'])): ?> · <?= htmlspecialchars($item['orientation']) ?><?php endif; ?></span>
</a>
<?php endforeach; ?>
</div>
<?php if ($totalPages > 1): ?>
<div class="pagination-wrap">
<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"><?= $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>
</div>
<?php endif; ?>
<?php else: ?>
<p class="search-empty">Aucun résultat pour cette recherche.</p>
<?php endif; ?>
</div>
</main>
<?php else: ?>
<!-- ── RÉPERTOIRE INDEX VIEW ─────────────────────────── -->
<main class="search-main" id="main-content">
<div class="repertoire-index">
<!-- ANNÉES -->
<div class="repertoire-col">
<h2 class="repertoire-col__header">Années</h2>
<?php foreach ($years as $y): ?>
<a href="search.php?year=<?= (int)$y ?>"
class="year-index-item <?= (isset($_GET['year']) && $_GET['year'] == $y) ? 'active' : '' ?>">
<?= (int)$y ?>
</a>
<?php endforeach; ?>
</div>
<!-- CATÉGORIES -->
<div class="repertoire-col">
<h2 class="repertoire-col__header">Catégories</h2>
<?php if (!empty($orientations)): ?>
<span class="cat-index-label">Orientation</span>
<?php foreach ($orientations as $o): ?>
<a href="search.php?orientation=<?= urlencode($o['name']) ?>"
class="cat-index-item <?= (isset($_GET['orientation']) && $_GET['orientation'] == $o['name']) ? 'active' : '' ?>">
<?= htmlspecialchars($o['name']) ?>
</a>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($apPrograms)): ?>
<span class="cat-index-label">Ateliers Pluridisciplinaires</span>
<?php foreach ($apPrograms as $ap): ?>
<a href="search.php?ap_program=<?= urlencode($ap['name']) ?>"
class="cat-index-item <?= (isset($_GET['ap_program']) && $_GET['ap_program'] == $ap['name']) ? 'active' : '' ?>">
<?= htmlspecialchars($ap['name']) ?>
</a>
<?php endforeach; ?>
<?php endif; ?>
</div>
<!-- ÉTUDIANTES -->
<div class="repertoire-col">
<h2 class="repertoire-col__header">É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);
foreach ($authorMap as $name => $id): ?>
<a href="tfe.php?id=<?= (int)$id ?>" class="student-index-item">
<?= htmlspecialchars($name) ?>
</a>
<?php endforeach; ?>
</div>
<!-- MOTS-CLÉS -->
<div class="repertoire-col">
<h2 class="repertoire-col__header">Mots-clés</h2>
<?php foreach ($keywords as $kw): ?>
<a href="search.php?keyword=<?= urlencode($kw['name']) ?>"
class="keyword-index-item <?= (isset($_GET['keyword']) && $_GET['keyword'] == $kw['name']) ? 'active' : '' ?>">
<?= htmlspecialchars($kw['name']) ?>
</a>
<?php endforeach; ?>
</div>
</div>
</main>
<?php endif; ?>
</body>
</html>