mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration
Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
This commit is contained in:
@@ -152,6 +152,7 @@
|
||||
<th scope="col">Utilisations</th>
|
||||
<th scope="col">Expiration</th>
|
||||
<th scope="col">Créé le</th>
|
||||
<th scope="col">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -175,6 +176,12 @@
|
||||
<td style="text-align:center;"><?= intval($link['usage_count']) ?></td>
|
||||
<td><?= $expires ?></td>
|
||||
<td><?= $created ?></td>
|
||||
<td class="admin-actions-col">
|
||||
<button type="button" class="admin-icon-btn admin-icon-btn--delete" title="Supprimer"
|
||||
onclick="openDeleteArchivedLinkDialog(<?= $link['id'] ?>)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 256 256"><path d="M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z"></path></svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
@@ -641,6 +648,10 @@ function _executeArchiveLink() {
|
||||
const form = document.getElementById('archive-link-form-' + _pendingArchiveLinkId);
|
||||
if (form) form.submit();
|
||||
}
|
||||
function openDeleteArchivedLinkDialog(id) {
|
||||
document.getElementById('delete-archived-link-id').value = id;
|
||||
document.getElementById('delete-archived-link-dialog').showModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Archive link confirm -->
|
||||
@@ -658,3 +669,24 @@ function _executeArchiveLink() {
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- ═══════════════════════ DELETE ARCHIVED LINK DIALOG ═══════════════════════ -->
|
||||
<dialog id="delete-archived-link-dialog" class="admin-dialog admin-dialog--sm" aria-labelledby="delete-archived-link-title">
|
||||
<div class="admin-dialog__header">
|
||||
<h2 id="delete-archived-link-title">Supprimer le lien</h2>
|
||||
<button type="button" class="admin-dialog__close" aria-label="Fermer"
|
||||
onclick="this.closest('dialog').close()">✕</button>
|
||||
</div>
|
||||
<div class="admin-dialog__alert">
|
||||
<p>Supprimer définitivement ce lien archivé ? Cette action est irréversible.</p>
|
||||
</div>
|
||||
<div class="admin-dialog__footer">
|
||||
<form method="post" action="actions/acces-etudiante.php">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="delete">
|
||||
<input type="hidden" name="id" id="delete-archived-link-id" value="">
|
||||
<button type="submit" class="btn btn--danger">Supprimer</button>
|
||||
</form>
|
||||
<button type="button" class="btn btn--secondary" onclick="this.closest('dialog').close()">Annuler</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
@@ -1,30 +1,28 @@
|
||||
<main id="main-content">
|
||||
<main id="main-content" class="full-editor-page">
|
||||
<h1><a href="/admin/contenus.php" class="admin-back-btn" title="Retour"><svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"><path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm48-88a8,8,0,0,1-8,8H107.31l18.35,18.34a8,8,0,0,1-11.32,11.32l-32-32a8,8,0,0,1,0-11.32l32-32a8,8,0,0,1,11.32,11.32L107.31,120H168A8,8,0,0,1,176,128Z"></path></svg></a> Éditer : <?= htmlspecialchars($editTitle) ?></h1>
|
||||
|
||||
<?php if ($editType === 'about_page'): ?>
|
||||
|
||||
<!-- ── Markdown content ──────────────────────────────────────────────── -->
|
||||
<h2>Contenu de la page</h2>
|
||||
<form action="/admin/actions/page.php" method="post" class="admin-form"
|
||||
hx-post="/admin/actions/page.php"
|
||||
hx-trigger="overtype:change delay:1500ms"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="handleAutosaveResponse(event)">
|
||||
<form action="/admin/actions/page.php" method="post" class="admin-form admin-form--full-editor">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||
<input type="hidden" name="slug" value="about">
|
||||
|
||||
<label for="editor">Contenu (Markdown) :</label>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<div class="full-editor-toolbar">
|
||||
<span class="full-editor-label">Contenu (Markdown) :</span>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
|
||||
</div>
|
||||
<input type="hidden" id="content" name="content"
|
||||
value="<?= htmlspecialchars($initialContent) ?>">
|
||||
<div id="editor"></div>
|
||||
<div class="autosave-status" data-autosave-status></div>
|
||||
</form>
|
||||
|
||||
<!-- ── Contacts ──────────────────────────────────────────────────────── -->
|
||||
@@ -137,50 +135,46 @@
|
||||
</script>
|
||||
|
||||
<?php elseif ($editType === 'page' && $pageSlug !== 'about'): ?>
|
||||
<form action="/admin/actions/page.php" method="post" class="admin-form"
|
||||
hx-post="/admin/actions/page.php"
|
||||
hx-trigger="overtype:change delay:1500ms"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="handleAutosaveResponse(event)">
|
||||
<form action="/admin/actions/page.php" method="post" class="admin-form admin-form--full-editor">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION["csrf_token"]) ?>">
|
||||
<input type="hidden" name="slug" value="<?= htmlspecialchars($pageSlug) ?>">
|
||||
|
||||
<label for="editor">Contenu (Markdown) :</label>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<div class="full-editor-toolbar">
|
||||
<span class="full-editor-label">Contenu (Markdown) :</span>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
|
||||
</div>
|
||||
<input type="hidden" id="content" name="content"
|
||||
value="<?= htmlspecialchars($initialContent) ?>">
|
||||
<div id="editor"></div>
|
||||
<div class="autosave-status" data-autosave-status></div>
|
||||
</form>
|
||||
|
||||
<?php elseif ($editType === 'form_help'): ?>
|
||||
<p class="param-note">Ce texte est affiché dans le formulaire de soumission des étudiant·es (lien de partage). Supporte le Markdown.</p>
|
||||
<form action="/admin/actions/form-help.php" method="post" class="admin-form"
|
||||
hx-post="/admin/actions/form-help.php"
|
||||
hx-trigger="overtype:change delay:1500ms"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="handleAutosaveResponse(event)">
|
||||
<form action="/admin/actions/form-help.php" method="post" class="admin-form admin-form--full-editor">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="form_help_key" value="<?= htmlspecialchars($formHelpKey) ?>">
|
||||
|
||||
<label for="editor">Contenu (Markdown) :</label>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<div class="full-editor-toolbar">
|
||||
<span class="full-editor-label">Contenu (Markdown) :</span>
|
||||
<button type="button" class="btn btn--sm"
|
||||
hx-get="/admin/markdown-cheatsheet-fragment.php"
|
||||
hx-target="#md-cheatsheet-container"
|
||||
hx-swap="innerHTML"
|
||||
hx-on::after-request="document.getElementById('md-cheatsheet-dialog').showModal()">
|
||||
Aide Markdown
|
||||
</button>
|
||||
<button type="submit" class="btn btn--primary btn--sm">Enregistrer</button>
|
||||
</div>
|
||||
<input type="hidden" id="content" name="content"
|
||||
value="<?= htmlspecialchars($initialContent) ?>">
|
||||
<div id="editor"></div>
|
||||
<div class="autosave-status" data-autosave-status></div>
|
||||
</form>
|
||||
|
||||
<?php else: ?>
|
||||
|
||||
@@ -69,14 +69,6 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
<option value="<?= $y ?>" <?= $yearFilter == $y ? 'selected' : '' ?>><?= $y ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="orientation">
|
||||
<option value="">Orientation</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="ap">
|
||||
<option value="">AP</option>
|
||||
<?php foreach ($apPrograms as $ap): ?>
|
||||
@@ -85,6 +77,14 @@ document.addEventListener('htmx:afterSwap',()=>{document.querySelectorAll('input
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<select name="orientation">
|
||||
<option value="">Orientation</option>
|
||||
<?php foreach ($orientations as $o): ?>
|
||||
<option value="<?= $o['id'] ?>" <?= $orientationFilter == $o['id'] ? 'selected' : '' ?>>
|
||||
<?= htmlspecialchars($o['name']) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php if ($searchQuery || $yearFilter || $orientationFilter || $apFilter): ?>
|
||||
<a href="/admin/" class="btn btn--secondary btn--sm admin-filters-reset">✕ Réinitialiser</a>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -360,14 +360,14 @@
|
||||
<!-- Danger zone: remove credentials -->
|
||||
<?php if ($hasPassword): ?>
|
||||
<fieldset class="param-danger-zone">
|
||||
<legend>Supprimer la configuration du mot de passe PHP</legend>
|
||||
<legend>Supprimer la configuration du mot de passe</legend>
|
||||
<p>
|
||||
Supprime le hash de la base de données. L'accès admin
|
||||
dépendra uniquement de l'authentification nginx Basic Auth si elle est configurée.
|
||||
ne sera plus protégé (mode développement).
|
||||
</p>
|
||||
<?php /* TODO: replace this browser confirm() with a proper <dialog> modal like the other confirmations */ ?>
|
||||
<form method="post" action="/admin/actions/account.php"
|
||||
onsubmit="return confirm('Supprimer le mot de passe PHP ? L\'accès admin ne sera protégé que par nginx Basic Auth.')">
|
||||
onsubmit="return confirm('Supprimer le mot de passe ? L\'accès admin ne sera plus protégé.')">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<input type="hidden" name="action" value="remove_credentials">
|
||||
<input type="hidden" name="redirect" value="/admin/parametres.php">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// header.php — unified site header for public and admin sections.
|
||||
// Reads: $isAdmin (bool), $currentNav (string, public only)
|
||||
$_isAdmin = !empty($isAdmin);
|
||||
$_isLogin = !empty($isLogin);
|
||||
$_navCurrent = $currentNav ?? '';
|
||||
$_currentPage = basename($_SERVER['PHP_SELF']);
|
||||
$_thesisId = $_GET['id'] ?? null;
|
||||
@@ -9,7 +10,7 @@ $_thesisId = $_GET['id'] ?? null;
|
||||
<header>
|
||||
<a href="#main-content" class="skip-link">Aller au contenu principal</a>
|
||||
|
||||
<?php if ($_isAdmin): ?>
|
||||
<?php if ($_isAdmin && !$_isLogin): ?>
|
||||
|
||||
<nav aria-label="Navigation admin">
|
||||
<ul class="nav-left-links">
|
||||
@@ -87,7 +88,7 @@ $_thesisId = $_GET['id'] ?? null;
|
||||
|
||||
</header>
|
||||
|
||||
<?php if ($_isAdmin): ?>
|
||||
<?php if ($_isAdmin && !$_isLogin): ?>
|
||||
<div class="admin-mobile-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" aria-hidden="true"><rect width="256" height="256" fill="none"/><rect x="24" y="56" width="208" height="144" rx="16" fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/><line x1="24" y1="168" x2="104" y2="88" fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/><line x1="152" y1="168" x2="232" y2="88" fill="none" stroke="currentColor" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<h2>Section administrateur</h2>
|
||||
@@ -95,7 +96,7 @@ $_thesisId = $_GET['id'] ?? null;
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$_isAdmin): ?>
|
||||
<?php if (!$_isAdmin && !$_isLogin): ?>
|
||||
<?php
|
||||
// Search bar — public section only (rendered below header for equal height)
|
||||
$searchBarValue = $searchBarValue ?? $_GET['query'] ?? '';
|
||||
|
||||
@@ -58,6 +58,7 @@ $filepondBase = $filepondBase ?? null;
|
||||
<?php if ($includeFilePond): ?>
|
||||
<script src="<?= App::assetV('/assets/js/app/pill-search.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/app/jury-autocomplete.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/app/autosave-handler.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/vendor/htmx.min.js') ?>" defer></script>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($extraJs as $js): ?>
|
||||
|
||||
@@ -64,4 +64,6 @@ $ariaDescribedBy = ($errorFieldName === $name) ? ' aria-describedby="flash-error
|
||||
</fieldset>
|
||||
</div>
|
||||
<?php
|
||||
unset($checked, $hxPost, $hxTarget, $hxSwap, $hxInclude, $ariaInvalid, $ariaDescribedBy, $errorFieldName);
|
||||
unset($checked, $hxPost, $hxTarget, $hxSwap, $hxInclude, $ariaInvalid, $ariaDescribedBy);
|
||||
// NOTE: $errorFieldName is intentionally NOT unset — it is a shared variable
|
||||
// consumed by downstream partials (e.g. fichiers-fragment.php).
|
||||
|
||||
@@ -89,7 +89,7 @@ $websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
|
||||
data-queue-type="cover"
|
||||
data-existing-files='<?= htmlspecialchars(json_encode($existingFilesJsonForCover ?? []), ENT_QUOTES) ?>'
|
||||
aria-describedby="couverture-hint">
|
||||
<small id="couverture-hint">JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.</small>
|
||||
<small id="couverture-hint">JPG, PNG ou WEBP. Format 4:3 recommandé (ex. 1200 × 900 px). Max 20 MB.</small>
|
||||
</div>
|
||||
<?php if ($editMode): ?>
|
||||
<button type="button" class="btn btn--sm btn--ghost file-browser-trigger"
|
||||
|
||||
@@ -20,7 +20,7 @@ $adminMode = $adminMode ?? false;
|
||||
$name = 'couverture';
|
||||
$label = 'Image de couverture (optionnel) :';
|
||||
$accept = 'image/jpeg,image/png,image/webp';
|
||||
$hint = 'JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.';
|
||||
$hint = 'JPG, PNG ou WEBP. Format 4:3 recommandé (ex. 1200 × 900 px). Max 20 MB.';
|
||||
include APP_ROOT . '/templates/partials/form/file-field.php';
|
||||
?>
|
||||
|
||||
|
||||
@@ -102,6 +102,10 @@ $existingWebsiteUrl = $existingWebsiteUrl ?? '';
|
||||
$existingWebsiteLabel = $existingWebsiteLabel ?? '';
|
||||
$checkedFormatsForSiteWeb = $checkedFormatsForSiteWeb ?? [];
|
||||
|
||||
// Duration (value + unit)
|
||||
$durationValue = $durationValue ?? null;
|
||||
$durationUnit = $durationUnit ?? 'pages';
|
||||
|
||||
// WCAG 3.3.1: which field has a validation error (set by caller from App::consumeAutofocus())
|
||||
$errorFieldName = $errorFieldName ?? null;
|
||||
?>
|
||||
@@ -413,8 +417,38 @@ if ($filesMode === 'add'): ?>
|
||||
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- ═══════════════════ Métadonnées complémentaires ═══════════════════
|
||||
(Durée/Nombre de pages supprimés — redondants avec les fichiers attachés) -->
|
||||
<!-- ═══════════════════ Durée ═══════════════════ -->
|
||||
<fieldset>
|
||||
<legend>Durée</legend>
|
||||
<div class="admin-form-group admin-form-group--inline">
|
||||
<div>
|
||||
<label for="duration_unit">Unité :</label>
|
||||
<select id="duration_unit" name="duration_unit">
|
||||
<?php
|
||||
$_currentUnit = $durationUnit ?? ($formData['duration_unit'] ?? 'pages');
|
||||
$_units = [
|
||||
'pages' => 'pages',
|
||||
'minutes' => 'minutes',
|
||||
'sec' => 'secondes',
|
||||
'heures' => 'heures',
|
||||
'mo' => 'Mo',
|
||||
];
|
||||
foreach ($_units as $_val => $_label): ?>
|
||||
<option value="<?= $_val ?>" <?= $_currentUnit === $_val ? 'selected' : '' ?>><?= htmlspecialchars($_label) ?></option>
|
||||
<?php endforeach; unset($_units, $_currentUnit, $_val, $_label); ?>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="duration_value">Valeur :</label>
|
||||
<input type="number" id="duration_value" name="duration_value"
|
||||
value="<?= htmlspecialchars((string)($durationValue ?? ($formData['duration_value'] ?? ''))) ?>"
|
||||
step="0.1" min="0" placeholder="0"
|
||||
style="width: 8ch;">
|
||||
</div>
|
||||
</div>
|
||||
<small>Optionnel. Exemples : 88 pages, 32 minutes, 1.5 heures, 120 Mo.</small>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Degrés d'ouverture et licences ═══════════════════ -->
|
||||
<?php
|
||||
@@ -552,6 +586,16 @@ if ($filesMode === 'add'): ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showAutosaveStatus): ?>
|
||||
<!-- Hidden autosave element: polls the form every 3s and POSTs to draft.php.
|
||||
Uses hx-include to serialize the entire form. hx-swap="none" so response
|
||||
doesn't alter the DOM. -->
|
||||
<div hx-post="<?= htmlspecialchars($autosaveUrl ?? '') ?>"
|
||||
hx-trigger="every 3s"
|
||||
hx-include="closest form"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="handleAutosaveResponse(event)"
|
||||
data-autosave-probe
|
||||
aria-hidden="true" style="display:none"></div>
|
||||
<div class="autosave-status" data-autosave-status></div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
@@ -73,4 +73,6 @@ foreach ($attrs as $k => $v) {
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
unset($required, $placeholder, $id, $hint, $attrs, $selectAttrStr, $k, $v, $hintId, $describedBy, $ariaDescribedBy, $ariaInvalid, $errorFieldName);
|
||||
unset($required, $placeholder, $id, $hint, $attrs, $selectAttrStr, $k, $v, $hintId, $describedBy, $ariaDescribedBy, $ariaInvalid);
|
||||
// NOTE: $errorFieldName is intentionally NOT unset — it is a shared variable
|
||||
// consumed by downstream partials (e.g. fichiers-fragment.php).
|
||||
|
||||
@@ -74,4 +74,6 @@ $ariaInvalid = ($errorFieldName === $name) ? ' aria-invalid="true" aria-errormes
|
||||
</div>
|
||||
<?php
|
||||
// Reset consumed variables so includes in a loop don't bleed state.
|
||||
unset($type, $required, $placeholder, $hint, $id, $attrs, $attrStr, $k, $v, $hintId, $describedBy, $ariaDescribedBy, $ariaInvalid, $errorFieldName);
|
||||
unset($type, $required, $placeholder, $hint, $id, $attrs, $attrStr, $k, $v, $hintId, $describedBy, $ariaDescribedBy, $ariaInvalid);
|
||||
// NOTE: $errorFieldName is intentionally NOT unset — it is a shared variable
|
||||
// consumed by the parent partial (e.g. fieldset-tfe-info.php for the synopsis textarea).
|
||||
|
||||
@@ -95,7 +95,7 @@ $filterColumns = [
|
||||
['dataKey' => 'years', 'dim' => 'years', 'heading' => 'Années'],
|
||||
['dataKey' => 'ap_programs', 'dim' => 'ap', 'heading' => 'Ateliers Pluridisciplinaires'],
|
||||
['dataKey' => 'orientations', 'dim' => 'or', 'heading' => 'Orientations'],
|
||||
['dataKey' => 'finality_types', 'dim' => 'fi', 'heading' => 'Finalité du Master'],
|
||||
['dataKey' => 'finality_types', 'dim' => 'fi', 'heading' => 'Finalité du Master'],
|
||||
['dataKey' => 'keywords', 'dim' => 'kw', 'heading' => 'Mots-clés'],
|
||||
];
|
||||
|
||||
@@ -118,7 +118,7 @@ foreach ($renderOrder as $colKey):
|
||||
if ($colKey === 'students'): ?>
|
||||
<!-- ÉTUDIANTES -->
|
||||
<section class="repertoire-col" data-col="students">
|
||||
<h2>Étudiantes</h2>
|
||||
<h2>Étudiant·es</h2>
|
||||
<ul>
|
||||
<?php if (empty($studentWorks)): ?>
|
||||
<li class="rep-empty">—</li>
|
||||
@@ -147,7 +147,7 @@ foreach ($renderOrder as $colKey):
|
||||
<?php else:
|
||||
$col = array_values(array_filter($filterColumns, fn($c) => $c['dim'] === $colKey))[0]; ?>
|
||||
<section class="repertoire-col" data-col="<?= $col['dim'] ?>">
|
||||
<h2><?= htmlspecialchars($col['heading']) ?></h2>
|
||||
<h2><?= $col['heading'] ?></h2>
|
||||
<ul>
|
||||
<?php foreach ($repData[$col['dataKey']] as $item):
|
||||
repFilterEntry($item, $col['dim'], $activeSets, $anyActive, $colHasMatches[$col['dim']], $hx);
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
<main class="apropos-main" id="main-content">
|
||||
<div class="prose apropos-single">
|
||||
<?php if (!empty(trim($content))): ?>
|
||||
<?= $html ?>
|
||||
<?php else: ?>
|
||||
<p>Contenu à venir.</p>
|
||||
<div class="apropos-layout">
|
||||
|
||||
<!-- LEFT: sticky table of contents -->
|
||||
<?php if (!empty($tocItems)): ?>
|
||||
<nav class="apropos-toc" aria-label="Sections de la page">
|
||||
<p class="apropos-toc-label">Parties</p>
|
||||
<ul>
|
||||
<?php foreach ($tocItems as $item): ?>
|
||||
<li><a href="<?= htmlspecialchars($item['href']) ?>"><?= htmlspecialchars($item['label']) ?></a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- MIDDLE: main prose -->
|
||||
<div class="apropos-content">
|
||||
<section class="apropos-section">
|
||||
<div class="prose">
|
||||
<?php if (!empty(trim($content))): ?>
|
||||
<?= $html ?>
|
||||
<?php else: ?>
|
||||
<p>Contenu à venir.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
<main class="apropos-main" id="main-content">
|
||||
<div class="prose apropos-single">
|
||||
<?php if (!empty(trim($content))): ?>
|
||||
<?= $html ?>
|
||||
<?php else: ?>
|
||||
<p>Contenu à venir.</p>
|
||||
<div class="apropos-layout">
|
||||
|
||||
<!-- LEFT: sticky table of contents -->
|
||||
<?php if (!empty($tocItems)): ?>
|
||||
<nav class="apropos-toc" aria-label="Sections de la page">
|
||||
<p class="apropos-toc-label">Parties</p>
|
||||
<ul>
|
||||
<?php foreach ($tocItems as $item): ?>
|
||||
<li><a href="<?= htmlspecialchars($item['href']) ?>"><?= htmlspecialchars($item['label']) ?></a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- MIDDLE: main prose -->
|
||||
<div class="apropos-content">
|
||||
<section class="apropos-section">
|
||||
<div class="prose">
|
||||
<?php if (!empty(trim($content))): ?>
|
||||
<?= $html ?>
|
||||
<?php else: ?>
|
||||
<p>Contenu à venir.</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -17,18 +17,6 @@
|
||||
</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>
|
||||
@@ -41,6 +29,18 @@
|
||||
</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-finality">Finalité
|
||||
<select class="search-filter-select" name="finality" id="filter-finality">
|
||||
<option value="">Toutes</option>
|
||||
|
||||
@@ -46,6 +46,27 @@
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data["duration_value"]) && !empty($data["duration_unit"])): ?>
|
||||
<?php
|
||||
$_dVal = (float)$data["duration_value"];
|
||||
$_dUnit = $data["duration_unit"];
|
||||
$_unitLabels = [
|
||||
'pages' => 'pages',
|
||||
'minutes' => 'minutes',
|
||||
'sec' => 'secondes',
|
||||
'heures' => 'heures',
|
||||
'mo' => 'Mo',
|
||||
];
|
||||
$_label = $_unitLabels[$_dUnit] ?? $_dUnit;
|
||||
// if float, show 0.1 or .0 as needed
|
||||
$_display = ($_dVal == (int)$_dVal) ? (int)$_dVal : $_dVal;
|
||||
?>
|
||||
<p class="tfe-meta-item">
|
||||
<span class="tfe-meta-label">Durée :</span>
|
||||
<?= $_display ?> <?= htmlspecialchars($_label) ?>
|
||||
</p>
|
||||
<?php unset($_unitLabels, $_dVal, $_dUnit, $_label, $_display); endif; ?>
|
||||
|
||||
<?php if (!empty($data["languages"])): ?>
|
||||
<p class="tfe-meta-item">
|
||||
<span class="tfe-meta-label">Langue :</span>
|
||||
|
||||
Reference in New Issue
Block a user