Files
xamxam/app/templates/partials/form/fichiers-fragment.php
Pontoporeia 19bf9f101a Refactor apropos/charte/licence pages: shared layout, TOC anchors, and UI polish
Unify the three public pages (à propos, charte, licence) onto a single
grid layout (.page-content) with sticky TOC sidebar, replacing the old
separate  /  /  markup.

- Merge about.php, charte.php, licence.php templates into shared
  .page-content / .content-section structure
- Add CommonMark HeadingPermalinkExtension for stable heading anchors
- Use SlugNormalizer for TOC links so they match rendered heading IDs
- Standardize link styling across content blocks: bold black, accent on
  hover (consistent with global link style)
- Fix code block wrapping: use pre-wrap instead of pre, constrain grid
  columns with min-width:0, auto scrollbar
- Fix apropos page grid placement: force content-section into column 2
  so contacts and credits stay in the content area, not the sidebar

Also includes accumulated WIP changes:
- Header gradient: hardcoded purple-to-green (replaces CSS variables)
- Search placeholder font
- Duration field: replace minutes/sec/heures with h:m:s time inputs
- TFE file optional for formats 1,4,6 with client-side JS toggle
- Licence form: em-dash to hyphen, details/summary classes
- Pill search: block Enter key form submission when no results
- Draft autosave: remove CSRF rotation (broke concurrent FilePond uploads)
- Language pill: clear hints for excluded main languages
- Search results: gradient placeholder cards for items without covers
- TFE display: format durée values as XhYm instead of decimal
2026-06-19 19:40:05 +02:00

223 lines
12 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.
<?php
/**
* fichiers-fragment.php
*
* Shared HTMX fragment: returns the combined Format(s) + Fichiers block.
* Used by both the partage and admin forms.
*
* All slots (Couverture, Note d'intention, TFE, Annexes, Site web) are
* always visible — decorelated from the format checkboxes. The checkboxes
* serve only as metadata selectors; they no longer trigger HTMX swaps.
*
* Expected POST:
* formats[] — array of selected format_type IDs
* website_url — current value (repopulation)
* website_label — current value (repopulation)
* admin_mode — '1' for admin context (removes required attrs)
* edit_mode — '1' for edit context (preloads existing files)
*/
require_once APP_ROOT . '/src/PeerTubeService.php';
$_ptDb = Database::getInstance();
$peerTubeEnabled = PeerTubeService::isEnabled($_ptDb);
$peerTubeSettings = PeerTubeService::getSettings($_ptDb);
$db = $_ptDb->getConnection();
// Exclude removed format types (Écriture, Image)
$allFormats = $db->query("SELECT id, name FROM format_types WHERE name NOT IN ('Écriture', 'Image') ORDER BY sort_order, id")
->fetchAll(PDO::FETCH_ASSOC);
$selectedFormats = isset($_POST['formats']) && is_array($_POST['formats'])
? array_map('intval', $_POST['formats'])
: [];
$adminMode = ($_POST['admin_mode'] ?? '0') === '1';
$editMode = ($_POST['edit_mode'] ?? '0') === '1';
$errorFieldName = $errorFieldName ?? null;
// TFE file is optional when format is Site web (1), Performance (4) or Installation (6)
$noTfeFileFormats = [1, 4, 6];
$tfeFileOptional = !empty(array_intersect($selectedFormats, $noTfeFileFormats));
$websiteUrl = htmlspecialchars($_POST['website_url'] ?? '');
$websiteLabel = htmlspecialchars($_POST['website_label'] ?? '');
?>
<!-- ═══════════════════ Format(s) + Fichiers ═══════════════════ -->
<div id="format-fichiers-block">
<input type="hidden" name="admin_mode" value="<?= $adminMode ? '1' : '0' ?>">
<input type="hidden" name="edit_mode" value="<?= $editMode ? '1' : '0' ?>">
<!-- ═══════════════════ Format(s) — sticky ═══════════════════ -->
<fieldset id="fieldset-formats">
<legend>Format(s)</legend>
<div>
<span class="admin-row-label">Format(s) du TFE :<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?></span>
<fieldset class="admin-checkbox-group" role="group"
<?= !$adminMode ? 'aria-required="true"' : '' ?> >
<legend class="sr-only">Format(s) du TFE</legend>
<ul>
<?php foreach ($allFormats as $opt): ?>
<li>
<label class="admin-checkbox-label">
<input type="checkbox"
name="formats[]"
value="<?= htmlspecialchars((string)$opt['id']) ?>"
<?= in_array((int)$opt['id'], $selectedFormats, true) ? 'checked' : '' ?>
<?= ($errorFieldName === 'formats') ? 'aria-invalid="true" aria-errormessage="flash-error" aria-describedby="flash-error"' : '' ?>>
<?= htmlspecialchars($opt['name']) ?>
</label>
</li>
<?php endforeach; ?>
</ul>
</fieldset>
</div>
</fieldset>
<!-- ═══════════════════ Fichiers ═══════════════════ -->
<fieldset>
<legend>Fichiers</legend>
<!-- ── 1. Couverture ── -->
<div class="admin-form-group">
<label for="couverture">Image de couverture (optionnel)</label>
<div class="admin-file-input">
<input type="file" id="couverture"
name="queue_file[cover][]"
class="tfe-file-picker tfe-file-picker--single"
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é (ex. 1200 × 900 px). Max 20 MB.</small>
</div>
<?php if ($editMode): ?>
<button type="button" class="btn btn--sm btn--ghost file-browser-trigger"
data-queue-type="cover"
data-thesis-id="<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>"
hx-get="/admin/fragments/file-browser.php"
hx-target="#relink-modal-body"
hx-swap="innerHTML"
hx-trigger="click"
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'cover', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
📂 Relier un fichier existant
</button>
<?php endif; ?>
</div>
<!-- ── 2. Note d'intention ── -->
<div class="admin-form-group">
<label for="note_intention">Note d'intention<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?></label>
<div class="admin-file-input">
<input type="file" id="note_intention"
name="queue_file[note_intention][]"
class="tfe-file-picker tfe-file-picker--single"
data-queue-type="note_intention"
data-existing-files='<?= htmlspecialchars(json_encode($existingFilesJsonForNoteIntention ?? []), ENT_QUOTES) ?>'
<?= !$adminMode ? 'required' : '' ?>
<?= ($errorFieldName === 'note_intention') ? 'aria-invalid="true" aria-errormessage="flash-error"' : '' ?>
aria-describedby="note_intention-hint<?= ($errorFieldName === 'note_intention') ? ' flash-error' : '' ?>">
<small id="note_intention-hint">PDF uniquement. Max 100 MB. Si votre fichier est trop lourd, compressez-le avec <a href="https://www.bentopdf.com" target="_blank" rel="noopener">bentopdf.com</a>.</small>
</div>
<?php if ($editMode): ?>
<button type="button" class="btn btn--sm btn--ghost file-browser-trigger"
data-queue-type="note_intention"
data-thesis-id="<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>"
hx-get="/admin/fragments/file-browser.php"
hx-target="#relink-modal-body"
hx-swap="innerHTML"
hx-trigger="click"
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'note_intention', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
📂 Relier un fichier existant
</button>
<?php endif; ?>
</div>
<!-- ── 3. TFE (all files: PDF, images, video, audio, VTT, archives) ── -->
<div class="admin-form-group admin-files-fieldgroup">
<label for="tfe-files-input">TFE<?= (!$adminMode && !$tfeFileOptional) ? ' <span class="asterisk">*</span>' : '' ?><?= $tfeFileOptional ? ' (optionnel pour ce format)' : '' ?></label>
<div class="admin-file-input">
<input type="file" id="tfe-files-input"
name="queue_file[tfe][]"
multiple
class="tfe-file-picker"
data-queue-type="tfe"
<?= (!$adminMode && !$tfeFileOptional) ? 'required' : '' ?>
data-existing-files='<?= htmlspecialchars(json_encode($existingFilesJsonForTfe ?? []), ENT_QUOTES) ?>'
<?= ($errorFieldName === 'tfe-files-input') ? 'aria-invalid="true" aria-errormessage="flash-error"' : '' ?>
aria-describedby="tfe-files-hint<?= ($errorFieldName === 'tfe-files-input') ? ' flash-error' : '' ?>">
<small id="tfe-files-hint" class="admin-file-hint">
Glissez pour réordonner.
<br>
<br>
PDF (max 100 MB) · Images (max 1 GB) · Vidéo &amp; Audio (max 8 GB) · VTT · Archives (max 1 GB).
<br>→ PDFs trop lourds ? <a href="https://www.bentopdf.com" target="_blank" rel="noopener">https://bentopdf.com/</a>
<?php if ($peerTubeEnabled): ?>
<br><br>Vidéos et audio hébergés sur <a href="<?= htmlspecialchars($peerTubeSettings['instance_url']) ?>" target="_blank" rel="noopener">PeerTube</a>.
<?php endif; ?>
</small>
</div>
<?php if ($editMode): ?>
<button type="button" class="btn btn--sm btn--ghost file-browser-trigger"
data-queue-type="tfe"
data-thesis-id="<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>"
hx-get="/admin/fragments/file-browser.php"
hx-target="#relink-modal-body"
hx-swap="innerHTML"
hx-trigger="click"
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'tfe', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
📂 Relier un fichier existant
</button>
<?php endif; ?>
</div>
<!-- ── 4. Annexes ── -->
<div id="annexes-input-block">
<input type="hidden" name="has_annexes" value="0">
<div class="admin-form-group admin-files-fieldgroup">
<label for="annexe-files-input">Annexes (optionnel)</label>
<div class="admin-file-input">
<input type="file" id="annexe-files-input"
name="queue_file[annexe][]"
multiple
class="tfe-file-picker"
data-queue-type="annexe"
data-existing-files='<?= htmlspecialchars(json_encode($existingFilesJsonForAnnexe ?? []), ENT_QUOTES) ?>'
aria-describedby="annexe-files-hint">
<small id="annexe-files-hint" class="admin-file-hint">PDF ou archives ZIP/TAR. Max 1 GB. Glissez pour réordonner.</small>
</div>
<?php if ($editMode): ?>
<button type="button" class="btn btn--sm btn--ghost file-browser-trigger"
data-queue-type="annexe"
data-thesis-id="<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>"
hx-get="/admin/fragments/file-browser.php"
hx-target="#relink-modal-body"
hx-swap="innerHTML"
hx-trigger="click"
onclick="document.getElementById('relink-modal').showModal(); window.__xamxamRelinkCtx = { queueType: 'annexe', thesisId: '<?= htmlspecialchars((string)($thesisId ?? $_GET['id'] ?? '')) ?>' };">
📂 Relier un fichier existant
</button>
<?php endif; ?>
</div>
</div>
<!-- ── 5. Site web url ── -->
<div id="slot-siteweb" class="admin-form-group">
<label for="website_url">URL du site (optionnel)</label>
<div class="admin-file-input">
<input type="url" id="website_url" name="website_url"
value="<?= $websiteUrl ?>"
placeholder="https://mon-tfe.erg.be"
aria-describedby="website_url-hint">
<small id="website_url-hint">Le TFE sera affiché comme un site embarqué sur sa page publique.</small>
</div>
</div>
</fieldset><!-- /Fichiers -->
</div><!-- #format-fichiers-block -->
<!-- ═══════════════════ File Browser Modal (edit mode only) ═══════════════════ -->
<?php if ($editMode): ?>
<?php include APP_ROOT . '/templates/partials/form/file-browser-fragment.php'; ?>
<?php endif; ?>