mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
feat: single entry point routing — convert to front controller pattern
- Create app/public/index.php as front controller (bootstrap + Dispatcher) - Rewrite app/router.php for PHP dev server → all non-asset requests to index.php - Update Dispatcher to render full page layouts (head+header+view+footer) - Move public view templates into templates/public/ (home, search, tfe, about, repertoire) - Delete dead direct-access public/*.php files (apropos, search, tfe, licence, repertoire) - Add clean URL routes to Dispatcher (/search, /tfe, /repertoire, /apropos, /licence, /media) - Remove .php extensions from all internal links (header, views, templates, URLs) - Update OG tags in controllers to use clean URLs - Update nginx posterg.conf → front-controller try_files pattern, block direct .php access - Update header.php and search-bar.php form actions to clean URLs - Switch AboutController nav key from 'nav' to 'currentNav' for consistency
This commit is contained in:
98
app/templates/public/about.php
Normal file
98
app/templates/public/about.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Render a comma-separated list of entries with links.
|
||||
* Entries joined with comma, last two joined with " & ".
|
||||
*/
|
||||
function renderEntries(array $entries): string {
|
||||
if (empty($entries)) return '';
|
||||
$parts = [];
|
||||
foreach ($entries as $e) {
|
||||
$text = htmlspecialchars($e['text'] ?? '');
|
||||
$url = $e['url'] ?? '';
|
||||
if (!empty($url)) {
|
||||
$parts[] = '<span class="apropos-entry"><a href="' . htmlspecialchars($url) . '" target="_blank" rel="noopener">' . $text . '</a></span>';
|
||||
} else {
|
||||
$parts[] = '<span class="apropos-entry">' . $text . '</span>';
|
||||
}
|
||||
}
|
||||
$count = count($parts);
|
||||
if ($count === 1) return $parts[0];
|
||||
$prefix = implode(', ', array_slice($parts, 0, $count - 2));
|
||||
$suffix = implode(' & ', array_slice($parts, -2));
|
||||
return $prefix !== '' ? $prefix . ', ' . $suffix : $suffix;
|
||||
}
|
||||
?>
|
||||
<main class="apropos-main" id="main-content">
|
||||
<div class="apropos-layout">
|
||||
|
||||
<!-- LEFT: sticky table of contents -->
|
||||
<nav class="apropos-toc" aria-label="Sections de la page">
|
||||
<p class="apropos-toc-label">Parties</p>
|
||||
<ul>
|
||||
<li><a href="#apropos-intro">À propos</a></li>
|
||||
<?php if (!empty($contacts)): ?>
|
||||
<li><a href="#apropos-contacts">Contacts</a></li>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($credits)): ?>
|
||||
<li><a href="#apropos-credits">Crédits</a></li>
|
||||
<?php endif; ?>
|
||||
</ul>
|
||||
<div class="apropos-toc-erg">
|
||||
<a href="https://erg.be" target="_blank" rel="noopener">
|
||||
Site de l'erg ↗
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- MIDDLE: main prose + sections -->
|
||||
<div class="apropos-content">
|
||||
|
||||
<!-- Intro text from DB -->
|
||||
<section class="apropos-section" id="apropos-intro">
|
||||
<div class="prose">
|
||||
<?= $aboutHtml ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($contacts)): ?>
|
||||
<!-- Contacts section -->
|
||||
<section class="apropos-section" id="apropos-contacts">
|
||||
<h2 class="apropos-section-title">Contacts</h2>
|
||||
<div class="apropos-contacts-grid">
|
||||
<?php foreach ($contacts as $group): ?>
|
||||
<address class="apropos-contact-card">
|
||||
<?= renderEntries($group['entries'] ?? []) ?>
|
||||
<?php if (!empty($group['role'])): ?>
|
||||
<span><?= htmlspecialchars($group['role']) ?></span>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$emails = array_filter(array_column($group['entries'] ?? [], 'email'), fn($e) => !empty($e));
|
||||
foreach ($emails as $email):
|
||||
?>
|
||||
<a href="mailto:<?= htmlspecialchars($email) ?>"><?= htmlspecialchars($email) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</address>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($credits)): ?>
|
||||
<!-- Credits section -->
|
||||
<section class="apropos-section" id="apropos-credits">
|
||||
<h2 class="apropos-section-title">Crédits</h2>
|
||||
<dl class="apropos-credits-list">
|
||||
<?php foreach ($credits as $group): ?>
|
||||
<div class="apropos-credit-row">
|
||||
<dt><?= htmlspecialchars($group['label']) ?></dt>
|
||||
<dd><?= renderEntries($group['entries'] ?? []) ?></dd>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</dl>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php if ($year): ?>
|
||||
<p class="filter-info" role="status">
|
||||
Année : <?= htmlspecialchars($year) ?>
|
||||
<a href="?<?= http_build_query(array_diff_key($vars ?? [], ['page' => 1, 'year' => 1])) ?>" class="clear-filter"><span aria-hidden="true">✕</span> Réinitialiser</a>
|
||||
<a href="/" class="clear-filter"><span aria-hidden="true">✕</span> Réinitialiser</a>
|
||||
</p>
|
||||
<?php elseif ($isDefaultView): ?>
|
||||
<p class="home-section-label" role="status">Publication récente</p>
|
||||
@@ -24,7 +24,7 @@
|
||||
?>
|
||||
<?php if ($thumb): ?>
|
||||
<figure>
|
||||
<img src="/media.php?path=<?= urlencode($thumb) ?>"
|
||||
<img src="/media?path=<?= urlencode($thumb) ?>"
|
||||
alt="Couverture — <?= htmlspecialchars($item['title']) ?> par <?= htmlspecialchars($item['authors'] ?? '') ?>"
|
||||
loading="lazy">
|
||||
</figure>
|
||||
|
||||
6
app/templates/public/repertoire.php
Normal file
6
app/templates/public/repertoire.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<main class="search-main" id="main-content">
|
||||
<h1 class="sr-only">Répertoire</h1>
|
||||
<span id="rep-indicator" class="rep-indicator htmx-indicator" aria-hidden="true"></span>
|
||||
<?php include APP_ROOT . '/templates/partials/repertoire-index.php'; ?>
|
||||
</main>
|
||||
<script src="/assets/js/htmx.min.js"></script>
|
||||
67
app/templates/public/search.php
Normal file
67
app/templates/public/search.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php if ($validationError): ?>
|
||||
<div class="search-error">⚠ <?= htmlspecialchars($validationError) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Filter controls -->
|
||||
<form class="search-controls" method="GET" action="/search">
|
||||
<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?query=<?= urlencode($_GET['query'] ?? '') ?>" 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?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 include APP_ROOT . '/templates/partials/pagination.php'; ?>
|
||||
|
||||
<?php else: ?>
|
||||
<p class="search-empty">Aucun résultat pour cette recherche.</p>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
245
app/templates/public/tfe.php
Normal file
245
app/templates/public/tfe.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<main class="tfe-main" id="main-content">
|
||||
<article class="tfe-layout">
|
||||
|
||||
<!-- LEFT: info — article header -->
|
||||
<header class="tfe-left">
|
||||
<!-- Author above title -->
|
||||
<p class="tfe-author"><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu') ?></p>
|
||||
|
||||
<h1 class="tfe-title">
|
||||
<?= htmlspecialchars($data['title']) ?>
|
||||
<?php if (!empty($data['subtitle'])): ?>
|
||||
– <?= htmlspecialchars($data['subtitle']) ?>
|
||||
<?php endif; ?>
|
||||
</h1>
|
||||
|
||||
<dl>
|
||||
<?php if (!empty($data['orientation'])): ?>
|
||||
<div>
|
||||
<dt>Orientation :</dt>
|
||||
<dd><a href="/repertoire?or[]=<?= urlencode($data['orientation']) ?>"><?= htmlspecialchars($data['orientation']) ?></a></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['ap_program'])): ?>
|
||||
<div>
|
||||
<dt>Atelier pluridisciplinaire :</dt>
|
||||
<dd><a href="/repertoire?ap[]=<?= urlencode($data['ap_program']) ?>"><?= htmlspecialchars($data['ap_program']) ?></a></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['year'])): ?>
|
||||
<div>
|
||||
<dt>Date :</dt>
|
||||
<dd><a href="/repertoire?fy[]=<?= urlencode($data['year']) ?>"><?= htmlspecialchars($data['year']) ?></a></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['languages'])): ?>
|
||||
<div>
|
||||
<dt>Langue :</dt>
|
||||
<dd><?php
|
||||
$langs = array_map('trim', explode(',', $data['languages']));
|
||||
$langLinks = array_map(fn($l) => '<a href="/search?query=' . urlencode($l) . '">' . htmlspecialchars($l) . '</a>', $langs);
|
||||
echo implode(', ', $langLinks);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['formats'])): ?>
|
||||
<div>
|
||||
<dt>Format :</dt>
|
||||
<dd><?php
|
||||
$fmts = array_map('trim', explode(',', $data['formats']));
|
||||
$fmtLinks = array_map(fn($f) => '<a href="/search?query=' . urlencode($f) . '">' . htmlspecialchars($f) . '</a>', $fmts);
|
||||
echo implode(', ', $fmtLinks);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['file_size_info'])): ?>
|
||||
<div>
|
||||
<dt>Durée :</dt>
|
||||
<dd><?= htmlspecialchars($data['file_size_info']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['keywords'])): ?>
|
||||
<div>
|
||||
<dt>Mots-clés :</dt>
|
||||
<dd><?php
|
||||
$kws = array_map('trim', explode(',', $data['keywords']));
|
||||
$kwLinks = array_map(fn($k) => '<a href="/repertoire?kw[]=' . urlencode($k) . '">' . htmlspecialchars($k) . '</a>', $kws);
|
||||
echo implode(', ', $kwLinks);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($promoteursInternes)): ?>
|
||||
<div>
|
||||
<dt>Promoteur·ice interne :</dt>
|
||||
<dd><?php
|
||||
$links = array_map(fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>', $promoteursInternes);
|
||||
echo implode(', ', $links);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($promoteursExternes)): ?>
|
||||
<div>
|
||||
<dt>Promoteur·ice externe :</dt>
|
||||
<dd><?php
|
||||
$links = array_map(fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>', $promoteursExternes);
|
||||
echo implode(', ', $links);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($juryPresidents)): ?>
|
||||
<div>
|
||||
<dt>Président·e du jury :</dt>
|
||||
<dd><?php
|
||||
$links = array_map(fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>', $juryPresidents);
|
||||
echo implode(', ', $links);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($juryLecteurs)): ?>
|
||||
<div>
|
||||
<dt>Lecteur·ices :</dt>
|
||||
<dd><?php
|
||||
$links = array_map(fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>', $juryLecteurs);
|
||||
echo implode(', ', $links);
|
||||
?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['access_type'])): ?>
|
||||
<div>
|
||||
<dt>Accès :</dt>
|
||||
<dd><?= htmlspecialchars($data['access_type']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['license_type'])): ?>
|
||||
<div>
|
||||
<dt>Licence :</dt>
|
||||
<dd><?= htmlspecialchars($data['license_type']) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['context_note'])): ?>
|
||||
<div class="tfe-meta-note">
|
||||
<dt>Note :</dt>
|
||||
<dd class="tfe-note-value"><?= nl2br(htmlspecialchars($data['context_note'])) ?></dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['author_email']) && !empty($data['author_show_contact'])): ?>
|
||||
<div>
|
||||
<dt>Contact :</dt>
|
||||
<dd>
|
||||
<?php
|
||||
$_contact = $data['author_email'];
|
||||
$_isUrl = filter_var($_contact, FILTER_VALIDATE_URL) !== false;
|
||||
$_isEmail = !$_isUrl && str_contains($_contact, '@');
|
||||
if ($_isUrl):
|
||||
?>
|
||||
<a href="<?= htmlspecialchars($_contact) ?>" target="_blank" rel="noopener">
|
||||
<?= htmlspecialchars(preg_replace('#^https?://#i', '', rtrim($_contact, '/'))) ?>
|
||||
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
|
||||
</a>
|
||||
<?php elseif ($_isEmail): ?>
|
||||
<a href="mailto:<?= htmlspecialchars($_contact) ?>"><?= htmlspecialchars($_contact) ?></a>
|
||||
<?php else: ?>
|
||||
<?= htmlspecialchars($_contact) ?>
|
||||
<?php endif; ?>
|
||||
</dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['baiu_link'])): ?>
|
||||
<?php
|
||||
$_baiuHref = htmlspecialchars($data['baiu_link']);
|
||||
$_baiuLabel = preg_replace('#^https?://#i', '', rtrim($data['baiu_link'], '/'));
|
||||
?>
|
||||
<div>
|
||||
<dt>Lien :</dt>
|
||||
<dd>
|
||||
<a href="<?= $_baiuHref ?>" target="_blank" rel="noopener">
|
||||
<?= htmlspecialchars($_baiuLabel) ?>
|
||||
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
|
||||
</a>
|
||||
|
||||
</dd>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</dl>
|
||||
|
||||
<?php if (!empty($data['synopsis'])): ?>
|
||||
<p class="tfe-synopsis-text">
|
||||
<?= nl2br(htmlspecialchars($data['synopsis'])) ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
|
||||
<!-- RIGHT: media — supplementary aside -->
|
||||
<aside class="tfe-right">
|
||||
<?php
|
||||
$_videoIndex = 0;
|
||||
?>
|
||||
<?php if ($isInterdit): ?>
|
||||
<p class="tfe-restricted">
|
||||
Ce TFE n'est pas disponible en ligne.
|
||||
</p>
|
||||
<?php elseif (!empty($data['files'])): ?>
|
||||
<?php foreach ($data['files'] as $file): ?>
|
||||
<?php
|
||||
$ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION));
|
||||
if ($ext === 'vtt') continue;
|
||||
?>
|
||||
<figure>
|
||||
<?php if ($ext === 'pdf'): ?>
|
||||
<embed src="/media?path=<?= urlencode($file['file_path']) ?>"
|
||||
type="application/pdf" width="100%" height="700px">
|
||||
<p class="tfe-pdf-fallback">
|
||||
<a href="/media?path=<?= urlencode($file['file_path']) ?>&download=1">
|
||||
Télécharger le PDF
|
||||
</a>
|
||||
</p>
|
||||
<?php elseif (in_array($ext, ['jpg','jpeg','png','gif','bmp','webp'])): ?>
|
||||
<img src="/media?path=<?= urlencode($file['file_path']) ?>"
|
||||
alt="<?= htmlspecialchars(
|
||||
!empty($file['description'])
|
||||
? $file['description']
|
||||
: ($data['title'] . ' — ' . ($data['authors'] ?? ''))
|
||||
) ?>">
|
||||
<?php elseif ($ext === 'mp4'): ?>
|
||||
<?php
|
||||
$_vttPath = $captionFiles[$_videoIndex] ?? null;
|
||||
$_videoIndex++;
|
||||
?>
|
||||
<video width="100%" controls>
|
||||
<source src="/media?path=<?= urlencode($file['file_path']) ?>" type="video/mp4">
|
||||
<?php if ($_vttPath): ?>
|
||||
<track kind="captions"
|
||||
src="/media?path=<?= urlencode($_vttPath) ?>"
|
||||
srclang="fr"
|
||||
label="Sous-titres"
|
||||
default>
|
||||
<?php endif; ?>
|
||||
</video>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($file['description'])): ?>
|
||||
<figcaption><?= htmlspecialchars($file['description']) ?></figcaption>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<p class="tfe-no-files">Aucun fichier disponible pour ce TFE.</p>
|
||||
<?php endif; ?>
|
||||
</aside>
|
||||
|
||||
</article>
|
||||
</main>
|
||||
Reference in New Issue
Block a user