mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
admin: replace header 'Ajouter un TFE' nav link with toolbar button
This commit is contained in:
28
public/partage/.htaccess
Normal file
28
public/partage/.htaccess
Normal file
@@ -0,0 +1,28 @@
|
||||
# Route all partage requests to index.php
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [L]
|
||||
</IfModule>
|
||||
|
||||
# Security headers
|
||||
<IfModule mod_headers.c>
|
||||
Header always set X-Frame-Options "SAMEORIGIN"
|
||||
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
|
||||
Header always set X-XSS-Protection "1; mode=block"
|
||||
|
||||
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
Header always set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;"
|
||||
</IfModule>
|
||||
|
||||
# Prevent directory listing
|
||||
Options -Indexes
|
||||
|
||||
# Protect dotfiles
|
||||
<FilesMatch "^\.">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
500
public/partage/index.php
Normal file
500
public/partage/index.php
Normal file
@@ -0,0 +1,500 @@
|
||||
<?php
|
||||
/**
|
||||
* Partage — Entry point for shared student submission forms.
|
||||
*
|
||||
* Routes:
|
||||
* /partage/<slug> — Render the share-link form (or password gate)
|
||||
* /partage/<slug>/submit — POST endpoint for form submissions via share link
|
||||
* /partage/thanks.php?id=N — Post-submission confirmation page
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/bootstrap.php';
|
||||
|
||||
// Parse the requested path from REQUEST_URI
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
|
||||
// Remove query string
|
||||
$basePath = parse_url($requestUri, PHP_URL_PATH);
|
||||
// Extract the part after /partage/
|
||||
$path = trim(str_replace('/partage/', '', $basePath), '/');
|
||||
|
||||
// Split into parts: /partage/<slug>/<action>
|
||||
$parts = explode('/', $path);
|
||||
$slug = $parts[0] ?? '';
|
||||
$action = $parts[1] ?? '';
|
||||
|
||||
// Validate slug format: YYYYMMDD-XXXXXXXX (17 chars)
|
||||
if (!preg_match('/^\d{8}-[A-Z2-7]{8}$/', $slug)) {
|
||||
http_response_code(404);
|
||||
die('Lien invalide.');
|
||||
}
|
||||
|
||||
// ── POST: form submission ─────────────────────────────────────────────────────
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'submit') {
|
||||
handleShareLinkSubmission($slug);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── GET: render form ─────────────────────────────────────────────────────────
|
||||
App::boot(); // boot database + CSRF
|
||||
|
||||
require_once APP_ROOT . '/src/ShareLink.php';
|
||||
$shareLinkModel = new ShareLink(Database::getInstance());
|
||||
|
||||
$validationResult = $shareLinkModel->validateLink($slug);
|
||||
|
||||
if (!$validationResult['valid']) {
|
||||
$reason = $validationResult['reason'];
|
||||
|
||||
if ($reason === 'not_found') {
|
||||
http_response_code(404);
|
||||
die('Ce lien de partage n\'existe pas ou a été supprimé.');
|
||||
}
|
||||
|
||||
if ($reason === 'disabled') {
|
||||
http_response_code(403);
|
||||
die('Ce lien de partage a été désactivé.');
|
||||
}
|
||||
|
||||
if ($reason === 'expired') {
|
||||
http_response_code(403);
|
||||
die('Ce lien de partage a expiré.');
|
||||
}
|
||||
|
||||
if ($reason === 'needs_password') {
|
||||
// Show password gate
|
||||
$link = $validationResult['link'];
|
||||
requirePasswordGate($link, $slug);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
die('Erreur inattendue.');
|
||||
}
|
||||
|
||||
// Link is valid — render the form
|
||||
$link = $validationResult['link'];
|
||||
renderShareLinkForm($slug, $link);
|
||||
|
||||
// ── Functions ─────────────────────────────────────────────────────────────────
|
||||
|
||||
function requirePasswordGate(array $link, string $slug): void
|
||||
{
|
||||
$error = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['share_password'])) {
|
||||
require_once APP_ROOT . '/src/ShareLink.php';
|
||||
$shareLinkModel = new ShareLink(Database::getInstance());
|
||||
|
||||
if ($shareLinkModel->verifyPassword($link, $_POST['share_password'])) {
|
||||
// Store verified status in session
|
||||
$_SESSION['share_verified_' . $slug] = true;
|
||||
// Redirect to clear POST data
|
||||
header('Location: /partage/' . $slug);
|
||||
exit;
|
||||
} else {
|
||||
$error = 'Mot de passe incorrect.';
|
||||
}
|
||||
}
|
||||
|
||||
$pageTitle = 'Accès protégé';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($pageTitle) ?></title>
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/system.css') ?>">
|
||||
<style>
|
||||
.password-gate {
|
||||
max-width: 400px;
|
||||
margin: 4rem auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.password-gate h1 { margin-bottom: 1.5rem; }
|
||||
.password-gate input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
margin: 0.5rem 0 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.password-gate button {
|
||||
padding: 0.75rem 2rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.password-error {
|
||||
color: red;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="password-gate">
|
||||
<h1>🔒 Accès protégé</h1>
|
||||
<?php if ($error): ?>
|
||||
<p class="password-error"><?= htmlspecialchars($error) ?></p>
|
||||
<?php endif; ?>
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token'] ?? '') ?>">
|
||||
<label for="share_password">Ce lien est protégé par un mot de passe :</label>
|
||||
<input type="password" id="share_password" name="share_password" required autofocus>
|
||||
<br>
|
||||
<button type="submit">Accéder au formulaire</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
function renderShareLinkForm(string $slug, array $link): void
|
||||
{
|
||||
require_once APP_ROOT . '/src/ThesisCreateController.php';
|
||||
|
||||
try {
|
||||
$ctrl = ThesisCreateController::make();
|
||||
extract($ctrl->loadFormData());
|
||||
} catch (Exception $e) {
|
||||
error_log('Failed to load form data: ' . $e->getMessage());
|
||||
die('Erreur lors du chargement du formulaire.');
|
||||
}
|
||||
|
||||
$formData = $_SESSION['form_data_share_' . $slug] ?? [];
|
||||
unset($_SESSION['form_data_share_' . $slug]);
|
||||
|
||||
// Generate a CSRF token specific to this share link (stored in session)
|
||||
$shareCsrfKey = 'share_csrf_' . $slug;
|
||||
if (empty($_SESSION[$shareCsrfKey])) {
|
||||
$_SESSION[$shareCsrfKey] = bin2hex(random_bytes(32));
|
||||
}
|
||||
$shareCsrfToken = $_SESSION[$shareCsrfKey];
|
||||
|
||||
$pageTitle = 'Soumettre un TFE';
|
||||
|
||||
// Determine if previously verified by password
|
||||
$isVerified = !empty($_SESSION['share_verified_' . $slug]);
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($pageTitle) ?></title>
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/system.css') ?>">
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/admin.css') ?>">
|
||||
<style>
|
||||
.student-body {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
.licence-explanation {
|
||||
background: #f9f9f9;
|
||||
border-left: 4px solid #666;
|
||||
padding: 1.5rem;
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.licence-info h3 { margin-top: 1.5rem; }
|
||||
.licence-degree { margin: 1.5rem 0; padding: 1rem; background: white; border: 1px solid #ddd; border-radius: 4px; }
|
||||
.licence-note { color: #666; font-size: 0.9rem; margin-top: 0.5rem; }
|
||||
.licence-generalites { margin-top: 2rem; }
|
||||
.form-footer {
|
||||
margin-top: 2rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 2px solid #333;
|
||||
}
|
||||
.share-badge {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: #e0f0ff;
|
||||
border: 1px solid #99c;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
h1 { margin-bottom: 0.5rem; }
|
||||
.mode-toggle { font-size: 0.9rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="admin-body student-body">
|
||||
<main id="main-content">
|
||||
<div class="thesis-add-header">
|
||||
<h1>Soumettre un TFE</h1>
|
||||
<?php if ($isVerified): ?>
|
||||
<span class="share-badge">🔓 Accès partagé</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
// Show flash messages from error redirect
|
||||
$flashError = $_SESSION['_flash_error'] ?? null;
|
||||
$flashSuccess = $_SESSION['_flash_success'] ?? null;
|
||||
unset($_SESSION['_flash_error'], $_SESSION['_flash_success']);
|
||||
?>
|
||||
<?php if ($flashError): ?>
|
||||
<div class="flash-error" role="alert"><?= htmlspecialchars($flashError) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($flashSuccess): ?>
|
||||
<div class="flash-success" role="alert"><?= htmlspecialchars($flashSuccess) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/partage/<?= urlencode($slug) ?>/submit" method="post" enctype="multipart/form-data" class="admin-form">
|
||||
<input type="hidden" name="share_link_token" value="<?= htmlspecialchars($shareCsrfToken) ?>">
|
||||
|
||||
<!-- ═══════════════════ Informations du TFE ═══════════════════ -->
|
||||
<fieldset>
|
||||
<legend>Informations du TFE</legend>
|
||||
|
||||
<?php $name = 'titre'; $label = 'Titre :'; $value = old($formData, 'titre'); $required = true; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
<?php $name = 'subtitle'; $label = 'Sous-titre (si applicable) :'; $value = old($formData, 'subtitle'); $required = false; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
<?php $name = 'auteurice'; $label = 'Auteur·ice(s) :'; $value = old($formData, 'auteurice'); $required = true; $attrs = ['autocomplete' => 'name']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
<?php $name = 'mail'; $label = 'Contact(s) (optionnel) [mail/site/insta/etc.] :'; $value = old($formData, 'mail'); $attrs = ['autocomplete' => 'email']; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
|
||||
<div class="admin-form-group">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="contact_public" value="1"
|
||||
<?= isset($formData['contact_public']) ? 'checked' : '' ?>>
|
||||
Je veux que mon contact soit accessible à toustes depuis la plateforme xamxam
|
||||
</label>
|
||||
<small>Si cette case est cochée, votre contact apparaîtra sur la page publique de votre TFE.</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="synopsis">Synopsis :</label>
|
||||
<textarea id="synopsis" name="synopsis" rows="7" required><?= old($formData, 'synopsis') ?></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Composition du jury ═══════════════════ -->
|
||||
<fieldset>
|
||||
<legend>Composition du jury</legend>
|
||||
|
||||
<?php $name = 'jury_president'; $label = 'Président·e du jury :'; $value = old($formData, 'jury_president'); include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
<?php $name = 'jury_promoteur'; $label = 'Promoteur·ice :'; $value = old($formData, 'jury_promoteur'); $hintCheckbox = ['name' => 'jury_promoteur_ext', 'label' => 'Externe à l\'erg', 'checked' => !empty($formData['jury_promoteur_ext'])]; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
|
||||
<div class="jury-additional">
|
||||
<p>Lecteurs·rices (optionnel) :</p>
|
||||
<?php for ($i = 0; $i < 4; $i++): ?>
|
||||
<div class="jury-additional-row">
|
||||
<input type="text"
|
||||
name="jury_lecteurs[]"
|
||||
placeholder="<?= $i === 0 ? 'Nom du lecteur·ice' : ''; ?>"
|
||||
value="<?= old($formData, "jury_lecteurs:$i") ?>">
|
||||
<label class="admin-checkbox-label">
|
||||
<input type="checkbox" name="jury_lecteurs_ext[<?= $i ?>]" value="1"
|
||||
<?= !empty($formData["jury_lecteurs_ext:$i"]) ? 'checked' : '' ?>>
|
||||
Externe
|
||||
</label>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Cadre académique ═══════════════════ -->
|
||||
<fieldset>
|
||||
<legend>Cadre académique</legend>
|
||||
|
||||
<?php
|
||||
$name = 'année'; $label = 'Année :'; $value = old($formData, 'année'); $required = true;
|
||||
$type = 'number';
|
||||
$placeholder = date('Y');
|
||||
$attrs = ['min' => 2000, 'max' => date('Y') + 1];
|
||||
include APP_ROOT . '/templates/partials/form/text-field.php';
|
||||
?>
|
||||
|
||||
<?php $name = 'orientation'; $label = 'Orientation :'; $options = $orientations; $selected = isset($formData['orientation']) ? $formData['orientation'] : ''; $required = true; $placeholder = ''; include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
<?php $name = 'ap'; $label = 'Atelier pluridisciplinaire :'; $options = $apPrograms; $selected = isset($formData['ap']) ? $formData['ap'] : ''; $required = true; $placeholder = ''; include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
<?php $name = 'finality'; $label = 'Finalité du master :'; $options = $finalityTypes; $selected = isset($formData['finality']) ? $formData['finality'] : ''; $required = true; $placeholder = ''; include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
|
||||
<?php $name = 'languages'; $label = 'Langue(s) :'; $options = $languages; $checked = $formData['languages'] ?? []; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
<?php $name = 'formats'; $label = 'Format(s) :'; $options = $formatTypes; $checked = $formData['formats'] ?? []; include APP_ROOT . '/templates/partials/form/checkbox-list.php'; ?>
|
||||
|
||||
<?php $name = 'tag'; $label = 'Mots-clés :'; $value = old($formData, 'tag'); $placeholder = 'sociologie, anthropologie, ...'; $hint = 'Séparez par des virgules. Max 10 mots-clés.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Fichiers ═══════════════════ -->
|
||||
<fieldset>
|
||||
<legend>Fichiers</legend>
|
||||
|
||||
<?php $name = 'couverture'; $label = 'Image de couverture :'; $accept = 'image/jpeg,image/png'; $hint = 'JPG, PNG. Taille max : 10 MB.'; include APP_ROOT . '/templates/partials/form/file-field.php'; ?>
|
||||
<?php $name = 'banner'; $label = 'Image bannière (accueil) :'; $accept = 'image/jpeg,image/png,image/webp'; $hint = 'JPG, PNG ou WEBP. Format paysage recommandé (4:1). Max 5 MB.'; include APP_ROOT . '/templates/partials/form/file-field.php'; ?>
|
||||
<?php $name = 'files'; $label = 'Fichiers du TFE :'; $accept = '.pdf,.jpg,.jpeg,.png,.mp4,.zip,.vtt'; $hint = 'PDF, JPG, PNG, MP4, ZIP. Max 50 MB par fichier. Pour les vidéos, un fichier .vtt de sous-titres peut être joint (il sera associé automatiquement à la vidéo correspondante).'; $multiple = true; include APP_ROOT . '/templates/partials/form/file-field.php'; ?>
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Métadonnées complémentaires ═══════════════════ -->
|
||||
<fieldset>
|
||||
<legend>Métadonnées complémentaires</legend>
|
||||
|
||||
<?php $name = 'license_id'; $label = 'Licence :'; $options = $licenseTypes; $selected = isset($formData['license_id']) ? $formData['license_id'] : ''; $placeholder = '— Inconnue —'; include APP_ROOT . '/templates/partials/form/select-field.php'; ?>
|
||||
|
||||
<?php $name = 'duration_info'; $label = 'Durée / Taille :'; $value = old($formData, 'duration_info'); $placeholder = 'Ex : 84 pages'; $hint = 'Durée (minutes) ou nombre de pages.'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
|
||||
<?php $name = 'lien'; $label = 'Lien (site / ressource) :'; $value = old($formData, 'lien'); $type = 'url'; $placeholder = 'https://...'; include APP_ROOT . '/templates/partials/form/text-field.php'; ?>
|
||||
|
||||
<?php
|
||||
$accessOptions = array_map(function($at) {
|
||||
return ['id' => $at['id'], 'name' => $at['name']];
|
||||
}, $enabledAccessTypes);
|
||||
$defaultAccessType = 2;
|
||||
$selectedAccessType = isset($formData['access_type_id'])
|
||||
? (int)$formData['access_type_id']
|
||||
: $defaultAccessType;
|
||||
$name = 'access_type_id';
|
||||
$label = 'Visibilité / Accès :';
|
||||
$options = $accessOptions;
|
||||
$selected = $selectedAccessType;
|
||||
$placeholder = null;
|
||||
$required = true;
|
||||
$attrs = [];
|
||||
include APP_ROOT . '/templates/partials/form/select-field.php';
|
||||
?>
|
||||
</fieldset>
|
||||
|
||||
<!-- ═══════════════════ Degrés d'ouverture ═══════════════════ -->
|
||||
<fieldset class="licence-explanation">
|
||||
<legend>Degrés d'ouverture et licences</legend>
|
||||
|
||||
<div class="licence-info">
|
||||
<h3>Je veux que mon TFE soit disponible sous les conditions suivantes :</h3>
|
||||
|
||||
<div class="licence-degree">
|
||||
<h4>🔓 Libre</h4>
|
||||
<p>Mon TFE est en libre accès à tout le monde sur la plateforme des TFE ainsi que dans la bibliothèque de l'erg. Je suis conscient·e des responsabilités et obligations légales qui viennent avec une diffusion externe – et acquiesce avoir lu la documentation prévue à cet effet par l'erg, ainsi qu'avoir discuté des enjeux d'une publication avec l'équipe pédagogique. J'accepte de partager mes droits de diffusion avec l'erg, ce uniquement dans le cadre d'une diffusion sur la plateforme xamxam.</p>
|
||||
<ul>
|
||||
<li><label><input type="checkbox" name="cc4r" value="1"> J'accepte les conditions collectives de réutilisation (CC4r) <em class="hint">(pas obligatoire)</em></label></li>
|
||||
<li><label><input type="checkbox" name="specific_license" value="1"> Je souhaite appliquer une licence spécifique à mon travail <em class="hint">(pas obligatoire)</em></label></li>
|
||||
</ul>
|
||||
<p class="licence-note"><em>Au moins une des deux cases doit être cochée pour le degré Libre.</em></p>
|
||||
</div>
|
||||
|
||||
<div class="licence-degree">
|
||||
<h4>🔒 Interne</h4>
|
||||
<p>Mon TFE et ma note d'intention ne sont accessibles que sur place en physique ainsi que sur la plateforme xamxam par la communauté erg. Une note descriptive est disponible sur le site à toustes. J'autorise une (ré-)utilisation et diffusion dans un contexte académique et didactique au sein de l'erg.</p>
|
||||
<p class="licence-note"><em>La diffusion limitée est protégée par le cadre académique/didactique, le travail pourrait donc être diffusé en interne et être cité par d'autres étudiant·es sans implications légales pour l'auteur·ice ni pour l'école.</em></p>
|
||||
<ul>
|
||||
<li><label><input type="checkbox" name="cc4r" value="1"> J'accepte les conditions collectives de réutilisation (CC4r) <em class="hint">(pas obligatoire)</em></label></li>
|
||||
<li><label><input type="checkbox" name="specific_license" value="1"> Je souhaite appliquer une licence spécifique à mon travail <em class="hint">(pas obligatoire)</em></label></li>
|
||||
</ul>
|
||||
<p class="licence-note"><em>Au moins une des deux cases doit être cochée.</em></p>
|
||||
</div>
|
||||
|
||||
<div class="licence-degree">
|
||||
<h4>🚫 Interdit</h4>
|
||||
<p>Mon TFE n'est pas disponible en physique ni sur le site. Une note descriptive est disponible sur le site.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="licence-generalites">
|
||||
<h3>Généralités</h3>
|
||||
<ul>
|
||||
<li>L'auteur·ice peut décider entre trois degrés de partage de son travail : <strong>libre</strong>, <strong>interne</strong>, <strong>interdit</strong>.</li>
|
||||
<li>L'auteur·ice peut, à tout moment, décider de <strong>restreindre</strong> le degré d'accès à son travail. Il ne peut néanmoins pas l'ouvrir davantage.</li>
|
||||
<li>Le choix effectué dans ce formulaire sera d'application <strong>une semaine après la soutenance orale</strong> de l'auteur·ice. Celui-ci peut donc décider de restreindre ce choix avant sa publication (mais pas l'ouvrir).</li>
|
||||
<li>L'erg se réserve le droit de restreindre le degré d'ouverture du TFE – ce en accord avec le règlement.</li>
|
||||
<li>Dans tous les cas, l'auteur·ice garde les droits d'auteurs, de diffusion, d'utilisation, etc. de son travail – sauf si la licence choisie restreindrait ses droits.</li>
|
||||
<li>La diffusion « xamxam » est indépendante de la diffusion à la BAIU.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="submit" name="go">Soumettre</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle share link form submission.
|
||||
*/
|
||||
function handleShareLinkSubmission(string $slug): void
|
||||
{
|
||||
session_start();
|
||||
|
||||
require_once APP_ROOT . '/src/ShareLink.php';
|
||||
$shareLinkModel = new ShareLink(Database::getInstance());
|
||||
|
||||
$link = $shareLinkModel->findBySlug($slug);
|
||||
|
||||
if ($link === null || !$link['is_active'] || ($link['expires_at'] !== null && strtotime($link['expires_at']) < time())) {
|
||||
http_response_code(403);
|
||||
die('Ce lien n\'est plus valide.');
|
||||
}
|
||||
|
||||
// Check password verification if link has a password
|
||||
if ($link['password_hash'] !== null && empty($_SESSION['share_verified_' . $slug])) {
|
||||
// Allow password to be submitted along with the form (first attempt or re-verify)
|
||||
if (isset($_POST['share_password_submit'])) {
|
||||
if ($shareLinkModel->verifyPassword($link, $_POST['share_password_submit'])) {
|
||||
$_SESSION['share_verified_' . $slug] = true;
|
||||
} else {
|
||||
http_response_code(403);
|
||||
die('Mot de passe incorrect.');
|
||||
}
|
||||
} else {
|
||||
http_response_code(403);
|
||||
die('Vous devez entrer le mot de passe avant de soumettre le formulaire.');
|
||||
}
|
||||
}
|
||||
|
||||
// Validate share-link CSRF token
|
||||
$shareCsrfKey = 'share_csrf_' . $slug;
|
||||
if (!isset($_POST['share_link_token'], $_SESSION[$shareCsrfKey])
|
||||
|| !hash_equals($_SESSION[$shareCsrfKey], $_POST['share_link_token'])) {
|
||||
error_log('Share link CSRF validation failed for ' . $slug);
|
||||
http_response_code(403);
|
||||
die('Token de sécurité invalide.');
|
||||
}
|
||||
|
||||
require_once APP_ROOT . '/src/ThesisCreateController.php';
|
||||
|
||||
try {
|
||||
$ctrl = ThesisCreateController::make();
|
||||
$thesisId = $ctrl->submit($_POST, $_FILES);
|
||||
|
||||
// Mark the link as used
|
||||
require_once APP_ROOT . '/src/ShareLink.php';
|
||||
$shareLinkModel = new ShareLink(Database::getInstance());
|
||||
$shareLinkModel->incrementUsage($link['id']);
|
||||
|
||||
// Clean up share-specific session data
|
||||
unset($_SESSION[$shareCsrfKey]);
|
||||
unset($_SESSION['share_verified_' . $slug]);
|
||||
|
||||
// Redirect to thanks page
|
||||
header('Location: /partage/thanks.php?id=' . urlencode((string)$thesisId));
|
||||
exit();
|
||||
} catch (Exception $e) {
|
||||
error_log('Share link submission error: ' . $e->getMessage());
|
||||
|
||||
$_SESSION['_flash_error'] = $e->getMessage();
|
||||
$_SESSION['form_data_share_' . $slug] = $_POST;
|
||||
$_SESSION[$shareCsrfKey] = bin2hex(random_bytes(32)); // Regenerate token
|
||||
|
||||
// Redirect back to the form
|
||||
header('Location: /partage/' . urlencode($slug));
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to retrieve old form data (with support for array keys via : delimiter)
|
||||
*/
|
||||
function old(array $data, string $key, string $default = ''): string {
|
||||
// Support nested keys like "jury_lecteurs:0"
|
||||
$parts = explode(':', $key);
|
||||
$value = $data;
|
||||
foreach ($parts as $part) {
|
||||
if (is_array($value) && isset($value[$part])) {
|
||||
$value = $value[$part];
|
||||
} else {
|
||||
$value = $default;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return is_array($value) ? htmlspecialchars(json_encode($value)) : htmlspecialchars((string)$value);
|
||||
}
|
||||
87
public/partage/thanks.php
Normal file
87
public/partage/thanks.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Thanks page for share-link submissions.
|
||||
* Displays a centered confirmation with a link to create another thesis via the same link.
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/bootstrap.php';
|
||||
|
||||
App::boot();
|
||||
|
||||
$thesisId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
|
||||
|
||||
if ($thesisId <= 0) {
|
||||
http_response_code(400);
|
||||
die('ID de TFE invalide.');
|
||||
}
|
||||
|
||||
// Verify the thesis exists
|
||||
$db = Database::getInstance();
|
||||
$thesis = $db->getThesis($thesisId);
|
||||
if (!$thesis) {
|
||||
http_response_code(404);
|
||||
die('TFE introuvable.');
|
||||
}
|
||||
|
||||
// Get the share link slug from the referer path
|
||||
$pathParts = explode('/', trim($_SERVER['REQUEST_URI'] ?? '', '/'));
|
||||
$slug = count($pathParts) >= 2 ? $pathParts[0] : null;
|
||||
|
||||
$pageTitle = 'Merci — TFE enregistré';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($pageTitle) ?></title>
|
||||
<link rel="stylesheet" href="<?= App::assetV('/assets/css/system.css') ?>">
|
||||
<style>
|
||||
.thanks-center {
|
||||
max-width: 600px;
|
||||
margin: 4rem auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
.thanks-center h1 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
.thanks-center p {
|
||||
font-size: 1.1rem;
|
||||
color: #555;
|
||||
margin: 1rem 0 2rem;
|
||||
}
|
||||
.thanks-center .thesis-title {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
color: #333;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.thanks-center .btn-add-another {
|
||||
display: inline-block;
|
||||
padding: 0.75rem 2rem;
|
||||
background: #333;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.thanks-center .btn-add-another:hover {
|
||||
background: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="thanks-center">
|
||||
<h1>✅ Merci !</h1>
|
||||
<p>Votre TFE a bien été enregistré sur la plateforme.</p>
|
||||
<?php if ($thesis): ?>
|
||||
<div class="thesis-title"><?= htmlspecialchars($thesis['title']) ?> — <?= htmlspecialchars($thesis['authors'] ?? '') ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($slug): ?>
|
||||
<a href="/partage/<?= urlencode($slug) ?>" class="btn-add-another">+ Soumettre un autre TFE</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user