Add website-type TFE support: URLs stored as thesis_files rows, HTMX-toggle on Site web format

This commit is contained in:
Pontoporeia
2026-05-07 21:24:46 +02:00
parent 9dc7ea98f2
commit ac0008df6c
10 changed files with 399 additions and 39 deletions

14
TODO.md
View File

@@ -102,6 +102,20 @@
- [x] Migration `013_fix_csv_column_shift.sql`: move orientation from synopsis→orientation_id, finality from context_note→finality_id for already-imported theses - [x] Migration `013_fix_csv_column_shift.sql`: move orientation from synopsis→orientation_id, finality from context_note→finality_id for already-imported theses
- [x] Migration `013_fix_remarks_keywords.php`: move keywords from remarks→tags+thesis_tags for already-imported theses - [x] Migration `013_fix_remarks_keywords.php`: move keywords from remarks→tags+thesis_tags for already-imported theses
## Support website-type TFE (URL instead of uploaded files)
- [x] Add `file_type = 'website'` support to `thesis_files` — URL stored in `file_path`, no filesystem upload (no schema change needed)
- [x] Admin add form: "Site web (URL)" field dynamically shown via HTMX when "Site web" format checked
- [x] Admin edit form: website URL field via HTMX toggle + recognize website (🌐 icon) in existing-files list
- [x] Student partage form: website URL field via HTMX toggle + HTMX script added
- [x] TFE detail page (`tfe.php`): render `website` type as iframe with sandbox in media section
- [x] `ThesisCreateController`: handle website URL in submit → `handleWebsiteUrl()` stores as thesis_files row
- [x] `ThesisEditController`: handle website URL in save → `handleWebsiteUrl()` replaces existing website row; delete-files skips unlink for URLs
- [x] Edit page: website rows deletable via same delete_files checkbox mechanism
- [x] File size 0 for website rows — hidden in edit list to avoid showing "0.00 MB"
- [x] HTMX fragment endpoint: `/admin/actions/format-website-fragment.php` + `/partage/format-website-fragment`
- [x] `checkbox-list.php` partial: optional `hxPost`/`hxTarget` for HTMX live update
- [x] Server-side initial render: pre-populate `#website-url-section` if "Site web" already checked
## Standardise répertoire filter column rendering ## Standardise répertoire filter column rendering
- [x] Centralise filter column rendering into a shared `repFilterEntry()` function - [x] Centralise filter column rendering into a shared `repFilterEntry()` function
- [x] Define `$filterColumns` config array as single source of truth for the 5 filter columns - [x] Define `$filterColumns` config array as single source of truth for the 5 filter columns

View File

@@ -204,6 +204,11 @@
} }
/* ── Jury fieldset ──────────────────────────────────────────────────────── */ /* ── Jury fieldset ──────────────────────────────────────────────────────── */
/* ── Website-URL inline fieldset (shown/hidden via HTMX) ────────────────── */
/* The fieldset is shown/hidden via outerHTML swap — no CSS needed */
/* ── Jury fieldset (continued) ──────────────────────────────────────────── */
.admin-body fieldset fieldset.admin-jury-lecteurs, .admin-body fieldset fieldset.admin-jury-lecteurs,
.student-body fieldset fieldset.admin-jury-lecteurs { .student-body fieldset fieldset.admin-jury-lecteurs {
border: none; border: none;

View File

@@ -0,0 +1,66 @@
<?php
/**
* format-website-fragment.php (partage)
*
* HTMX fragment for the student share form: returns the website URL input fields
* if "Site web" is among the currently selected format checkboxes.
*
* Regular PHP include inside partage/index.php routing — no separate bootstrap.
* The parent partage/index.php already handles boot + session.
*
* Expected POST:
* - formats[]: array of selected format_type IDs
* - website_url: current website_url value (for repopulation)
* - website_label: current website_label value (for repopulation)
*/
// Find the "Site web" format ID
$stmt = Database::getInstance()->getConnection()->prepare(
'SELECT id FROM format_types WHERE name = ? LIMIT 1'
);
$stmt->execute(['Site web']);
$websiteFormatId = $stmt->fetchColumn();
if (!$websiteFormatId) {
echo '';
exit;
}
$selectedFormats = isset($_POST['formats']) && is_array($_POST['formats'])
? array_map('intval', $_POST['formats'])
: [];
if (!in_array((int)$websiteFormatId, $selectedFormats, true)) {
echo '';
exit;
}
$websiteUrl = htmlspecialchars($_POST['website_url'] ?? '');
$websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
?>
<fieldset id="website-url-fieldset">
<legend>Site web</legend>
<div class="admin-form-group">
<label for="website_url">URL du site :</label>
<div class="admin-file-input">
<input type="url"
id="website_url"
name="website_url"
value="<?= $websiteUrl ?>"
placeholder="https://mon-tfe.erg.be">
<small>Si le TFE est un site web, entrez son URL ici. Il sera affiché comme un site embarqué sur la page du TFE.</small>
</div>
</div>
<div class="admin-form-group">
<label for="website_label">Légende :</label>
<input type="text"
id="website_label"
name="website_label"
value="<?= $websiteLabel ?>"
placeholder="Description du site (optionnel)"
class="admin-file-label-input"
style="max-width:400px;">
</div>
</fieldset>

View File

@@ -21,6 +21,13 @@ $parts = explode('/', $path);
$slug = $parts[0] ?? ''; $slug = $parts[0] ?? '';
$action = $parts[1] ?? ''; $action = $parts[1] ?? '';
// Special route: /partage/format-website-fragment (HTMX fragment, no auth needed)
if ($slug === 'format-website-fragment' && $_SERVER['REQUEST_METHOD'] === 'POST') {
App::boot();
require_once __DIR__ . '/format-website-fragment.php';
exit;
}
// Special route: /partage/recapitulatif?id=N // Special route: /partage/recapitulatif?id=N
if ($slug === 'recapitulatif' || $slug === 'recapitulatif.php') { if ($slug === 'recapitulatif' || $slug === 'recapitulatif.php') {
App::boot(); App::boot();
@@ -265,6 +272,7 @@ function renderShareLinkForm(string $slug, array $link): void
<link rel="stylesheet" href="<?= App::assetV('/assets/css/form.css') ?>"> <link rel="stylesheet" href="<?= App::assetV('/assets/css/form.css') ?>">
<script src="<?= App::assetV('/assets/js/sortable.min.js') ?>" defer></script> <script src="<?= App::assetV('/assets/js/sortable.min.js') ?>" defer></script>
<script src="<?= App::assetV('/assets/js/file-upload-queue.js') ?>" defer></script> <script src="<?= App::assetV('/assets/js/file-upload-queue.js') ?>" defer></script>
<script src="<?= App::assetV('/assets/js/htmx.min.js') ?>" defer></script>
</head> </head>
<body class="student-body"> <body class="student-body">
<main id="main-content"> <main id="main-content">
@@ -319,12 +327,6 @@ function renderShareLinkForm(string $slug, array $link): void
<?php $name = 'language_autre'; $label = 'Autre(s) langue(s) :'; $value = old($formData, 'language_autre'); $hint = 'Si votre TFE contient une langue absente de la liste, précisez-la ici.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?> <?php $name = 'language_autre'; $label = 'Autre(s) langue(s) :'; $value = old($formData, 'language_autre'); $hint = 'Si votre TFE contient une langue absente de la liste, précisez-la ici.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
</fieldset> </fieldset>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
<legend>Format(s)</legend>
<?php $name = 'formats'; $label = 'Format(s) du TFE :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; $required = true; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
</fieldset>
<!-- ═══════════════════ Mots-clés ═══════════════════ --> <!-- ═══════════════════ Mots-clés ═══════════════════ -->
<fieldset> <fieldset>
<legend>Mots-clés</legend> <legend>Mots-clés</legend>
@@ -368,6 +370,19 @@ function renderShareLinkForm(string $slug, array $link): void
require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; require APP_ROOT . '/templates/partials/form/jury-fieldset.php';
?> ?>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
<legend>Format(s)</legend>
<?php
$name = 'formats'; $label = 'Format(s) du TFE :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; $required = true;
$hxPost = '/partage/format-website-fragment';
$hxTarget = '#website-url-fieldset';
// Capture before include unsets it
$_checkedFormatsForSiteWeb = $checked;
include APP_ROOT . '/templates/partials/form/checkbox-list.php';
?>
</fieldset>
<!-- ═══════════════════ Fichiers ═══════════════════ --> <!-- ═══════════════════ Fichiers ═══════════════════ -->
<?php <?php
$helpContent = $helpFn('fieldset_files'); $helpContent = $helpFn('fieldset_files');
@@ -375,6 +390,43 @@ function renderShareLinkForm(string $slug, array $link): void
include APP_ROOT . '/templates/partials/form/fieldset-files.php'; include APP_ROOT . '/templates/partials/form/fieldset-files.php';
?> ?>
<!-- Website URL fieldset — shown/hidden via HTMX when "Site web" checked -->
<fieldset id="website-url-fieldset" style="display:none">
<legend>Site web</legend>
<div class="admin-form-group">
<label for="website_url">URL du site :</label>
<div class="admin-file-input">
<input type="url"
id="website_url"
name="website_url"
value="<?= htmlspecialchars($formData['website_url'] ?? '') ?>"
placeholder="https://mon-tfe.erg.be">
<small>Si le TFE est un site web, entrez son URL ici. Il sera affiché comme un site embarqué sur la page du TFE.</small>
</div>
</div>
<div class="admin-form-group">
<label for="website_label">Légende :</label>
<input type="text"
id="website_label"
name="website_label"
value="<?= htmlspecialchars($formData['website_label'] ?? '') ?>"
placeholder="Description du site (optionnel)"
class="admin-file-label-input"
style="max-width:400px;">
</div>
</fieldset>
<?php
// Server-side: show if Site web already checked (e.g. on error redirect)
$_stmt = Database::getInstance()->getConnection()->prepare('SELECT id FROM format_types WHERE name = ? LIMIT 1');
$_stmt->execute(['Site web']);
$_siteWebId = $_stmt->fetchColumn();
if ($_siteWebId && in_array((string)$_siteWebId, array_map('strval', $_checkedFormatsForSiteWeb), true)) {
echo '<script>document.getElementById("website-url-fieldset").style.display=""</script>';
}
?>
<!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ --> <!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ -->
<?php <?php
$oldFn = $shareOldFn; $oldFn = $shareOldFn;

View File

@@ -216,6 +216,9 @@ class ThesisCreateController
$this->db->handleBannerUpload($thesisId, $files['banner'] ?? null); $this->db->handleBannerUpload($thesisId, $files['banner'] ?? null);
$this->handleThesisFiles($thesisId, $data['annee'], $identifier, $files['files'] ?? null, $authorSlug, $post); $this->handleThesisFiles($thesisId, $data['annee'], $identifier, $files['files'] ?? null, $authorSlug, $post);
// ── 6. Website URL — stored as thesis_files row ──────────────────────
$this->handleWebsiteUrl($thesisId, $post);
return $thesisId; return $thesisId;
} }
@@ -813,4 +816,41 @@ class ThesisCreateController
} }
return $candidate; return $candidate;
} }
/**
* Store a website URL as a thesis_files row (file_type = 'website').
*
* The URL is stored in file_path; no filesystem operation is performed.
* label and sort_order from the POST are preserved.
*/
private function handleWebsiteUrl(int $thesisId, array $post): void
{
$websiteUrl = trim($post['website_url'] ?? '');
if ($websiteUrl === '') {
return;
}
// Validate URL
$websiteUrl = filter_var($websiteUrl, FILTER_VALIDATE_URL);
if ($websiteUrl === false) {
error_log('ThesisCreateController: invalid website URL, skipping');
return;
}
$label = trim($post['website_label'] ?? '');
$sortOrder = isset($post['website_order']) ? (int)$post['website_order'] : null;
$fileName = rtrim(preg_replace('#^https?://#i', '', $websiteUrl), '/');
$this->db->insertThesisFile(
$thesisId,
'website',
$websiteUrl,
$fileName,
0,
'text/html',
$label !== '' ? $label : null,
$sortOrder
);
error_log("ThesisCreateController: website stored → $websiteUrl");
}
} }

View File

@@ -331,12 +331,15 @@ class ThesisEditController
} }
$filePath = $this->db->deleteThesisFile($fileId, $thesisId); $filePath = $this->db->deleteThesisFile($fileId, $thesisId);
if ($filePath && defined('STORAGE_ROOT')) { if ($filePath && defined('STORAGE_ROOT')) {
// Skip filesystem deletion for website URLs (not real files)
if (!str_starts_with($filePath, 'http://') && !str_starts_with($filePath, 'https://')) {
$abs = STORAGE_ROOT . '/' . $filePath; $abs = STORAGE_ROOT . '/' . $filePath;
if (file_exists($abs)) { if (file_exists($abs)) {
@unlink($abs); @unlink($abs);
} }
} }
} }
}
// ── Reorder existing files ──────────────────────────────────────────── // ── Reorder existing files ────────────────────────────────────────────
if (!empty($post['file_sort_order']) && is_array($post['file_sort_order'])) { if (!empty($post['file_sort_order']) && is_array($post['file_sort_order'])) {
@@ -358,6 +361,9 @@ class ThesisEditController
if (!empty($files['files']['name'][0])) { if (!empty($files['files']['name'][0])) {
$this->handleThesisFiles($thesisId, $post, $files['files']); $this->handleThesisFiles($thesisId, $post, $files['files']);
} }
// ── Website URL — add or update ──────────────────────────────────────
$this->handleWebsiteUrl($thesisId, $post);
} }
// ── Private: file uploads ───────────────────────────────────────────────── // ── Private: file uploads ─────────────────────────────────────────────────
@@ -703,4 +709,50 @@ class ThesisEditController
} }
return $info; return $info;
} }
/**
* Add or update a website URL thesis_file row.
*
* If a website row already exists for this thesis, it is replaced.
* Otherwise a new row is inserted.
*/
private function handleWebsiteUrl(int $thesisId, array $post): void
{
$websiteUrl = trim($post['website_url'] ?? '');
// Remove existing website rows
$existingFiles = $this->db->getThesisFiles($thesisId);
foreach ($existingFiles as $f) {
if ($f['file_type'] === 'website') {
$this->db->deleteThesisFile((int)$f['id'], $thesisId);
}
}
if ($websiteUrl === '') {
return;
}
// Validate URL
$websiteUrl = filter_var($websiteUrl, FILTER_VALIDATE_URL);
if ($websiteUrl === false) {
error_log('ThesisEditController: invalid website URL, skipping');
return;
}
$label = trim($post['website_label'] ?? '');
$sortOrder = isset($post['website_order']) ? (int)$post['website_order'] : null;
$fileName = rtrim(preg_replace('#^https?://#i', '', $websiteUrl), '/');
$this->db->insertThesisFile(
$thesisId,
'website',
$websiteUrl,
$fileName,
0,
'text/html',
$label !== '' ? $label : null,
$sortOrder
);
error_log("ThesisEditController: website stored → $websiteUrl");
}
} }

View File

@@ -36,12 +36,6 @@
<?php $name = 'language_autre'; $label = 'Autre(s) langue(s) :'; $value = old('language_autre'); $hint = 'Si votre TFE contient une langue absente de la liste, précisez-la ici.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?> <?php $name = 'language_autre'; $label = 'Autre(s) langue(s) :'; $value = old('language_autre'); $hint = 'Si votre TFE contient une langue absente de la liste, précisez-la ici.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
</fieldset> </fieldset>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
<legend>Format(s)</legend>
<?php $name = 'formats'; $label = 'Format(s) du TFE :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; $required = true; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
</fieldset>
<!-- ═══════════════════ Mots-clés ═══════════════════ --> <!-- ═══════════════════ Mots-clés ═══════════════════ -->
<fieldset> <fieldset>
<legend>Mots-clés</legend> <legend>Mots-clés</legend>
@@ -74,9 +68,59 @@
require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; require APP_ROOT . '/templates/partials/form/jury-fieldset.php';
?> ?>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
<legend>Format(s)</legend>
<?php
$name = 'formats'; $label = 'Format(s) du TFE :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; $required = true;
$hxPost = '/partage/format-website-fragment';
$hxTarget = '#website-url-fieldset';
// Capture before include unsets it
$_checkedFormatsForSiteWeb = $checked;
include APP_ROOT . '/templates/partials/form/checkbox-list.php';
?>
</fieldset>
<!-- ═══════════════════ Fichiers ═══════════════════ --> <!-- ═══════════════════ Fichiers ═══════════════════ -->
<?php include APP_ROOT . '/templates/partials/form/fieldset-files.php'; ?> <?php include APP_ROOT . '/templates/partials/form/fieldset-files.php'; ?>
<!-- Website URL fieldset — shown/hidden via HTMX when "Site web" checked -->
<fieldset id="website-url-fieldset" style="display:none">
<legend>Site web</legend>
<div class="admin-form-group">
<label for="website_url">URL du site :</label>
<div class="admin-file-input">
<input type="url"
id="website_url"
name="website_url"
value="<?= htmlspecialchars($formData['website_url'] ?? '') ?>"
placeholder="https://mon-tfe.erg.be">
<small>Si le TFE est un site web, entrez son URL ici. Il sera affiché comme un site embarqué sur la page du TFE.</small>
</div>
</div>
<div class="admin-form-group">
<label for="website_label">Légende :</label>
<input type="text"
id="website_label"
name="website_label"
value="<?= htmlspecialchars($formData['website_label'] ?? '') ?>"
placeholder="Description du site (optionnel)"
class="admin-file-label-input"
style="max-width:400px;">
</div>
</fieldset>
<?php
// Server-side: show if Site web already checked (e.g. on error redirect)
$_stmt = Database::getInstance()->getConnection()->prepare('SELECT id FROM format_types WHERE name = ? LIMIT 1');
$_stmt->execute(['Site web']);
$_siteWebId = $_stmt->fetchColumn();
if ($_siteWebId && in_array((string)$_siteWebId, array_map('strval', $_checkedFormatsForSiteWeb), true)) {
echo '<script>document.getElementById("website-url-fieldset").style.display=""</script>';
}
?>
<!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ --> <!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ -->
<?php <?php
$oldFn = 'old'; $oldFn = 'old';

View File

@@ -60,16 +60,6 @@
<?php $name = 'language_autre'; $label = 'Autre(s) langue(s) :'; $value = old('language_autre'); include APP_ROOT . '/templates/partials/form/text-field.php'; ?> <?php $name = 'language_autre'; $label = 'Autre(s) langue(s) :'; $value = old('language_autre'); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
</fieldset> </fieldset>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
<legend>Format(s)</legend>
<?php
$checkedFormats = $formData['formats'] ?? $currentFormats;
$name = 'formats'; $label = 'Format(s) du TFE :'; $options = $formatTypes; $checked = $checkedFormats; $required = true;
include APP_ROOT . '/templates/partials/form/checkbox-list.php';
?>
</fieldset>
<!-- ═══════════════════ Mots-clés ═══════════════════ --> <!-- ═══════════════════ Mots-clés ═══════════════════ -->
<fieldset> <fieldset>
<legend>Mots-clés</legend> <legend>Mots-clés</legend>
@@ -132,6 +122,20 @@
require APP_ROOT . '/templates/partials/form/jury-fieldset.php'; require APP_ROOT . '/templates/partials/form/jury-fieldset.php';
?> ?>
<!-- ═══════════════════ Format(s) ═══════════════════ -->
<fieldset>
<legend>Format(s)</legend>
<?php
$checkedFormats = $formData['formats'] ?? $currentFormats ?? [];
$name = 'formats'; $label = 'Format(s) du TFE :'; $options = $formatTypes; $checked = $checkedFormats; $required = true;
$hxPost = '/partage/format-website-fragment';
$hxTarget = '#website-url-fieldset';
// Capture before include unsets it
$_checkedFormatsForSiteWeb = $checked;
include APP_ROOT . '/templates/partials/form/checkbox-list.php';
?>
</fieldset>
<!-- ═══════════════════ Fichiers ═══════════════════ --> <!-- ═══════════════════ Fichiers ═══════════════════ -->
<fieldset> <fieldset>
<legend>Fichiers</legend> <legend>Fichiers</legend>
@@ -173,20 +177,25 @@
$fType === 'video' || in_array($fExt, ['mp4','webm','mov','ogv']) => '🎬', $fType === 'video' || in_array($fExt, ['mp4','webm','mov','ogv']) => '🎬',
$fType === 'audio' || in_array($fExt, ['mp3','ogg','wav','flac','aac','m4a']) => '🔊', $fType === 'audio' || in_array($fExt, ['mp3','ogg','wav','flac','aac','m4a']) => '🔊',
$fType === 'caption' || $fExt === 'vtt' => '💬', $fType === 'caption' || $fExt === 'vtt' => '💬',
$fType === 'website' => '🌐',
default => '📎', default => '📎',
}; };
$isExternalUrl = str_starts_with($f['file_path'] ?? '', 'http://') || str_starts_with($f['file_path'] ?? '', 'https://');
$fLinkHref = $isExternalUrl
? htmlspecialchars($f['file_path'])
: ('/media.php?path=' . urlencode($f['file_path']));
?> ?>
<li class="admin-file-list-item" data-file-id="<?= (int)$f['id'] ?>"> <li class="admin-file-list-item" data-file-id="<?= (int)$f['id'] ?>">
<input type="hidden" name="file_sort_order[]" value="<?= (int)$f['id'] ?>"> <input type="hidden" name="file_sort_order[]" value="<?= (int)$f['id'] ?>">
<span class="admin-file-drag-handle" title="Réordonner">⠿</span> <span class="admin-file-drag-handle" title="Réordonner">⠿</span>
<span class="admin-file-icon-col"><?= $fIcon ?></span> <span class="admin-file-icon-col"><?= $fIcon ?></span>
<span class="admin-file-info"> <span class="admin-file-info">
<a href="/media.php?path=<?= urlencode($f['file_path']) ?>" target="_blank" rel="noopener" class="admin-file-name"> <a href="<?= $fLinkHref ?>" target="_blank" rel="noopener" class="admin-file-name">
<?= htmlspecialchars($f['file_name'] ?? basename($f['file_path'])) ?> <?= htmlspecialchars($f['file_name'] ?? basename($f['file_path'])) ?>
</a> </a>
<span class="admin-file-meta-row"> <span class="admin-file-meta-row">
<span class="admin-file-type-badge"><?= htmlspecialchars($fType) ?></span> <span class="admin-file-type-badge"><?= htmlspecialchars($fType) ?></span>
<?php if (!empty($f['file_size'])): ?> <?php if (!empty($f['file_size']) && $f['file_size'] > 0): ?>
<span class="admin-file-size"><?= number_format($f['file_size'] / 1024 / 1024, 2) ?> MB</span> <span class="admin-file-size"><?= number_format($f['file_size'] / 1024 / 1024, 2) ?> MB</span>
<?php endif; ?> <?php endif; ?>
</span> </span>
@@ -239,6 +248,55 @@
</div> </div>
</fieldset> </fieldset>
<!-- Website URL fieldset — shown/hidden via HTMX when "Site web" checked -->
<?php
// Extract existing website URL/label from thesis_files for initial render
$existingWebsite = null;
$existingWebsiteLabel = '';
foreach ($currentFiles as $f) {
if ($f['file_type'] === 'website') {
$existingWebsite = $f['file_path'] ?? '';
$existingWebsiteLabel = $f['display_label'] ?? '';
break;
}
}
?>
<fieldset id="website-url-fieldset" style="display:none">
<legend>Site web</legend>
<div class="admin-form-group">
<label for="website_url">URL du site :</label>
<div class="admin-file-input">
<input type="url"
id="website_url"
name="website_url"
value="<?= htmlspecialchars($existingWebsite ?? '') ?>"
placeholder="https://mon-tfe.erg.be">
<small>Si le TFE est un site web, entrez son URL ici. Il sera affiché comme un site embarqué sur la page du TFE.</small>
</div>
</div>
<div class="admin-form-group">
<label for="website_label">Légende :</label>
<input type="text"
id="website_label"
name="website_label"
value="<?= htmlspecialchars($existingWebsiteLabel) ?>"
placeholder="Description du site (optionnel)"
class="admin-file-label-input"
style="max-width:400px;">
</div>
</fieldset>
<?php
// Server-side: show if Site web already checked
$_stmt = Database::getInstance()->getConnection()->prepare('SELECT id FROM format_types WHERE name = ? LIMIT 1');
$_stmt->execute(['Site web']);
$_siteWebId = $_stmt->fetchColumn();
if ($_siteWebId && in_array((string)$_siteWebId, array_map('strval', $_checkedFormatsForSiteWeb), true)) {
echo '<script>document.getElementById("website-url-fieldset").style.display=""</script>';
}
?>
<!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ --> <!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ -->
<?php <?php
$editMetaFormData = [ $editMetaFormData = [

View File

@@ -13,14 +13,26 @@
* array $options — each element must have 'id' and 'name' keys * array $options — each element must have 'id' and 'name' keys
* array $checked — array of 'id' values that are currently checked * array $checked — array of 'id' values that are currently checked
* bool $required — whether at least one checkbox must be checked; default false * bool $required — whether at least one checkbox must be checked; default false
* string $hxPost — optional hx-post URL for HTMX live update
* string $hxTarget — optional hx-target CSS selector for HTMX swap
*/ */
$checked = $checked ?? []; $checked = $checked ?? [];
$required = $required ?? false; $required = $required ?? false;
$hxPost = $hxPost ?? '';
$hxTarget = $hxTarget ?? '';
?> ?>
<div> <div>
<span class="admin-row-label"><?= htmlspecialchars($label) ?><?= $required ? ' <span class="asterisk">*</span>' : '' ?></span> <span class="admin-row-label"><?= htmlspecialchars($label) ?><?= $required ? ' <span class="asterisk">*</span>' : '' ?></span>
<fieldset class="admin-checkbox-group"<?= $required ? ' required aria-required="true"' : '' ?>> <fieldset class="admin-checkbox-group"
<?= $required ? ' required aria-required="true"' : '' ?>
<?php if ($hxPost !== ''): ?>
hx-post="<?= htmlspecialchars($hxPost) ?>"
hx-target="<?= htmlspecialchars($hxTarget) ?>"
hx-trigger="change"
hx-include="this, #website-url-fieldset"
hx-swap="outerHTML"
<?php endif; ?>>
<legend class="sr-only"><?= htmlspecialchars($label) ?></legend> <legend class="sr-only"><?= htmlspecialchars($label) ?></legend>
<ul> <ul>
<?php foreach ($options as $opt): ?> <?php foreach ($options as $opt): ?>
@@ -38,4 +50,4 @@ $required = $required ?? false;
</fieldset> </fieldset>
</div> </div>
<?php <?php
unset($checked); unset($checked, $hxPost, $hxTarget);

View File

@@ -442,7 +442,8 @@
$isVideo = in_array($ext, ['mp4','webm','mov','ogv'], true) || $fileType === 'video'; $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'; $isAudio = in_array($ext, ['mp3','ogg','oga','wav','flac','aac','m4a'], true) || $fileType === 'audio';
$isPdf = ($ext === 'pdf') || $fileType === 'main'; $isPdf = ($ext === 'pdf') || $fileType === 'main';
$isOther = !($isImage || $isVideo || $isAudio || $isPdf); $isWebsite = ($fileType === 'website');
$isOther = !($isImage || $isVideo || $isAudio || $isPdf || $isWebsite);
$_vttPath = null; $_vttPath = null;
if ($isVideo) { if ($isVideo) {
@@ -451,8 +452,10 @@
} }
$caption = !empty($file["display_label"]) ? $file["display_label"] : ($file["description"] ?? ''); $caption = !empty($file["display_label"]) ? $file["display_label"] : ($file["description"] ?? '');
$mediaUrl = '/media?path=' . urlencode($file["file_path"]); $filePath = $file['file_path'] ?? '';
$fileName = htmlspecialchars($file["file_name"] ?? basename($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> <figure>
<?php if ($isPdf): ?> <?php if ($isPdf): ?>
@@ -464,6 +467,20 @@
<p class="tfe-pdf-fallback"> <p class="tfe-pdf-fallback">
<a href="<?= $mediaUrl ?>&download=1">Télécharger le PDF</a> <a href="<?= $mediaUrl ?>&download=1">Télécharger le PDF</a>
</p> </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): ?> <?php elseif ($isImage): ?>
<img src="<?= $mediaUrl ?>" <img src="<?= $mediaUrl ?>"
alt="<?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' — ' . ($data['authors'] ?? '')) ?>"> alt="<?= htmlspecialchars($caption !== '' ? $caption : $data['title'] . ' — ' . ($data['authors'] ?? '')) ?>">