Files
xamxam/app/templates/partials/form/fichiers-fragment.php
Pontoporeia d588ae004d Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration
Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
2026-06-15 15:56:52 +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 (3), Performance (4) or Installation (6)
$noTfeFileFormats = [3, 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; ?>