mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-07 11:39:18 +02:00
Problem: <video> elements on tfe.php had no <track kind="captions"> element, violating WCAG 4.1.2 (name, role, value) for video content. Changes: - public/tfe.php: collect all text/vtt files from the thesis file list before rendering; skip standalone rendering of .vtt entries; for each MP4 emit a <track kind="captions" srclang="fr" label="Sous-titres" default> pointing to the N-th VTT file (N-th video paired with N-th caption in document order) - public/media.php: add text/vtt to allowed MIME list; normalise finfo text/plain -> text/vtt for .vtt files; add vtt branch to cache/header block (Content-Type: text/vtt; charset=utf-8, 1-day cache) - public/admin/actions/formulaire.php: allow .vtt uploads (text/vtt MIME, vtt extension); normalise text/plain finfo result; set file_type='caption' for VTT files so they are distinguishable from other thesis files - public/admin/add.php: extend files field accept attr to include .vtt; update hint text to document the VTT sidecar convention VTT files uploaded under theses/ inherit the same access_type visibility gate in media.php as all other thesis content (403 for access_type_id=3).
271 lines
12 KiB
PHP
271 lines
12 KiB
PHP
<?php
|
||
require_once __DIR__ . '/../config/bootstrap.php';
|
||
require_once APP_ROOT . '/src/Database.php';
|
||
|
||
if (isset($_GET['id'])) {
|
||
$thesisId = intval($_GET['id']);
|
||
try {
|
||
$db = Database::getInstance();
|
||
$data = $db->getThesisById($thesisId);
|
||
if (!$data) { header('Location: index.php'); exit; }
|
||
} catch (Exception $e) {
|
||
error_log("Error loading thesis: " . $e->getMessage());
|
||
header('Location: index.php'); exit;
|
||
}
|
||
} else {
|
||
header('Location: index.php'); exit;
|
||
}
|
||
|
||
$currentNav = '';
|
||
$_tfeAuthor = $data['authors'] ?? '';
|
||
$pageTitle = $data['title'] . (!empty($_tfeAuthor) ? ' – ' . $_tfeAuthor : '') . ' – Posterg';
|
||
// Build meta description: strip markdown/HTML from synopsis, truncate to ~160 chars
|
||
$_synopsisRaw = strip_tags($data['synopsis'] ?? '');
|
||
$metaDescription = mb_strlen($_synopsisRaw) > 160
|
||
? mb_substr($_synopsisRaw, 0, 157) . '…'
|
||
: (empty($_synopsisRaw) ? 'Mémoire de fin d\'études – Posterg, répertoire des TFE de l\'erg.' : $_synopsisRaw);
|
||
unset($_tfeAuthor, $_synopsisRaw);
|
||
|
||
// --- Open Graph / Twitter Card tags ------------------------------------------
|
||
$_ogBaseUrl = 'https://posterg.erg.be';
|
||
// Resolve OG image: banner → first image file → none
|
||
$_ogImage = '';
|
||
if (!empty($data['banner_path'])) {
|
||
$_ogImage = $_ogBaseUrl . '/media.php?path=' . rawurlencode($data['banner_path']);
|
||
} elseif (!empty($data['files'])) {
|
||
foreach ($data['files'] as $_f) {
|
||
$_ext = strtolower(pathinfo($_f['file_path'], PATHINFO_EXTENSION));
|
||
if (in_array($_ext, ['jpg', 'jpeg', 'png', 'gif', 'webp'])) {
|
||
$_ogImage = $_ogBaseUrl . '/media.php?path=' . rawurlencode($_f['file_path']);
|
||
break;
|
||
}
|
||
}
|
||
unset($_f, $_ext);
|
||
}
|
||
$ogTags = [
|
||
'type' => 'article',
|
||
'title' => $data['title'] . (!empty($data['authors']) ? ' – ' . $data['authors'] : ''),
|
||
'description' => $metaDescription,
|
||
'url' => $_ogBaseUrl . '/tfe.php?id=' . $thesisId,
|
||
'image' => $_ogImage,
|
||
'image_alt' => $data['title'] . (!empty($data['authors']) ? ' par ' . $data['authors'] : ''),
|
||
'site_name' => 'Posterg – ERG',
|
||
'article_author' => $data['authors'] ?? '',
|
||
'article_published_time' => !empty($data['year']) ? $data['year'] . '-01-01' : '',
|
||
];
|
||
unset($_ogBaseUrl, $_ogImage);
|
||
// --- End Open Graph ----------------------------------------------------------
|
||
|
||
$extraCss = ['/assets/css/tfe.css'];
|
||
$bodyClass = 'tfe-body';
|
||
?>
|
||
<?php include APP_ROOT . '/templates/head.php'; ?>
|
||
<?php include APP_ROOT . '/templates/header.php'; ?>
|
||
|
||
<main class="tfe-main" id="main-content">
|
||
<article class="tfe-layout">
|
||
|
||
<!-- LEFT: info — article header -->
|
||
<header class="tfe-left">
|
||
<a href="index.php" class="tfe-back-link">← Retour</a>
|
||
|
||
<!-- Title is the primary heading; author is metadata -->
|
||
<h1 class="tfe-title">
|
||
<?= htmlspecialchars($data['title']) ?>
|
||
<?php if (!empty($data['subtitle'])): ?>
|
||
– <?= htmlspecialchars($data['subtitle']) ?>
|
||
<?php endif; ?>
|
||
</h1>
|
||
|
||
<p class="tfe-author"><?= htmlspecialchars($data['authors'] ?? 'Auteur inconnu') ?></p>
|
||
|
||
<dl>
|
||
<?php if (!empty($data['orientation'])): ?>
|
||
<div>
|
||
<dt>Orientation :</dt>
|
||
<dd><?= htmlspecialchars($data['orientation']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['ap_program'])): ?>
|
||
<div>
|
||
<dt>Atelier pluridisciplinaire :</dt>
|
||
<dd><?= htmlspecialchars($data['ap_program']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['year'])): ?>
|
||
<div>
|
||
<dt>Date :</dt>
|
||
<dd><?= htmlspecialchars($data['year']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['languages'])): ?>
|
||
<div>
|
||
<dt>Langue :</dt>
|
||
<dd><?= htmlspecialchars($data['languages']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['formats'])): ?>
|
||
<div>
|
||
<dt>Format :</dt>
|
||
<dd><?= htmlspecialchars($data['formats']) ?></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><?= htmlspecialchars($data['keywords']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['jury_president'])): ?>
|
||
<div>
|
||
<dt>Président·e du jury :</dt>
|
||
<dd><?= htmlspecialchars($data['jury_president']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['jury_promoteurs'])): ?>
|
||
<div>
|
||
<dt>Promoteur·ice :</dt>
|
||
<dd><?= htmlspecialchars($data['jury_promoteurs']) ?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($data['jury_lecteurs'])): ?>
|
||
<div>
|
||
<dt>Lecteur·ices :</dt>
|
||
<dd><?= htmlspecialchars($data['jury_lecteurs']) ?></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['baiu_link'])): ?>
|
||
<div>
|
||
<dt>Contact :</dt>
|
||
<dd>
|
||
<a href="<?= htmlspecialchars($data['baiu_link']) ?>" target="_blank" rel="noopener">
|
||
<?= htmlspecialchars($data['baiu_link']) ?>
|
||
<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
|
||
$accessTypeId = $db->getThesisAccessTypeId($thesisId) ?? 1;
|
||
$isInterdit = ($accessTypeId === 3);
|
||
|
||
// Collect any WebVTT caption files uploaded for this thesis.
|
||
// The N-th VTT file is paired with the N-th video in document order.
|
||
$_captionFiles = [];
|
||
foreach ($data['files'] ?? [] as $_cf) {
|
||
$__mime = $_cf['mime_type'] ?? '';
|
||
$__ext2 = strtolower(pathinfo($_cf['file_path'], PATHINFO_EXTENSION));
|
||
if ($__mime === 'text/vtt' || $__ext2 === 'vtt') {
|
||
$_captionFiles[] = $_cf['file_path'];
|
||
}
|
||
}
|
||
$_videoIndex = 0;
|
||
unset($_cf, $__mime, $__ext2);
|
||
?>
|
||
<?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));
|
||
// VTT caption files are consumed inline by <video>; skip standalone rendering.
|
||
if ($ext === 'vtt') continue;
|
||
?>
|
||
<figure>
|
||
<?php if ($ext === 'pdf'): ?>
|
||
<embed src="/media.php?path=<?= urlencode($file['file_path']) ?>"
|
||
type="application/pdf" width="100%" height="700px">
|
||
<p class="tfe-pdf-fallback">
|
||
<a href="/media.php?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.php?path=<?= urlencode($file['file_path']) ?>"
|
||
alt="<?= htmlspecialchars(
|
||
!empty($file['description'])
|
||
? $file['description']
|
||
: ($data['title'] . ' — ' . ($data['authors'] ?? ''))
|
||
) ?>">
|
||
<?php elseif ($ext === 'mp4'): ?>
|
||
<?php
|
||
// Pair this video with the N-th VTT file (if one was uploaded).
|
||
$_vttPath = $_captionFiles[$_videoIndex] ?? null;
|
||
$_videoIndex++;
|
||
?>
|
||
<video width="100%" controls>
|
||
<source src="/media.php?path=<?= urlencode($file['file_path']) ?>" type="video/mp4">
|
||
<?php if ($_vttPath): ?>
|
||
<track kind="captions"
|
||
src="/media.php?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>
|
||
|
||
<?php include APP_ROOT . '/templates/footer.php'; ?>
|