Files
xamxam/app/templates/public/tfe.php
Pontoporeia 3016c199bd Fix edit form: is_published reset, contact decoupling, note label, author name case
- Fix #1: Add is_published to getThesisRawFields() SELECT so the publish
  checkbox stays checked when editing an already-published TFE.
- Fix #2: Rename 'Note contextuelle' → 'Note contextuelle relative à
  soutenance' in all templates and StudentEmail.
- Fix #3: Update findOrCreateAuthor to also UPDATE the author name when
  a record is found by name (fixes inability to capitalise names).
- Fix #4/#5: Decouple contact_interne (private author email) from
  contact_visible (public contact on TFE page). Add migration 037 to
  add contact_visible TEXT column to theses table and rebuild
  v_theses_full view. Update all controllers, templates, and DB methods
  to treat them independently.
- Fix #6: Investigated libre→interne restriction — no code barrier
  found; likely resolved by is_published fix.
2026-06-10 00:17:00 +02:00

439 lines
22 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.
<main class="tfe-main" id="main-content">
<article class="tfe-layout">
<!-- ROW 1: Author above Title -->
<div class="tfe-header-row">
<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>
</div>
<!-- ROW 2: Two columns — meta left, synopsis right -->
<div class="tfe-content-row">
<div class="tfe-meta">
<?php if (!empty($data["orientation"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Orientation :</span>
<a href="/repertoire?or[]=<?= urlencode($data["orientation"]) ?>"><?= htmlspecialchars($data["orientation"]) ?></a>
</p>
<?php endif; ?>
<?php if (!empty($data["ap_program"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Atelier pluridisciplinaire :</span>
<a href="/repertoire?ap[]=<?= urlencode($data["ap_program"]) ?>"><?= htmlspecialchars($data["ap_program"]) ?></a>
</p>
<?php endif; ?>
<?php if (!empty($data["finality_type"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Finalité :</span>
<a href="/repertoire?fi[]=<?= urlencode($data["finality_type"]) ?>"><?= htmlspecialchars($data["finality_type"]) ?></a>
</p>
<?php endif; ?>
<?php if (!empty($data["year"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Date :</span>
<a href="/repertoire?fy[]=<?= urlencode($data["year"]) ?>"><?= htmlspecialchars($data["year"]) ?></a>
</p>
<?php endif; ?>
<?php if (!empty($data["languages"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Langue :</span>
<?php
$langs = array_map("trim", explode(",", $data["languages"]));
$langLinks = array_map(
fn($l) => '<a href="/search?query=' . urlencode($l) . '">' . htmlspecialchars(mb_strtolower($l)) . '</a>',
$langs,
);
echo implode(", ", $langLinks);
?>
</p>
<?php endif; ?>
<?php if (!empty($data["formats"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Format :</span>
<?php
$fmts = array_map("trim", explode(",", $data["formats"]));
$fmtLinks = array_map(
fn($f) => '<a href="/search?query=' . urlencode($f) . '">' . htmlspecialchars(mb_strtolower($f)) . '</a>',
$fmts,
);
echo implode(", ", $fmtLinks);
?>
</p>
<?php endif; ?>
<?php if (!empty($data["keywords"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Mots-clés :</span>
<?php
$kws = array_map("trim", explode(",", $data["keywords"]));
$kwLinks = array_map(
fn($k) => '<a href="/repertoire?kw[]=' . urlencode($k) . '">' . htmlspecialchars(mb_strtolower($k)) . '</a>',
$kws,
);
echo implode(", ", $kwLinks);
?>
</p>
<?php endif; ?>
<?php if (!empty($promoteursInternes)): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Promoteur·ice interne :</span>
<?php
$links = array_map(
fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>',
$promoteursInternes,
);
echo implode(", ", $links);
?>
</p>
<?php endif; ?>
<?php if (!empty($promoteursExternes)): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Promoteur·ice externe :</span>
<?php
$links = array_map(
fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>',
$promoteursExternes,
);
echo implode(", ", $links);
?>
</p>
<?php endif; ?>
<?php if (!empty($promoteursUlb)): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Promoteur·ice ULB :</span>
<?php
$links = array_map(
fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>',
$promoteursUlb,
);
echo implode(", ", $links);
?>
</p>
<?php endif; ?>
<?php if (!empty($juryPresidents)): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Président·e du jury :</span>
<?php
$links = array_map(
fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>',
$juryPresidents,
);
echo implode(", ", $links);
?>
</p>
<?php endif; ?>
<?php if (!empty($juryLecteursInternes)): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Lecteur·ice(s) interne :</span>
<?php
$links = array_map(
fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>',
$juryLecteursInternes,
);
echo implode(", ", $links);
?>
</p>
<?php endif; ?>
<?php if (!empty($juryLecteursExternes)): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Lecteur·ice(s) externe :</span>
<?php
$links = array_map(
fn($n) => '<a href="/search?query=' . urlencode($n) . '">' . htmlspecialchars($n) . '</a>',
$juryLecteursExternes,
);
echo implode(", ", $links);
?>
</p>
<?php endif; ?>
<?php if (!empty($data["access_type"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Accès :</span>
<?= htmlspecialchars($data["access_type"]) ?>
</p>
<?php endif; ?>
<?php
$_cc2r = !empty($data["cc2r"]);
$_lic = !empty($data["license_type"]) ? $data["license_type"] : ($data["license_custom"] ?? null);
if ($_cc2r || $_lic): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Licence :</span>
<?= htmlspecialchars(implode(", ", array_filter([$_cc2r ? "CC2r" : null, $_lic]))) ?>
</p>
<?php endif; ?>
<?php if (!empty($data["context_note"])): ?>
<p class="tfe-meta-item tfe-meta-note">
<span class="tfe-meta-label">Note contextuelle relative à soutenance :</span>
<span class="tfe-note-value"><?= nl2br(htmlspecialchars($data["context_note"])) ?></span>
</p>
<?php endif; ?>
<?php if (!empty($data["contact_visible"])): ?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Contact :</span>
<?php
$_contact = $data["contact_visible"];
$_isUrl = filter_var($_contact, FILTER_VALIDATE_URL) !== false;
$_isEmail = !$_isUrl && str_contains($_contact, "@");
if ($_isUrl):
$_host = parse_url($_contact, PHP_URL_HOST);
$_path = parse_url($_contact, PHP_URL_PATH) ?? '';
$_isInstagram = $_host && str_contains($_host, 'instagram.com');
$_isMastodon = $_path && str_contains($_path, '/@');
if ($_isInstagram):
$_username = trim($_path, '/');
$_display = $_username ? '@' . $_username : preg_replace("#^https?://(www\.)?#i", "", rtrim($_contact, "/"));
elseif ($_isMastodon):
$_username = trim($_path, '/@');
$_display = $_username ? '@' . $_username : preg_replace("#^https?://(www\.)?#i", "", rtrim($_contact, "/"));
else:
$_display = preg_replace("#^https?://(www\.)?#i", "", rtrim($_contact, "/"));
endif; ?>
<a href="<?= htmlspecialchars($_contact) ?>" target="_blank" rel="noopener">
<?= htmlspecialchars($_display) ?>
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
<?php elseif ($_isEmail): ?>
<a href="<?= EmailObfuscator::mailto($_contact) ?>"><?= htmlspecialchars($_contact) ?></a>
<?php else: ?>
<?= htmlspecialchars($_contact) ?>
<?php endif; ?>
</p>
<?php endif; ?>
<?php if (!empty($data["baiu_link"])): ?>
<?php
$_baiuHref = htmlspecialchars($data["baiu_link"]);
$_baiuLabel = preg_replace("#^https?://(www\\.)?#i", "", rtrim($data["baiu_link"], "/"));
?>
<p class="tfe-meta-item">
<span class="tfe-meta-label">Lien :</span>
<a href="<?= $_baiuHref ?>" target="_blank" rel="noopener">
<?= htmlspecialchars($_baiuLabel) ?>
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
</p>
<?php endif; ?>
</div>
<?php if (!empty($data["synopsis"])): ?>
<div class="tfe-synopsis-text">
<?= nl2br(htmlspecialchars($data["synopsis"])) ?>
</div>
<?php else: ?>
<div class="tfe-synopsis-text tfe-synopsis-empty"></div>
<?php endif; ?>
</div>
<!-- ROW 3: All files — flex container -->
<div class="tfe-files">
<?php $_videoIndex = 0; ?>
<?php if ($isInterdit): ?>
<p class="tfe-restricted">
<?= htmlspecialchars(
$forbiddenMessage ??
"Ce TFE n'est pas disponible en ligne.",
) ?>
</p>
<?php elseif ($shouldHideFiles): ?>
<div class="tfe-restricted-access">
<p class="tfe-restricted-message">
<strong>Accès restreint</strong><br>
<?= htmlspecialchars(
$restrictedMessage ??
'Les fichiers attachés à ce TFE sont réservés aux utilisateur·ices autorisé·es.',
) ?>
</p>
<form id="access-request-form" class="tfe-access-request-form"
data-thesis-id="<?= $thesisId ?>">
<input type="hidden" name="csrf_token"
value="<?= htmlspecialchars(
$_SESSION["csrf_token"] ?? "",
) ?>">
<div class="form-group">
<label for="access-email">Votre adresse email :</label>
<input type="email"
id="access-email"
name="email"
required
placeholder="votre@email.com">
</div>
<div id="justification-container" class="form-group" style="display: none;">
<label for="access-justification">Pourquoi souhaitez-vous accéder à ce TFE ?</label>
<textarea id="access-justification"
name="justification"
rows="4"
placeholder="Décrivez brièvement votre motivation (recherche, collaboration, etc.)"></textarea>
</div>
<button type="submit" class="btn btn--primary tfe-btn-request-access">
Demander l'accès
</button>
<div id="access-request-message" class="tfe-access-message" style="display: none;"></div>
</form>
</div>
<?php elseif (!empty($data["files"])): ?>
<?php
// Preload PeerTube instance URL once
$_ptHasAnyPeerTube = false;
foreach ($data['files'] as $_f) {
if (str_starts_with($_f['file_path'] ?? '', 'peertube_ids:')) {
$_ptHasAnyPeerTube = true;
break;
}
}
$_ptInstanceUrl = '';
if ($_ptHasAnyPeerTube) {
require_once APP_ROOT . '/src/PeerTubeService.php';
$_ptSettings = PeerTubeService::getSettings(Database::getInstance());
$_ptInstanceUrl = $_ptSettings['instance_url'] ?? '';
}
?>
<?php foreach ($data["files"] as $file): ?>
<?php
$ext = strtolower(pathinfo($file["file_path"] ?? '', PATHINFO_EXTENSION));
$fileType = $file["file_type"] ?? '';
// Skip helper/internal types
if ($ext === 'vtt' || $fileType === 'caption') continue;
if ($fileType === 'cover') continue;
// Determine display category
$isImage = in_array($ext, ['jpg','jpeg','png','gif','bmp','webp'], true) || $fileType === 'image';
$isVideo = in_array($ext, ['mp4','webm','mov','ogv'], true) || $fileType === 'video';
$isAudio = in_array($ext, ['mp3','ogg','oga','wav','flac','aac','m4a'], true) || $fileType === 'audio';
$isPdf = ($ext === 'pdf') || $fileType === 'main';
$isWebsite = ($fileType === 'website');
$isOther = !($isImage || $isVideo || $isAudio || $isPdf || $isWebsite);
$_vttPath = null;
if ($isVideo) {
$_vttPath = $captionFiles[$_videoIndex] ?? null;
$_videoIndex++;
}
$caption = !empty($file["display_label"]) ? $file["display_label"] : ($file["description"] ?? '');
$filePath = $file['file_path'] ?? '';
$isExternalUrl = str_starts_with($filePath, 'http://') || str_starts_with($filePath, 'https://');
$isPeerTube = str_starts_with($filePath, 'peertube_ids:');
$mediaUrl = $isPeerTube ? '' : ($isExternalUrl ? htmlspecialchars($filePath) : ('/media?path=' . urlencode($filePath)));
$fileName = htmlspecialchars($file["file_name"] ?? basename($filePath));
?>
<div class="tfe-file-item">
<?php if ($isPdf): ?>
<iframe src="<?= $mediaUrl ?>"
width="100%" height="700px"
style="border:none"
title="<?= $fileName ?>">
</iframe>
<?php elseif ($isWebsite): ?>
<iframe src="<?= $mediaUrl ?>"
width="100%" height="700px"
style="border:none"
title="<?= $fileName ?>"
sandbox="allow-scripts allow-same-origin"
loading="lazy">
</iframe>
<p class="tfe-pdf-fallback">
<a href="<?= $mediaUrl ?>" target="_blank" rel="noopener">
Ouvrir le site dans un nouvel onglet
<span class="sr-only">(ouvre dans un nouvel onglet)</span>
</a>
</p>
<?php elseif ($isImage): ?>
<img src="<?= $mediaUrl ?>"
alt="<?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' — ' . ($data['authors'] ?? '')) ?>">
<?php elseif ($isVideo): ?>
<?php if ($isPeerTube): ?>
<?php
$peertubeUuid = substr($filePath, strlen('peertube_ids:'));
$title = $fileName;
$instanceUrl = $_ptInstanceUrl;
include APP_ROOT . '/templates/partials/peertube-embed.php';
?>
<?php else: ?>
<video width="100%" controls>
<source src="<?= $mediaUrl ?>" type="video/<?= htmlspecialchars($ext === 'mov' ? 'mp4' : $ext) ?>">
<?php if ($_vttPath): ?>
<track kind="captions"
src="/media?path=<?= urlencode($_vttPath) ?>"
srclang="fr" label="Sous-titres" default>
<?php endif; ?>
</video>
<?php endif; ?>
<?php elseif ($isAudio): ?>
<?php if ($isPeerTube): ?>
<?php
$peertubeUuid = substr($filePath, strlen('peertube_ids:'));
$title = $fileName;
$instanceUrl = $_ptInstanceUrl;
$width = 560;
$height = 150;
include APP_ROOT . '/templates/partials/peertube-embed.php';
?>
<?php else: ?>
<audio controls class="tfe-audio">
<source src="<?= $mediaUrl ?>" type="audio/<?= htmlspecialchars(match($ext) {
'mp3' => 'mpeg',
'ogg', 'oga' => 'ogg',
'wav' => 'wav',
'flac' => 'flac',
'aac' => 'aac',
'm4a' => 'mp4',
default => $ext,
}) ?>">
Votre navigateur ne supporte pas la lecture audio.
</audio>
<?php endif; ?>
<?php else: /* other — download only */ ?>
<div class="tfe-download-file">
<a href="<?= $mediaUrl ?>&download=1" class="tfe-download-link">
<span class="tfe-download-icon">📎</span>
<span><?= $fileName ?></span>
</a>
<?php if (!empty($file['file_size'])): ?>
<small class="tfe-download-size"><?= number_format($file['file_size'] / 1024 / 1024, 2) ?> MB</small>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($caption !== '' && !$isOther): ?>
<figcaption><?= htmlspecialchars($caption) ?></figcaption>
<?php endif; ?>
</div>
<?php endforeach; ?>
<?php else: ?>
<p class="tfe-no-files">Aucun fichier disponible pour ce TFE.</p>
<?php endif; ?>
</div>
</article>
</main>