mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
feat: FilePond production hardening — extension-based validation, server-side size limits (2GB), annexe validation, drop accept attributes, FilePond file styling
This commit is contained in:
@@ -98,20 +98,8 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='admin_mode'], [name='edit_mode'], [name='_cover']"
|
||||
hx-swap="outerHTML"
|
||||
<?php elseif ((int)$opt['id'] === ($videoId ?? 0)): ?>
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#slot-video"
|
||||
hx-select="#slot-video"
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='admin_mode'], [name='edit_mode'], [name='_cover']"
|
||||
hx-swap="outerHTML"
|
||||
<?php elseif ((int)$opt['id'] === ($audioId ?? 0)): ?>
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#slot-audio"
|
||||
hx-select="#slot-audio"
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='admin_mode'], [name='edit_mode'], [name='_cover']"
|
||||
<?php endif; ?>>
|
||||
<?php endif; ?>
|
||||
>
|
||||
<?= htmlspecialchars($opt['name']) ?>
|
||||
</label>
|
||||
</li>
|
||||
@@ -206,7 +194,6 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="couverture"
|
||||
name="couverture"
|
||||
accept="image/jpeg,image/png,image/webp"
|
||||
class="tfe-file-picker tfe-file-picker--single"
|
||||
data-queue-type="cover">
|
||||
<small>JPG, PNG ou WEBP. Format 4:3 recommandé. Max 20 MB.</small>
|
||||
@@ -220,7 +207,6 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
<div class="admin-file-input">
|
||||
<input type="file" id="note_intention"
|
||||
name="note_intention"
|
||||
accept=".pdf"
|
||||
class="tfe-file-picker tfe-file-picker--single"
|
||||
data-queue-type="note_intention"
|
||||
<?= !$adminMode ? 'required' : '' ?>>
|
||||
@@ -235,47 +221,30 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
<input type="file" id="tfe-files-input"
|
||||
name="queue_file[tfe][]"
|
||||
multiple
|
||||
accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.mp4,.webm,.ogv,.mov,.mp3,.ogg,.oga,.wav,.flac,.aac,.m4a,.vtt,.zip,.tar,.gz,.tgz"
|
||||
class="tfe-file-picker"
|
||||
<?= !$adminMode ? 'required' : '' ?>>
|
||||
<small class="admin-file-hint">
|
||||
PDF (max 100 MB) · Images (JPG/PNG/GIF/WEBP) · Vidéo · Audio · VTT · Archives.
|
||||
PDF (max 100 MB) · Images (max 500 MB) · Vidéo & Audio (max 2 GB) · VTT · Archives (max 500 MB).
|
||||
Glissez pour réordonner.
|
||||
PDFs trop lourds ? <a href="https://www.bentopdf.com" target="_blank" rel="noopener">https://bentopdf.com/</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 4. Annexes — multi-file upload (FilePond) ── -->
|
||||
<!-- ── 4. Annexes — multi-file upload (FilePond), always visible ── -->
|
||||
<div id="annexes-input-block">
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="has_annexes" value="1"
|
||||
<?= $hasAnnexesChecked ? 'checked' : '' ?>
|
||||
hx-post="<?= htmlspecialchars($hxPost) ?>"
|
||||
hx-target="#annexes-input-block"
|
||||
hx-select="#annexes-input-block"
|
||||
hx-trigger="change"
|
||||
hx-include="[name='formats[]'], [name='website_url'], [name='admin_mode'], [name='edit_mode'], [name='_cover'], [name='has_annexes']"
|
||||
hx-swap="outerHTML">
|
||||
Ce TFE comporte des annexes
|
||||
</label>
|
||||
</div>
|
||||
<?php if ($hasAnnexesChecked): ?>
|
||||
<!-- has_annexes checkbox disabled — annexe pool always on -->
|
||||
<input type="hidden" name="has_annexes" value="0">
|
||||
<div class="admin-form-group admin-files-fieldgroup">
|
||||
<label for="annexe-files-input">Annexes<?= !$adminMode ? ' <span class="asterisk">*</span>' : '' ?></label>
|
||||
<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
|
||||
accept=".pdf,.zip,.tar,.gz"
|
||||
class="tfe-file-picker"
|
||||
<?= !$adminMode ? 'required' : '' ?>>
|
||||
class="tfe-file-picker">
|
||||
<small class="admin-file-hint">PDF ou archives ZIP/TAR. Max 500 MB. Glissez pour réordonner.</small>
|
||||
</div>
|
||||
</div>
|
||||
<script>if(window.XamxamInitFilePonds)window.XamxamInitFilePonds();</script>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- ── Format-specific extras (individual swappable slots) ── -->
|
||||
@@ -296,7 +265,8 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
<div id="slot-siteweb" hidden></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Slot: Video -->
|
||||
<!-- Slot: Video (disabled — video files are now uploaded via the TFE input) -->
|
||||
<!--
|
||||
<?php if ($hasVideo): ?>
|
||||
<?php if ($peerTubeEnabled): ?>
|
||||
<div id="slot-video" class="admin-form-group">
|
||||
@@ -325,8 +295,11 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
<?php else: ?>
|
||||
<div id="slot-video" hidden></div>
|
||||
<?php endif; ?>
|
||||
-->
|
||||
<div id="slot-video" hidden></div>
|
||||
|
||||
<!-- Slot: Audio -->
|
||||
<!-- Slot: Audio (disabled — audio files are now uploaded via the TFE input) -->
|
||||
<!--
|
||||
<?php if ($hasAudio): ?>
|
||||
<?php if ($peerTubeEnabled): ?>
|
||||
<div id="slot-audio" class="admin-form-group">
|
||||
@@ -355,13 +328,10 @@ $hasAnnexesChecked = !empty($_POST['has_annexes']);
|
||||
<?php else: ?>
|
||||
<div id="slot-audio" hidden></div>
|
||||
<?php endif; ?>
|
||||
-->
|
||||
<div id="slot-audio" hidden></div>
|
||||
|
||||
<script>if(window.XamxamInitFilePonds)window.XamxamInitFilePonds();</script>
|
||||
</div>
|
||||
|
||||
</fieldset><!-- /Fichiers -->
|
||||
|
||||
<script>
|
||||
if(window.XamxamInitFilePonds)window.XamxamInitFilePonds();
|
||||
</script>
|
||||
</div><!-- #format-fichiers-block -->
|
||||
|
||||
@@ -317,10 +317,29 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
$synopsisExtra = ob_get_clean();
|
||||
|
||||
// Jury data from repopulation
|
||||
$juryPromoteur = old($formData, 'jury_promoteur');
|
||||
$juryPromoteur = null;
|
||||
$juryPromoteurs = [];
|
||||
$juryPromoteurUlb = old($formData, 'jury_promoteur_ulb_name');
|
||||
$juryPromoteurUlb = null;
|
||||
$juryPromoteursUlb = [];
|
||||
// promoteurices may be submitted as arrays (multiple entries)
|
||||
$promoteursRaw = old($formData, 'jury_promoteur');
|
||||
if (is_array($promoteursRaw)) {
|
||||
foreach ($promoteursRaw as $name) {
|
||||
$name = trim($name ?? '');
|
||||
if ($name !== '') $juryPromoteurs[] = ['name' => $name];
|
||||
}
|
||||
} elseif (is_string($promoteursRaw) && trim($promoteursRaw) !== '') {
|
||||
$juryPromoteur = $promoteursRaw;
|
||||
}
|
||||
$promoteursUlbRaw = old($formData, 'jury_promoteur_ulb_name');
|
||||
if (is_array($promoteursUlbRaw)) {
|
||||
foreach ($promoteursUlbRaw as $name) {
|
||||
$name = trim($name ?? '');
|
||||
if ($name !== '') $juryPromoteursUlb[] = ['name' => $name];
|
||||
}
|
||||
} elseif (is_string($promoteursUlbRaw) && trim($promoteursUlbRaw) !== '') {
|
||||
$juryPromoteurUlb = $promoteursUlbRaw;
|
||||
}
|
||||
$lecteursInternes = [];
|
||||
$lecteursExternes = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
@@ -376,7 +395,12 @@ function renderShareLinkForm(string $slug, array $link): void
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/common.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/form.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/filepond.min.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/filepond-plugin-image-preview.min.css') ?>">
|
||||
<script src="<?= App::assetV('/assets/js/filepond.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-file-validate-type.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-file-validate-size.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-image-preview.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/filepond-plugin-image-exif-orientation.min.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/file-upload-filepond.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/beforeunload-guard.js') ?>" defer></script>
|
||||
<script src="<?= App::assetV('/assets/js/htmx.min.js') ?>" defer></script>
|
||||
@@ -497,6 +521,12 @@ function handleShareLinkSubmission(string $slug): void
|
||||
$ctrl = ThesisCreateController::make();
|
||||
$thesisId = $ctrl->submit($_POST, $_FILES);
|
||||
|
||||
// Collect file processing warnings (invalid types, too large, etc.)
|
||||
$fileWarnings = $ctrl->getFileWarnings();
|
||||
if ($fileWarnings) {
|
||||
$_SESSION['_flash_warning'] = implode("\n", $fileWarnings);
|
||||
}
|
||||
|
||||
$identifier = $ctrl->getIdentifier($thesisId);
|
||||
$logger->logSubmission('partage', $thesisId, $identifier, $authorName, [
|
||||
'share_slug' => $slug,
|
||||
@@ -572,19 +602,23 @@ function handleShareLinkSubmission(string $slug): void
|
||||
/**
|
||||
* Helper to retrieve old form data (with support for array keys via : delimiter)
|
||||
*/
|
||||
function old(array $data, string $key, string $default = ''): string {
|
||||
/**
|
||||
* Retrieve old form data for repopulation.
|
||||
* Returns raw value (no escaping) — callers must htmlspecialchars() when rendering.
|
||||
* For arrays, returns the array as-is so callers can iterate.
|
||||
*/
|
||||
function old(array $data, string $key, $default = '') {
|
||||
// Support nested keys like "jury_lecteurs:0"
|
||||
$parts = explode(':', $key);
|
||||
$value = $data;
|
||||
foreach ($parts as $part) {
|
||||
if (is_array($value) && isset($value[$part])) {
|
||||
if (is_array($value) && array_key_exists($part, $value)) {
|
||||
$value = $value[$part];
|
||||
} else {
|
||||
$value = $default;
|
||||
break;
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
return is_array($value) ? htmlspecialchars(json_encode($value)) : htmlspecialchars((string)$value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user