mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 11:09:18 +02:00
- Feature 1: public /licence.php fetches 'licenses' page from DB, renders Markdown - Feature 1: nav.php adds 'Licence' link with active state - Feature 2: Database::getPage(), savePage(), getAllPages() methods - Feature 2: bundled src/Parsedown.php (MIT, zero-dependency) - Feature 2: apropos.php now renders 'about' page content from DB via Parsedown - Feature 2: admin/pages.php (list) + admin/pages-edit.php (EasyMDE editor) - Feature 2: admin/actions/page.php (auth+CSRF+validation+save) - Feature 2: admin/head.php adds 'Pages statiques' nav link - Feature 3: storage/schema.sql seeds 8 CC license types - Feature 3: storage/migrations/003_seed_license_types.sql (applied to live DB) - Feature 3: Database::getLicenseTypes() / getAllLicenseTypes() - Feature 3: admin/add.php + formulaire.php: license_id field on add form - Feature 3: admin/edit.php: license_id field on edit form with raw FK lookup - Feature 3: tfe.php: shows 'Licence :' meta row when non-null - Feature 6: main.css: .card__media--gradient styles - Feature 6: index.php: deterministic HSL gradient placeholder cards - Feature 6: Database::getLatestYearTheses() + getLatestPublishedYear() - Feature 6: index.php default home = random latest-year theses with info label
354 lines
15 KiB
PHP
354 lines
15 KiB
PHP
<?php
|
|
// Bootstrap application
|
|
require_once __DIR__ . "/../../config/bootstrap.php";
|
|
require_once __DIR__ . '/../../src/AdminAuth.php';
|
|
|
|
// PHP-level auth guard (defence-in-depth behind nginx Basic Auth)
|
|
AdminAuth::requireLogin();
|
|
|
|
// Generate CSRF token
|
|
if (empty($_SESSION['csrf_token'])) {
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
}
|
|
|
|
require_once __DIR__ . '/../../src/Database.php';
|
|
|
|
$thesisId = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
|
$error = null;
|
|
$success = null;
|
|
|
|
if ($thesisId <= 0) {
|
|
die("ID invalide");
|
|
}
|
|
|
|
try {
|
|
$db = new Database();
|
|
$pdo = $db->getPDO();
|
|
|
|
// Handle form submission
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['csrf_token'])) {
|
|
// Verify CSRF token
|
|
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
|
|
throw new Exception("Erreur de sécurité : token invalide.");
|
|
}
|
|
|
|
try {
|
|
$db->beginTransaction();
|
|
|
|
// Update thesis basic info
|
|
$editLicenseId = filter_var($_POST['license_id'] ?? '', FILTER_VALIDATE_INT) ?: null;
|
|
|
|
$stmt = $pdo->prepare("
|
|
UPDATE theses SET
|
|
title = ?,
|
|
subtitle = ?,
|
|
year = ?,
|
|
orientation_id = ?,
|
|
ap_program_id = ?,
|
|
finality_id = ?,
|
|
synopsis = ?,
|
|
file_size_info = ?,
|
|
baiu_link = ?,
|
|
license_id = ?,
|
|
is_published = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ?
|
|
");
|
|
|
|
$stmt->execute([
|
|
trim($_POST['titre']),
|
|
!empty($_POST['subtitle']) ? trim($_POST['subtitle']) : null,
|
|
intval($_POST['année']),
|
|
intval($_POST['orientation']),
|
|
intval($_POST['ap']),
|
|
intval($_POST['finality']),
|
|
trim($_POST['synopsis']),
|
|
!empty($_POST['duration_info']) ? trim($_POST['duration_info']) : null,
|
|
!empty($_POST['lien']) ? trim($_POST['lien']) : null,
|
|
$editLicenseId,
|
|
isset($_POST['is_published']) ? 1 : 0,
|
|
$thesisId
|
|
]);
|
|
|
|
// Update authors
|
|
$pdo->prepare("DELETE FROM thesis_authors WHERE thesis_id = ?")->execute([$thesisId]);
|
|
$authorsRaw = trim($_POST['auteurice'] ?? '');
|
|
if (!empty($authorsRaw)) {
|
|
$authors = array_map('trim', explode(',', $authorsRaw));
|
|
foreach ($authors as $index => $authorName) {
|
|
if (!empty($authorName)) {
|
|
$authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? ($_POST['mail'] ?? null) : null);
|
|
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)");
|
|
$stmt->execute([$thesisId, $authorId, $index + 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update supervisors
|
|
$pdo->prepare("DELETE FROM thesis_supervisors WHERE thesis_id = ?")->execute([$thesisId]);
|
|
$supervisorsRaw = trim($_POST['promoteurice'] ?? '');
|
|
if (!empty($supervisorsRaw)) {
|
|
$supervisors = array_map('trim', explode(',', $supervisorsRaw));
|
|
foreach ($supervisors as $index => $supervisorName) {
|
|
if (!empty($supervisorName)) {
|
|
$supervisorId = $db->findOrCreateSupervisor($supervisorName);
|
|
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)");
|
|
$stmt->execute([$thesisId, $supervisorId, $index + 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update languages
|
|
$pdo->prepare("DELETE FROM thesis_languages WHERE thesis_id = ?")->execute([$thesisId]);
|
|
if (isset($_POST['languages']) && is_array($_POST['languages'])) {
|
|
foreach ($_POST['languages'] as $languageId) {
|
|
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)");
|
|
$stmt->execute([$thesisId, intval($languageId)]);
|
|
}
|
|
}
|
|
|
|
// Update formats
|
|
$pdo->prepare("DELETE FROM thesis_formats WHERE thesis_id = ?")->execute([$thesisId]);
|
|
if (isset($_POST['formats']) && is_array($_POST['formats'])) {
|
|
foreach ($_POST['formats'] as $formatId) {
|
|
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)");
|
|
$stmt->execute([$thesisId, intval($formatId)]);
|
|
}
|
|
}
|
|
|
|
// Update keywords
|
|
$pdo->prepare("DELETE FROM thesis_keywords WHERE thesis_id = ?")->execute([$thesisId]);
|
|
$keywordsRaw = trim($_POST['tag'] ?? '');
|
|
if (!empty($keywordsRaw)) {
|
|
$keywords = array_map('trim', explode(',', $keywordsRaw));
|
|
$keywords = array_slice($keywords, 0, 10); // Max 10
|
|
foreach ($keywords as $keyword) {
|
|
if (!empty($keyword)) {
|
|
$keywordId = $db->findOrCreateKeyword($keyword);
|
|
if ($keywordId) {
|
|
$stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)");
|
|
$stmt->execute([$thesisId, $keywordId]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$db->commit();
|
|
$success = "TFE mis à jour avec succès!";
|
|
|
|
// Regenerate CSRF token
|
|
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
|
|
|
} catch (Exception $e) {
|
|
$db->rollback();
|
|
$error = $e->getMessage();
|
|
error_log("Edit error: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// Load thesis data
|
|
$thesis = $db->getThesis($thesisId);
|
|
|
|
if (!$thesis) {
|
|
die("TFE non trouvé");
|
|
}
|
|
|
|
// Load current relationships
|
|
$stmt = $pdo->prepare("SELECT language_id FROM thesis_languages WHERE thesis_id = ?");
|
|
$stmt->execute([$thesisId]);
|
|
$currentLanguages = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
$stmt = $pdo->prepare("SELECT format_id FROM thesis_formats WHERE thesis_id = ?");
|
|
$stmt->execute([$thesisId]);
|
|
$currentFormats = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
|
|
|
// Load reference data
|
|
$orientations = $db->getAllOrientations();
|
|
$apPrograms = $db->getAllAPPrograms();
|
|
$finalityTypes = $db->getAllFinalityTypes();
|
|
$languages = $db->getAllLanguages();
|
|
$formatTypes = $db->getAllFormatTypes();
|
|
$licenseTypes = $db->getAllLicenseTypes();
|
|
|
|
// Fetch raw license_id FK (view only exposes license_type name string)
|
|
$licenseStmt = $pdo->prepare("SELECT license_id FROM theses WHERE id = ?");
|
|
$licenseStmt->execute([$thesisId]);
|
|
$currentLicenseId = $licenseStmt->fetchColumn();
|
|
|
|
// Set page title for header
|
|
$pageTitle = "Éditer TFE - " . htmlspecialchars($thesis['title']);
|
|
|
|
} catch (Exception $e) {
|
|
error_log("Error loading edit page: " . $e->getMessage());
|
|
die("Erreur lors du chargement: " . $e->getMessage());
|
|
}
|
|
?>
|
|
<?php require_once APP_ROOT . '/templates/admin/head.php'; ?>
|
|
|
|
<main class="admin-main">
|
|
<h1 class="admin-page-title">Modifier un TFE</h1>
|
|
|
|
<?php if ($error): ?>
|
|
<div class="admin-alert admin-alert--error">⚠ <?= htmlspecialchars($error) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($success): ?>
|
|
<div class="admin-alert admin-alert--success">✓ <?= htmlspecialchars($success) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<form method="post" action="edit.php?id=<?= $thesisId ?>" class="admin-form">
|
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="auteurice">Auteur·ice(s) :</label>
|
|
<input class="admin-input" type="text" id="auteurice" name="auteurice"
|
|
value="<?= htmlspecialchars($thesis['authors']) ?>" required>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="mail">Contact :</label>
|
|
<input class="admin-input" type="text" id="mail" name="mail" value="">
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="année">Année :</label>
|
|
<input class="admin-input" type="number" id="année" name="année"
|
|
value="<?= $thesis['year'] ?>" required>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="orientation">Orientation :</label>
|
|
<select class="admin-select" id="orientation" name="orientation" required>
|
|
<?php foreach ($orientations as $o): ?>
|
|
<option value="<?= $o['id'] ?>"
|
|
<?= ($thesis['orientation'] == $o['name']) ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($o['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="ap">Atelier pluridisciplinaire :</label>
|
|
<select class="admin-select" id="ap" name="ap" required>
|
|
<?php foreach ($apPrograms as $ap): ?>
|
|
<option value="<?= $ap['id'] ?>"
|
|
<?= ($thesis['ap_program'] == $ap['name']) ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($ap['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="finality">Finalité du master :</label>
|
|
<select class="admin-select" id="finality" name="finality" required>
|
|
<?php foreach ($finalityTypes as $f): ?>
|
|
<option value="<?= $f['id'] ?>"
|
|
<?= ($thesis['finality_type'] == $f['name']) ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($f['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="promoteurice">Promoteur·ice(s) :</label>
|
|
<input class="admin-input" type="text" id="promoteurice" name="promoteurice"
|
|
value="<?= htmlspecialchars($thesis['supervisors'] ?? '') ?>">
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="license_id">Licence :</label>
|
|
<select class="admin-select" id="license_id" name="license_id">
|
|
<option value="">— Inconnue —</option>
|
|
<?php foreach ($licenseTypes as $lt): ?>
|
|
<option value="<?= $lt['id'] ?>"
|
|
<?= ($currentLicenseId == $lt['id']) ? 'selected' : '' ?>>
|
|
<?= htmlspecialchars($lt['name']) ?>
|
|
</option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="titre">Titre :</label>
|
|
<input class="admin-input" type="text" id="titre" name="titre"
|
|
value="<?= htmlspecialchars($thesis['title']) ?>" required>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="subtitle">Sous-titre :</label>
|
|
<input class="admin-input" type="text" id="subtitle" name="subtitle"
|
|
value="<?= htmlspecialchars($thesis['subtitle'] ?? '') ?>">
|
|
</div>
|
|
|
|
<div class="admin-form-row" style="align-items:start;">
|
|
<label class="admin-label" for="synopsis">Synopsis :</label>
|
|
<textarea class="admin-textarea" id="synopsis" name="synopsis" rows="7" required><?= htmlspecialchars($thesis['synopsis'] ?? '') ?></textarea>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label">Langue(s) :</label>
|
|
<div class="admin-checkbox-list">
|
|
<?php foreach ($languages as $lang): ?>
|
|
<label class="admin-checkbox-label">
|
|
<input type="checkbox" name="languages[]" value="<?= $lang['id'] ?>"
|
|
<?= in_array($lang['id'], $currentLanguages) ? 'checked' : '' ?>>
|
|
<?= htmlspecialchars($lang['name']) ?>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label">Format(s) :</label>
|
|
<div class="admin-checkbox-list">
|
|
<?php foreach ($formatTypes as $fmt): ?>
|
|
<label class="admin-checkbox-label">
|
|
<input type="checkbox" name="formats[]" value="<?= $fmt['id'] ?>"
|
|
<?= in_array($fmt['id'], $currentFormats) ? 'checked' : '' ?>>
|
|
<?= htmlspecialchars($fmt['name']) ?>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="tag">Mots-clés :</label>
|
|
<div>
|
|
<input class="admin-input" type="text" id="tag" name="tag"
|
|
value="<?= htmlspecialchars($thesis['keywords'] ?? '') ?>">
|
|
<p class="admin-hint">Séparer par des virgules. Max 10.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="duration_info">Durée / Taille :</label>
|
|
<input class="admin-input" type="text" id="duration_info" name="duration_info"
|
|
value="<?= htmlspecialchars($thesis['file_size_info'] ?? '') ?>">
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label" for="lien">Lien externe :</label>
|
|
<input class="admin-input" type="url" id="lien" name="lien"
|
|
value="<?= htmlspecialchars($thesis['baiu_link'] ?? '') ?>">
|
|
</div>
|
|
|
|
<div class="admin-form-row">
|
|
<label class="admin-label">Publication :</label>
|
|
<label class="admin-checkbox-label">
|
|
<input type="checkbox" name="is_published" value="1"
|
|
<?= $thesis['is_published'] ? 'checked' : '' ?>>
|
|
Publier ce TFE sur le site public
|
|
</label>
|
|
</div>
|
|
|
|
<div class="admin-submit-wrap">
|
|
<button type="submit" class="admin-btn">Enregistrer</button>
|
|
<a href="/admin/thanks.php?id=<?= $thesisId ?>" class="admin-btn-secondary" style="margin-left:.75rem;">Annuler</a>
|
|
</div>
|
|
</form>
|
|
</main>
|
|
|
|
<?php require_once APP_ROOT . '/templates/admin/footer.php'; ?>
|