mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
- lib/AdminAuth.php: new class with requireLogin(), login(), logout(), isAuthenticated(); starts session with hardened cookie params (HttpOnly, SameSite=Strict, Secure, Path=/admin) — also resolves item #8 (session cookie hardening) - requireLogin() auto-authenticates from nginx Basic Auth credentials ($_SERVER['PHP_AUTH_PW']) so the user only sees one browser prompt; falls back to /admin/login.php if the proxy is absent/misconfigured - config/admin_credentials.php: gitignored credential store; define ADMIN_PASSWORD_HASH with a bcrypt hash to enable PHP auth - config/admin_credentials.example.php: template for the above - config/bootstrap.php: auto-loads admin_credentials.php if present - .gitignore: exclude config/admin_credentials.php - public/admin/login.php: fallback login form (shown only when nginx Basic Auth is bypassed / proxy absent) - public/admin/logout.php: session destruction + redirect to login - All 7 admin PHP files: replace session_start() with AdminAuth::requireLogin() (defence-in-depth behind nginx Basic Auth) - public/admin/inc/head.php: Déconnexion button when ADMIN_PASSWORD_HASH is defined - nginx/PHP_AUTH_LAYER.md: documents dual-auth architecture, UX flow, and setup instructions - docs/TODO.SECURITY.md: items #2 and #8 moved to Resolved; priority order updated (all CRITICAL done)
251 lines
14 KiB
PHP
251 lines
14 KiB
PHP
<?php
|
|
// Bootstrap application
|
|
require_once __DIR__ . "/../../config/bootstrap.php";
|
|
require_once __DIR__ . '/../../lib/AdminAuth.php';
|
|
|
|
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
|
AdminAuth::requireLogin();
|
|
|
|
if (empty($_SESSION["csrf_token"])) {
|
|
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
$pageTitle = "Ajout de TFE";
|
|
|
|
// Load database helper
|
|
require_once __DIR__ . '/../../lib/Database.php';
|
|
|
|
try {
|
|
$db = new Database();
|
|
$orientations = $db->getAllOrientations();
|
|
$apPrograms = $db->getAllAPPrograms();
|
|
$finalityTypes = $db->getAllFinalityTypes();
|
|
$languages = $db->getAllLanguages();
|
|
$formatTypes = $db->getAllFormatTypes();
|
|
} catch (Exception $e) {
|
|
error_log("Failed to load form data: " . $e->getMessage());
|
|
die("Erreur lors du chargement du formulaire. Veuillez réessayer plus tard.");
|
|
}
|
|
|
|
// Get error message and preserved form data from session (if redirected back from error)
|
|
$error = isset($_SESSION["form_error"]) ? $_SESSION["form_error"] : null;
|
|
$formData = isset($_SESSION["form_data"]) ? $_SESSION["form_data"] : [];
|
|
|
|
// Clear session data after retrieving
|
|
unset($_SESSION["form_error"]);
|
|
unset($_SESSION["form_data"]);
|
|
|
|
// Helper function to get old form value
|
|
function old($key, $default = "")
|
|
{
|
|
global $formData;
|
|
return isset($formData[$key])
|
|
? htmlspecialchars($formData[$key])
|
|
: $default;
|
|
}
|
|
|
|
// Helper function to check if value was previously selected
|
|
function wasSelected($key, $value)
|
|
{
|
|
global $formData;
|
|
if (!isset($formData[$key])) {
|
|
return false;
|
|
}
|
|
if (is_array($formData[$key])) {
|
|
return in_array($value, $formData[$key]);
|
|
}
|
|
return $formData[$key] == $value;
|
|
}
|
|
?>
|
|
<?php require_once __DIR__ . "/inc/head.php"; ?>
|
|
<main>
|
|
<?php if ($error): ?>
|
|
<div class="error-message">
|
|
<strong>⚠️ Erreur:</strong> <?php echo htmlspecialchars($error); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<form action="actions/formulaire.php" method="post" enctype="multipart/form-data">
|
|
<!-- CSRF Protection -->
|
|
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars(
|
|
$_SESSION["csrf_token"],
|
|
); ?>">
|
|
|
|
|
|
<fieldset>
|
|
<legend>Informations de base</legend>
|
|
<label for="auteurice">Nom/Prénom/Pseudo *</label>
|
|
<input type="text" id="auteurice" name="auteurice" placeholder="Nom de l'auteur·ice" value="<?php echo old(
|
|
"auteurice",
|
|
); ?>" required>
|
|
|
|
<label for="mail">Contact (email, site web, insta, ...)</label>
|
|
<input type="text" id="mail" name="mail" placeholder="votre.email@example.com ou @instagram" value="<?php echo old(
|
|
"mail",
|
|
); ?>">
|
|
<label for="année">Année diplômante *</label>
|
|
<input type="number" id="année" name="année" min="2000" max="<?php echo date(
|
|
"Y",
|
|
) + 1; ?>" placeholder="<?php echo date(
|
|
"Y",
|
|
); ?>" value="<?php echo old("année"); ?>" required>
|
|
</fieldset>
|
|
|
|
|
|
<fieldset>
|
|
<legend>Informations académiques</legend>
|
|
<label for="orientation">Orientation principale *</label>
|
|
<select id="orientation" name="orientation" required>
|
|
<option value="">-- Sélectionner une orientation --</option>
|
|
<?php foreach ($orientations as $orientation): ?>
|
|
<option value="<?php echo htmlspecialchars(
|
|
$orientation["id"],
|
|
); ?>" <?php echo wasSelected(
|
|
"orientation",
|
|
$orientation["id"],
|
|
)
|
|
? "selected"
|
|
: ""; ?>>
|
|
<?php echo htmlspecialchars(
|
|
$orientation["name"],
|
|
); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<label for="ap">Atelier Pratique (AP) *</label>
|
|
<select id="ap" name="ap" required>
|
|
<option value="">-- Sélectionner un AP --</option>
|
|
<?php foreach ($apPrograms as $ap): ?>
|
|
<option value="<?php echo htmlspecialchars(
|
|
$ap["id"],
|
|
); ?>" <?php echo wasSelected("ap", $ap["id"])
|
|
? "selected"
|
|
: ""; ?>>
|
|
<?php echo htmlspecialchars($ap["name"]); ?>
|
|
<?php if (
|
|
$ap["code"]
|
|
): ?> (<?php echo htmlspecialchars(
|
|
$ap["code"],
|
|
); ?>)<?php endif; ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<label for="finality">Finalité du master *</label>
|
|
<select id="finality" name="finality" required>
|
|
<option value="">-- Sélectionner une finalité --</option>
|
|
<?php foreach ($finalityTypes as $finality): ?>
|
|
<option value="<?php echo htmlspecialchars(
|
|
$finality["id"],
|
|
); ?>" <?php echo wasSelected(
|
|
"finality",
|
|
$finality["id"],
|
|
)
|
|
? "selected"
|
|
: ""; ?>>
|
|
<?php echo htmlspecialchars($finality["name"]); ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
|
|
<label for="promoteurice">Promoteur·ice(s)</label>
|
|
<input type="text" id="promoteurice" name="promoteurice" placeholder="Nom du/de la promoteur·ice (si plusieurs, séparer par des virgules)" value="<?php echo old(
|
|
"promoteurice",
|
|
); ?>">
|
|
</fieldset>
|
|
|
|
|
|
|
|
<fieldset>
|
|
<legend>À propos du TFE</legend>
|
|
<label for="titre">Titre du mémoire *</label>
|
|
<input type="text" id="titre" name="titre" placeholder="Titre de votre TFE" value="<?php echo old(
|
|
"titre",
|
|
); ?>" required>
|
|
|
|
<label for="subtitle">Sous-titre (si applicable)</label>
|
|
<input type="text" id="subtitle" name="subtitle" placeholder="Sous-titre de votre TFE" value="<?php echo old(
|
|
"subtitle",
|
|
); ?>">
|
|
<label for="synopsis">Synopsis (environ 200 mots) *</label>
|
|
<textarea id="synopsis" name="synopsis" rows="8" placeholder="Décrivez votre TFE en quelques paragraphes..." required><?php echo old(
|
|
"synopsis",
|
|
); ?></textarea>
|
|
<label for="problématique">Problématique</label>
|
|
<textarea id="problématique" name="problématique" rows="4" placeholder="La problématique principale de votre mémoire..."><?php echo old(
|
|
"problématique",
|
|
); ?></textarea>
|
|
<label>Langue(s) du TFE * (sélection multiple possible)</label>
|
|
<ul class="no-style">
|
|
<?php foreach ($languages as $language): ?>
|
|
<li>
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="languages[]" value="<?php echo htmlspecialchars(
|
|
$language["id"],
|
|
); ?>" <?php echo wasSelected(
|
|
"languages",
|
|
$language["id"],
|
|
)
|
|
? "checked"
|
|
: ""; ?>>
|
|
<?php echo htmlspecialchars($language["name"]); ?>
|
|
</label>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
|
|
<label>Format(s) (sélection multiple possible)</label>
|
|
<ul class="no-style">
|
|
<?php foreach ($formatTypes as $format): ?>
|
|
<li>
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" name="formats[]" value="<?php echo htmlspecialchars(
|
|
$format["id"],
|
|
); ?>" <?php echo wasSelected(
|
|
"formats",
|
|
$format["id"],
|
|
)
|
|
? "checked"
|
|
: ""; ?>>
|
|
<?php echo htmlspecialchars($format["name"]); ?>
|
|
</label>
|
|
</li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
|
|
<label for="tag">Mots-clés (max 10, séparés par des virgules)</label>
|
|
<input type="text" id="tag" name="tag" placeholder="typographie, photographie, outils libre, post-colonial..." value="<?php echo old(
|
|
"tag",
|
|
); ?>">
|
|
<small>Séparez les mots-clés par des virgules. Maximum 10 mots-clés.</small>
|
|
<label for="duration_info">Durée/Taille (si applicable)</label>
|
|
<input type="text" id="duration_info" name="duration_info" placeholder="Ex: 68 minutes, 128 pages, 78 pages + 15 minutes" value="<?php echo old(
|
|
"duration_info",
|
|
); ?>">
|
|
<small>Indiquez la durée (en minutes) ou le nombre de pages de votre TFE.</small>
|
|
<label for="lien">Lien vers un site web ou ressource en ligne</label>
|
|
<input type="url" id="lien" name="lien" placeholder="https://monmemoire.erg.be/..." value="<?php echo old(
|
|
"lien",
|
|
); ?>">
|
|
</fieldset>
|
|
|
|
|
|
<fieldset>
|
|
<legend>Fichiers</legend>
|
|
<label for="couverture">Importer une image de couverture</label>
|
|
<small>Formats acceptés : JPG, PNG. Taille max : 10MB.</small>
|
|
<input type="file" id="couverture" name="couverture" accept="image/jpeg,image/png">
|
|
|
|
<label for="files">Importer le TFE et les fichiers annexes</label>
|
|
<small>Formats acceptés : PDF, JPG, PNG, MP4, ZIP. Taille max par fichier : 50MB.</small>
|
|
<small>Si vous voulez importer un dossier, créez une archive ZIP.</small>
|
|
<input type="file" id="files" name="files[]" multiple accept=".pdf,.jpg,.jpeg,.png,.mp4,.zip">
|
|
</fieldset>
|
|
|
|
<br>
|
|
<input type="submit" name="go" value="Soumettre mon TFE">
|
|
</form>
|
|
</main>
|
|
|
|
<?php require_once __DIR__ . "/inc/footer.php"; ?>
|