mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
- fix: 403 on /language-autre-fragment.php — add explicit nginx location block
The nginx catch-all blocked direct access
to all PHP files except /index.php and files inside /admin/.
language-autre-fragment.php lives at the public root and is POSTed to by
HTMX from both the admin edit form and the partage form. Added an explicit
fastcgi block so it is executed
rather than denied.
- fix: replace .php-suffixed public URLs blocked by nginx catch-all
Audit of all client-facing PHP URL references against nginx routing:
- fetch('/request-access.php') in tfe.php -> '/request-access'
(clean URL already routed by Dispatcher)
- /media.php?path= in form.php (x2) and admin/recapitulatif.php -> /media?path=
(nginx only has location = /media, no location for /media.php)
All these .php-suffixed URLs hit the nginx catch-all
location ~ \.php$ { deny all; }
which takes precedence over location / { try_files ... } for regex matches.
532 lines
24 KiB
PHP
532 lines
24 KiB
PHP
<main class="tfe-main" id="main-content">
|
||
<article class="tfe-layout">
|
||
|
||
<!-- LEFT: info — article header -->
|
||
<section 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($promoteursUlb)): ?>
|
||
<div>
|
||
<dt>Promoteur·ice ULB :</dt>
|
||
<dd><?php
|
||
$links = array_map(
|
||
fn($n) => '<a href="/search?query=' .
|
||
urlencode($n) .
|
||
'">' .
|
||
htmlspecialchars($n) .
|
||
"</a>",
|
||
$promoteursUlb,
|
||
);
|
||
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($juryLecteursInternes)): ?>
|
||
<div>
|
||
<dt>Lecteur·ice(s) interne :</dt>
|
||
<dd><?php
|
||
$links = array_map(
|
||
fn($n) => '<a href="/search?query=' .
|
||
urlencode($n) .
|
||
'">' .
|
||
htmlspecialchars($n) .
|
||
"</a>",
|
||
$juryLecteursInternes,
|
||
);
|
||
echo implode(", ", $links);
|
||
?></dd>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<?php if (!empty($juryLecteursExternes)): ?>
|
||
<div>
|
||
<dt>Lecteur·ice(s) externe :</dt>
|
||
<dd><?php
|
||
$links = array_map(
|
||
fn($n) => '<a href="/search?query=' .
|
||
urlencode($n) .
|
||
'">' .
|
||
htmlspecialchars($n) .
|
||
"</a>",
|
||
$juryLecteursExternes,
|
||
);
|
||
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; ?>
|
||
</section>
|
||
|
||
<!-- RIGHT: media — supplementary aside -->
|
||
<section class="tfe-right">
|
||
<?php $_videoIndex = 0; ?>
|
||
<?php if ($isInterdit): ?>
|
||
<p class="tfe-restricted">
|
||
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>
|
||
Les fichiers attachés à ce TFE sont réservés aux utilisateurs autorisés.
|
||
</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>
|
||
|
||
<script>
|
||
(function() {
|
||
const form = document.getElementById('access-request-form');
|
||
const emailInput = document.getElementById('access-email');
|
||
const justificationContainer = document.getElementById('justification-container');
|
||
const justificationInput = document.getElementById('access-justification');
|
||
const messageDiv = document.getElementById('access-request-message');
|
||
|
||
// Show/hide justification based on email domain
|
||
emailInput.addEventListener('input', function() {
|
||
const email = this.value.trim().toLowerCase();
|
||
const isErg = email.endsWith('@erg.school') || email.endsWith('@erg.be');
|
||
justificationContainer.style.display = isErg ? 'none' : 'block';
|
||
justificationInput.required = !isErg;
|
||
});
|
||
|
||
function showRetryPrompt(rejectedEmail, serverMessage) {
|
||
messageDiv.style.display = 'block';
|
||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||
messageDiv.innerHTML =
|
||
'<strong>Adresse e-mail introuvable sur le serveur de l\'ERG.</strong><br>' +
|
||
'<small>' + serverMessage.replace(/</g, '<') + '</small><br><br>' +
|
||
'Corrigez votre adresse e-mail et réessayez.';
|
||
// Highlight the email field and let the user fix it
|
||
emailInput.value = rejectedEmail;
|
||
emailInput.classList.add('input-error');
|
||
emailInput.focus();
|
||
emailInput.select();
|
||
// Remove error highlight once they start typing
|
||
emailInput.addEventListener('input', function clearError() {
|
||
emailInput.classList.remove('input-error');
|
||
emailInput.removeEventListener('input', clearError);
|
||
});
|
||
}
|
||
|
||
// Form submission
|
||
form.addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
const submitBtn = form.querySelector('button[type="submit"]');
|
||
submitBtn.disabled = true;
|
||
submitBtn.textContent = 'Envoi en cours...';
|
||
messageDiv.style.display = 'none';
|
||
|
||
const submittedEmail = emailInput.value.trim();
|
||
const formData = new FormData(form);
|
||
formData.append('thesis_id', '<?= $thesisId ?>');
|
||
|
||
fetch('/request-access', {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
submitBtn.disabled = false;
|
||
submitBtn.textContent = 'Demander l\'accès';
|
||
|
||
if (data.status === 'recipient_rejected') {
|
||
showRetryPrompt(submittedEmail, data.message);
|
||
return;
|
||
}
|
||
|
||
messageDiv.style.display = 'block';
|
||
if (data.success) {
|
||
messageDiv.className = 'tfe-access-message tfe-access-success';
|
||
messageDiv.textContent = data.message;
|
||
form.reset();
|
||
} else {
|
||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||
messageDiv.textContent = data.message || 'Une erreur est survenue. Veuillez réessayer.';
|
||
}
|
||
})
|
||
.catch(error => {
|
||
submitBtn.disabled = false;
|
||
submitBtn.textContent = 'Demander l\'accès';
|
||
messageDiv.style.display = 'block';
|
||
messageDiv.className = 'tfe-access-message tfe-access-error';
|
||
messageDiv.textContent = 'Erreur de connexion. Veuillez réessayer.';
|
||
});
|
||
});
|
||
})();
|
||
</script>
|
||
<?php elseif (!empty($data["files"])): ?>
|
||
<?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://');
|
||
$mediaUrl = $isExternalUrl ? htmlspecialchars($filePath) : ('/media?path=' . urlencode($filePath));
|
||
$fileName = htmlspecialchars($file["file_name"] ?? basename($filePath));
|
||
?>
|
||
<figure>
|
||
<?php if ($isPdf): ?>
|
||
<iframe src="<?= $mediaUrl ?>"
|
||
width="100%" height="700px"
|
||
style="border:none"
|
||
title="<?= $fileName ?>">
|
||
</iframe>
|
||
<p class="tfe-pdf-fallback">
|
||
<a href="<?= $mediaUrl ?>&download=1">Télécharger le PDF</a>
|
||
</p>
|
||
<?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): ?>
|
||
<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 elseif ($isAudio): ?>
|
||
<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 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; ?>
|
||
</figure>
|
||
<?php endforeach; ?>
|
||
<?php else: ?>
|
||
<p class="tfe-no-files">Aucun fichier disponible pour ce TFE.</p>
|
||
<?php endif; ?>
|
||
</section>
|
||
|
||
</article>
|
||
</main>
|