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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user