mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
- Reduce all spacing and padding in header for more compact fit - Fix back button overflow by removing width: 100% and adding overflow handling - Make filter section more compact with smaller fonts and spacing - Add main-wrapper div to group main and footer - Keep rounded corners (40px) on all three sections like main.css - Footer stays at bottom of main content area - Fix HTML structure: footer outside main, both inside wrapper
357 lines
16 KiB
PHP
357 lines
16 KiB
PHP
<?php
|
||
// Bootstrap application
|
||
require_once __DIR__ . '/../config/bootstrap.php';
|
||
require_once APP_ROOT . '/src/Database.php';
|
||
require_once APP_ROOT . '/src/RateLimit.php';
|
||
|
||
// Rate limiting: 30 requests per minute
|
||
$rateLimit = new RateLimit(30, 60);
|
||
|
||
// Check rate limit
|
||
if (!$rateLimit->check()) {
|
||
http_response_code(429);
|
||
header('Retry-After: ' . $rateLimit->getResetTime());
|
||
$rateLimit->sendHeaders();
|
||
|
||
// Simple error page
|
||
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Rate Limit</title></head><body>';
|
||
echo '<h1>Trop de requêtes</h1>';
|
||
echo '<p>Vous avez dépassé la limite de 30 recherches par minute. Veuillez réessayer dans ' . $rateLimit->getResetTime() . ' secondes.</p>';
|
||
echo '</body></html>';
|
||
exit;
|
||
}
|
||
|
||
$rateLimit->sendHeaders();
|
||
|
||
if (rand(1, 100) === 1) {
|
||
$rateLimit->cleanup();
|
||
}
|
||
|
||
// Pagination - adjust to grid
|
||
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
|
||
$itemsPerPage = 9; // Default grid size (3 rows × 3 columns)
|
||
|
||
// Collect search parameters
|
||
$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['finality'])) {
|
||
$searchParams['finality'] = $_GET['finality'];
|
||
}
|
||
if (!empty($_GET['keyword'])) {
|
||
$searchParams['keyword'] = $_GET['keyword'];
|
||
}
|
||
if (!empty($_GET['format'])) {
|
||
$searchParams['format'] = $_GET['format'];
|
||
}
|
||
if (!empty($_GET['language'])) {
|
||
$searchParams['language'] = $_GET['language'];
|
||
}
|
||
if (isset($_GET['is_doctoral'])) {
|
||
$searchParams['is_doctoral'] = $_GET['is_doctoral'] === '1';
|
||
}
|
||
|
||
$validationError = null;
|
||
$showFilters = isset($_GET['filters']) && $_GET['filters'] === 'show';
|
||
|
||
try {
|
||
$db = Database::getInstance();
|
||
|
||
// Get search results
|
||
$offset = ($page - 1) * $itemsPerPage;
|
||
$results = $db->searchTheses($searchParams, $itemsPerPage, $offset);
|
||
$totalItems = $db->countSearchResults($searchParams);
|
||
$totalPages = ceil($totalItems / $itemsPerPage);
|
||
|
||
// Get filter options
|
||
$years = $db->getAvailableYears();
|
||
$orientations = $db->getOrientations();
|
||
$apPrograms = $db->getApPrograms();
|
||
$finalityTypes = $db->getFinalityTypes();
|
||
$keywords = $db->getUsedKeywords();
|
||
$formats = $db->getFormatTypes();
|
||
$languages = $db->getLanguages();
|
||
|
||
} catch (InvalidArgumentException $e) {
|
||
error_log("Search validation error: " . $e->getMessage());
|
||
$validationError = $e->getMessage();
|
||
$results = [];
|
||
$totalPages = 0;
|
||
$totalItems = 0;
|
||
$years = [];
|
||
$orientations = [];
|
||
$apPrograms = [];
|
||
$finalityTypes = [];
|
||
$keywords = [];
|
||
$formats = [];
|
||
$languages = [];
|
||
} catch (Exception $e) {
|
||
error_log("Error in search: " . $e->getMessage());
|
||
$validationError = "Une erreur est survenue lors de la recherche.";
|
||
$results = [];
|
||
$totalPages = 0;
|
||
$totalItems = 0;
|
||
$years = [];
|
||
$orientations = [];
|
||
$apPrograms = [];
|
||
$finalityTypes = [];
|
||
$keywords = [];
|
||
$formats = [];
|
||
$languages = [];
|
||
}
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Recherche - Posterg</title>
|
||
<link rel="stylesheet" href="assets/modern-normalize.css">
|
||
<link rel="stylesheet" href="assets/common.css">
|
||
<link rel="stylesheet" href="assets/search.css">
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div class="search-header">
|
||
<a href="index.php" class="back-button">← Retour</a>
|
||
|
||
<form method="GET" action="search.php" class="search-form">
|
||
<input
|
||
type="text"
|
||
name="query"
|
||
class="search-input"
|
||
placeholder="Rechercher..."
|
||
value="<?= htmlspecialchars($_GET['query'] ?? ''); ?>"
|
||
autofocus
|
||
>
|
||
|
||
<div class="search-actions">
|
||
<button type="submit" class="search-button">Rechercher</button>
|
||
<button type="button" class="filter-button <?= $showFilters ? 'active' : ''; ?>" onclick="toggleFilters()">
|
||
Filtres
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Hidden field to maintain filter panel state -->
|
||
<input type="hidden" name="filters" id="filters-state" value="<?= $showFilters ? 'show' : 'hide'; ?>">
|
||
|
||
<!-- Preserve other filter values as hidden fields when searching -->
|
||
<?php foreach (['year', 'orientation', 'ap_program', 'finality', 'keyword', 'format', 'language', 'is_doctoral'] as $field): ?>
|
||
<?php if (!empty($_GET[$field])): ?>
|
||
<input type="hidden" name="<?= $field; ?>" value="<?= htmlspecialchars($_GET[$field]); ?>">
|
||
<?php endif; ?>
|
||
<?php endforeach; ?>
|
||
</form>
|
||
</div>
|
||
|
||
<?php if ($validationError): ?>
|
||
<div class="error-message">
|
||
⚠ <?= htmlspecialchars($validationError); ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<div class="filters-panel <?= $showFilters ? 'show' : ''; ?>" id="filters-panel">
|
||
<form method="GET" action="search.php">
|
||
<!-- Preserve query when using filters -->
|
||
<?php if (!empty($_GET['query'])): ?>
|
||
<input type="hidden" name="query" value="<?= htmlspecialchars($_GET['query']); ?>">
|
||
<?php endif; ?>
|
||
<input type="hidden" name="filters" value="show">
|
||
|
||
<div class="filters-grid">
|
||
<div class="filter-group">
|
||
<label class="filter-label">Année</label>
|
||
<select name="year" class="filter-select">
|
||
<option value="">Toutes</option>
|
||
<?php foreach ($years as $year): ?>
|
||
<option value="<?= (int)$year; ?>" <?= (isset($_GET['year']) && $_GET['year'] == $year) ? 'selected' : ''; ?>>
|
||
<?= (int)$year; ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">Orientation</label>
|
||
<select name="orientation" class="filter-select">
|
||
<option value="">Toutes</option>
|
||
<?php foreach ($orientations as $orientation): ?>
|
||
<option value="<?= htmlspecialchars($orientation['name']); ?>"
|
||
<?= (isset($_GET['orientation']) && $_GET['orientation'] == $orientation['name']) ? 'selected' : ''; ?>>
|
||
<?= htmlspecialchars($orientation['name']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">AP</label>
|
||
<select name="ap_program" class="filter-select">
|
||
<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>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">Finalité</label>
|
||
<select name="finality" class="filter-select">
|
||
<option value="">Toutes</option>
|
||
<?php foreach ($finalityTypes as $finality): ?>
|
||
<option value="<?= htmlspecialchars($finality['name']); ?>"
|
||
<?= (isset($_GET['finality']) && $_GET['finality'] == $finality['name']) ? 'selected' : ''; ?>>
|
||
<?= htmlspecialchars($finality['name']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">Format</label>
|
||
<select name="format" class="filter-select">
|
||
<option value="">Tous</option>
|
||
<?php foreach ($formats as $format): ?>
|
||
<option value="<?= htmlspecialchars($format['name']); ?>"
|
||
<?= (isset($_GET['format']) && $_GET['format'] == $format['name']) ? 'selected' : ''; ?>>
|
||
<?= htmlspecialchars($format['name']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">Langue</label>
|
||
<select name="language" class="filter-select">
|
||
<option value="">Toutes</option>
|
||
<?php foreach ($languages as $language): ?>
|
||
<option value="<?= htmlspecialchars($language['name']); ?>"
|
||
<?= (isset($_GET['language']) && $_GET['language'] == $language['name']) ? 'selected' : ''; ?>>
|
||
<?= htmlspecialchars($language['name']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">Mot-clé</label>
|
||
<select name="keyword" class="filter-select">
|
||
<option value="">Tous</option>
|
||
<?php foreach ($keywords as $keyword): ?>
|
||
<option value="<?= htmlspecialchars($keyword['keyword']); ?>"
|
||
<?= (isset($_GET['keyword']) && $_GET['keyword'] == $keyword['keyword']) ? 'selected' : ''; ?>>
|
||
<?= htmlspecialchars($keyword['keyword']); ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label class="filter-label">Type</label>
|
||
<select name="is_doctoral" class="filter-select">
|
||
<option value="">Tous</option>
|
||
<option value="0" <?= (isset($_GET['is_doctoral']) && $_GET['is_doctoral'] == '0') ? 'selected' : ''; ?>>
|
||
TFE uniquement
|
||
</option>
|
||
<option value="1" <?= (isset($_GET['is_doctoral']) && $_GET['is_doctoral'] == '1') ? 'selected' : ''; ?>>
|
||
Thèses doctorales
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="filter-actions">
|
||
<button type="submit" class="search-button">Appliquer</button>
|
||
<a href="search.php?filters=show" class="reset-button">Réinitialiser</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="main-wrapper">
|
||
<main>
|
||
<div class="cards-container">
|
||
<?php if (count($results) > 0): ?>
|
||
<?php foreach ($results as $item): ?>
|
||
<a href="tfe.php?id=<?= (int)$item['id']; ?>" class="card-link">
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<h3 class="title"><?= htmlspecialchars($item['title']); ?></h3>
|
||
<p class="authors"><?= htmlspecialchars($item['authors'] ?? 'Auteur inconnu'); ?></p>
|
||
<p class="year"><?= htmlspecialchars($item['year']); ?></p>
|
||
<?php if (!empty($item['orientation'])): ?>
|
||
<div class="tags">
|
||
<span class="tag"><?= htmlspecialchars($item['orientation']); ?></span>
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
<?php elseif (!empty($searchParams)): ?>
|
||
<div style="grid-column: 1 / -1; display: flex; align-items: center; justify-content: center; color: white; font-size: 1.2rem;">
|
||
Aucun résultat trouvé pour cette recherche.
|
||
</div>
|
||
<?php else: ?>
|
||
<div style="grid-column: 1 / -1; display: flex; align-items: center; justify-content: center; color: white; font-size: 1.2rem;">
|
||
Utilisez la barre de recherche pour trouver des mémoires.
|
||
</div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php if ($totalPages > 1): ?>
|
||
<div class="pagination">
|
||
<a href="?<?= http_build_query(array_merge($_GET, ['page' => max(1, $page - 1)])); ?>"
|
||
class="pagination-btn <?= $page <= 1 ? 'disabled' : ''; ?>">
|
||
←
|
||
</a>
|
||
<div class="pagination-info">
|
||
<span class="page-current"><?= $page; ?></span>
|
||
<span class="page-separator">/</span>
|
||
<span class="page-total"><?= $totalPages; ?></span>
|
||
</div>
|
||
<a href="?<?= http_build_query(array_merge($_GET, ['page' => min($totalPages, $page + 1)])); ?>"
|
||
class="pagination-btn <?= $page >= $totalPages ? 'disabled' : ''; ?>">
|
||
→
|
||
</a>
|
||
</div>
|
||
<?php endif; ?>
|
||
</main>
|
||
|
||
<footer>
|
||
<div class="results-footer">
|
||
<span class="results-count"><?= (int)$totalItems; ?></span>
|
||
<span>résultat<?= $totalItems > 1 ? 's' : ''; ?></span>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
|
||
<script>
|
||
function toggleFilters() {
|
||
const panel = document.getElementById('filters-panel');
|
||
const button = document.querySelector('.filter-button');
|
||
const state = document.getElementById('filters-state');
|
||
|
||
panel.classList.toggle('show');
|
||
button.classList.toggle('active');
|
||
|
||
// Update hidden field
|
||
state.value = panel.classList.contains('show') ? 'show' : 'hide';
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|