Files
Portfolio-Esteban/pages/admin.php
T
EstebanandClaude Sonnet 5 6fb78e0bbb Corrige le double sélecteur de fichier sur les zones de drop PDF (panel admin)
L'input file étant imbriqué dans la zone cliquable, input.click() déclenchait
un évènement click qui remontait (bubble) jusqu'à la zone et relançait
input.click() une seconde fois. Ajout d'un garde-fou sur e.target.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 15:42:52 +02:00

469 lines
24 KiB
PHP

<?php
// Titre de l'onglet
$pageTitle = 'Administration';
// On ajoute ici le header
require_once('./../elements/header.php');
// Ajout de la base de donnée
require_once '../functions/databaseConnection.php';
require_once("../functions/listUser.php");
require_once('../functions/listProject.php');
require_once('../functions/listXP.php');
require_once('../functions/listSchool.php');
require_once('../functions/listSkills.php');
require_once('../functions/listSettings.php');
// L'utilisateur doit être administrateur
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Location: ../pages/home.php');
exit;
}
// On met les variables à null ici pour éviter qu'il garde les options en premier chargement
$projetAModifier = null;
$xpAModifier = null;
$ecoleAModifier = null;
$skillAModifier = null;
// Si on modifie XP, Projets, École ou Compétence
if (isset($_GET['edit_project']) && is_numeric($_GET['edit_project'])) {
$projetAModifier = recupererProjetParId((int) $_GET['edit_project']);
}
if (isset($_GET['edit_xp']) && is_numeric($_GET['edit_xp'])) {
$xpAModifier = recupererXPParId((int) $_GET['edit_xp']);
}
if (isset($_GET['edit_school']) && is_numeric($_GET['edit_school'])) {
$ecoleAModifier = recupererEcoleParId((int) $_GET['edit_school']);
}
if (isset($_GET['edit_skill']) && is_numeric($_GET['edit_skill'])) {
$skillAModifier = recupererSkillParId((int) $_GET['edit_skill']);
}
// Valeurs actuelles de la notification de la page d'accueil
$noticeTitle = recupererSetting('site_notice_title') ?? '';
$noticeText = recupererSetting('site_notice_text') ?? '';
$noticeEnabled = recupererSetting('site_notice_enabled') ?? '1';
// Page du panneau d'administration
?>
<div class="admin-page">
<!-- Titre du panneau -->
<div class="page-header">
<h1>Panneau d'administration</h1>
<p>Gère le contenu du site : projets, expériences, écoles, compétences, notification et utilisateurs.</p>
</div>
<!-- Sous-navigation : accès rapide à chaque catégorie -->
<nav class="admin-nav">
<a href="#admin-projets"><i class="fa-solid fa-folder-open"></i> Projets</a>
<a href="#admin-experiences"><i class="fa-solid fa-briefcase"></i> Expériences</a>
<a href="#admin-ecoles"><i class="fa-solid fa-graduation-cap"></i> Écoles</a>
<a href="#admin-competences"><i class="fa-solid fa-code"></i> Compétences</a>
<a href="#admin-notification"><i class="fa-solid fa-bell"></i> Notification</a>
<a href="#admin-utilisateurs"><i class="fa-solid fa-users"></i> Utilisateurs</a>
<a href="#admin-donnees"><i class="fa-solid fa-database"></i> Données</a>
</nav>
<!-- ========== SECTION PROJETS ========== -->
<section id="admin-projets" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-folder-open"></i> Projets</h2>
<!-- Formulaire (replié par défaut, ouvert automatiquement en mode modification) -->
<details class="admin-block" <?php echo $projetAModifier ? 'open' : ''; ?>>
<summary><?php echo $projetAModifier ? 'Modifier le projet : ' . htmlspecialchars($projetAModifier['title']) : '+ Nouveau projet'; ?></summary>
<div class="project-form-card">
<!-- enctype multipart/form-data : nécessaire pour l'upload du PDF -->
<!-- en envoie les données à addProject.php -->
<form method="POST" enctype="multipart/form-data" action="<?php echo $projetAModifier ? '../scripts/updateProject.php' : '../scripts/addProject.php'; ?>">
<?php if ($projetAModifier): ?>
<input type="hidden" name="id" value="<?php echo (int) $projetAModifier['id']; ?>">
<?php endif; ?>
<label>Titre *</label>
<input type="text" name="title" required value="<?php echo $projetAModifier ? htmlspecialchars($projetAModifier['title']) : ''; ?>">
<label>Status (optionnel, affiché sur l'image : En cours, Terminé...)</label>
<input type="text" name="status" placeholder="En cours..." value="<?php echo $projetAModifier ? htmlspecialchars($projetAModifier['status'] ?? '') : ''; ?>">
<label>Tags (virgules)</label>
<input type="text" name="tags" value="<?php echo $projetAModifier ? htmlspecialchars($projetAModifier['tags']) : ''; ?>">
<label>Description *</label>
<textarea name="description" required><?php echo $projetAModifier ? htmlspecialchars($projetAModifier['description']) : ''; ?></textarea>
<label>Lien de l'image *</label>
<input type="url" name="imageurl" placeholder="https://..." required value="<?php echo $projetAModifier ? htmlspecialchars($projetAModifier['image_url']) : ''; ?>">
<label>Page détaillée en markdown (optionnel, prioritaire sur le PDF et le lien externe)</label>
<textarea name="markdown" class="markdown-textarea" placeholder="# Mon projet&#10;&#10;Description complète en **markdown**..."><?php echo $projetAModifier ? htmlspecialchars($projetAModifier['markdown'] ?? '') : ''; ?></textarea>
<label>Document PDF (optionnel, utilisé si pas de markdown)</label>
<?php if ($projetAModifier && !empty($projetAModifier['pdf_filename'])): ?>
<div class="pdf-current">
<i class="fa-solid fa-file-pdf"></i>
<span><?php echo htmlspecialchars($projetAModifier['pdf_filename']); ?></span>
<a href="../scripts/viewPdf.php?type=projet&id=<?php echo (int) $projetAModifier['id']; ?>" target="_blank" rel="noopener noreferrer">Voir</a>
<label class="checkbox-row pdf-remove-row">
<input type="checkbox" name="remove_pdf"> Supprimer ce PDF
</label>
</div>
<?php endif; ?>
<div class="pdf-dropzone" data-input="pdf-project">
<i class="fa-solid fa-cloud-arrow-up"></i>
<p>Glisse un PDF ici, ou <span class="pdf-dropzone-browse">clique pour choisir un fichier</span></p>
<span class="pdf-dropzone-filename"></span>
<input type="file" name="pdf" id="pdf-project" accept="application/pdf,.pdf" hidden>
</div>
<label>Lien externe (optionnel, utilisé si pas de markdown ni de PDF)</label>
<input type="url" name="linkurl" placeholder="https://..." value="<?php echo $projetAModifier ? htmlspecialchars($projetAModifier['link_url'] ?? '') : ''; ?>">
<input type="submit" name="submitproject" value="<?php echo $projetAModifier ? 'Modifier le projet' : '+ Ajouter le projet'; ?>" class="btn-submit">
</form>
</div>
</details>
<!-- Liste des projets avec modification / suppression -->
<?php afficherProjetsAdmin(); ?>
</section>
<!-- ========== SECTION EXPÉRIENCES ========== -->
<section id="admin-experiences" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-briefcase"></i> Expériences</h2>
<details class="admin-block" <?php echo $xpAModifier ? 'open' : ''; ?>>
<summary><?php echo $xpAModifier ? 'Modifier l\'expérience : ' . htmlspecialchars($xpAModifier['title']) : '+ Nouvelle expérience'; ?></summary>
<div class="project-form-card">
<!-- en envoie les données à addXP.php -->
<form method="POST" enctype="multipart/form-data" action="<?php echo $xpAModifier ? '../scripts/updateXP.php' : '../scripts/addXP.php'; ?>">
<?php if ($xpAModifier): ?>
<input type="hidden" name="id" value="<?php echo (int) $xpAModifier['id']; ?>">
<?php endif; ?>
<label>Titre *</label>
<input type="text" name="title" required value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['title']) : ''; ?>">
<label>Status (optionnel, affiché sur l'image : En cours, Terminé...)</label>
<input type="text" name="status" placeholder="En cours..." value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['status'] ?? '') : ''; ?>">
<label>Type *</label>
<input type="text" name="xptype" required value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['xptype']) : ''; ?>">
<label>Tags (virgules)</label>
<input type="text" name="tags" value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['tags']) : ''; ?>">
<label>Description *</label>
<textarea name="description" required><?php echo $xpAModifier ? htmlspecialchars($xpAModifier['description']) : ''; ?></textarea>
<label>Durée *</label>
<input type="text" name="duration" required value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['duration']) : ''; ?>">
<label>Lien de l'image *</label>
<input type="url" name="imageurl" placeholder="https://..." required value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['image_url']) : ''; ?>">
<label>Page détaillée en markdown (optionnel, prioritaire sur le PDF et le lien externe)</label>
<textarea name="markdown" class="markdown-textarea" placeholder="# Mon expérience&#10;&#10;Description complète en **markdown**..."><?php echo $xpAModifier ? htmlspecialchars($xpAModifier['markdown'] ?? '') : ''; ?></textarea>
<label>Document PDF (optionnel, utilisé si pas de markdown)</label>
<?php if ($xpAModifier && !empty($xpAModifier['pdf_filename'])): ?>
<div class="pdf-current">
<i class="fa-solid fa-file-pdf"></i>
<span><?php echo htmlspecialchars($xpAModifier['pdf_filename']); ?></span>
<a href="../scripts/viewPdf.php?type=experience&id=<?php echo (int) $xpAModifier['id']; ?>" target="_blank" rel="noopener noreferrer">Voir</a>
<label class="checkbox-row pdf-remove-row">
<input type="checkbox" name="remove_pdf"> Supprimer ce PDF
</label>
</div>
<?php endif; ?>
<div class="pdf-dropzone" data-input="pdf-xp">
<i class="fa-solid fa-cloud-arrow-up"></i>
<p>Glisse un PDF ici, ou <span class="pdf-dropzone-browse">clique pour choisir un fichier</span></p>
<span class="pdf-dropzone-filename"></span>
<input type="file" name="pdf" id="pdf-xp" accept="application/pdf,.pdf" hidden>
</div>
<label>Lien externe (optionnel, utilisé si pas de markdown ni de PDF)</label>
<input type="url" name="linkurl" placeholder="https://..." value="<?php echo $xpAModifier ? htmlspecialchars($xpAModifier['link_url'] ?? '') : ''; ?>">
<input type="submit" name="submitxp" value="<?php echo $xpAModifier ? 'Modifier l\'experience' : '+ Ajouter l\'experience'; ?>" class="btn-submit">
</form>
</div>
</details>
<!-- Liste des expériences avec modification / suppression -->
<?php afficherXPAdmin(); ?>
</section>
<!-- ========== SECTION ÉCOLES ========== -->
<section id="admin-ecoles" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-graduation-cap"></i> Écoles</h2>
<details class="admin-block" <?php echo $ecoleAModifier ? 'open' : ''; ?>>
<summary><?php echo $ecoleAModifier ? 'Modifier l\'école : ' . htmlspecialchars($ecoleAModifier['title']) : '+ Nouvelle école'; ?></summary>
<div class="project-form-card">
<!-- on envoie les données à addSchool.php -->
<form method="POST" enctype="multipart/form-data" action="<?php echo $ecoleAModifier ? '../scripts/updateSchool.php' : '../scripts/addSchool.php'; ?>">
<?php if ($ecoleAModifier): ?>
<input type="hidden" name="id" value="<?php echo (int) $ecoleAModifier['id']; ?>">
<?php endif; ?>
<label>Nom de l'école *</label>
<input type="text" name="title" required value="<?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['title']) : ''; ?>">
<label>Status (optionnel, affiché sur l'image : En cours, Diplômé...)</label>
<input type="text" name="status" placeholder="En cours..." value="<?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['status'] ?? '') : ''; ?>">
<label>Tags (virgules)</label>
<input type="text" name="tags" value="<?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['tags']) : ''; ?>">
<label>Description (diplôme, spécialité...) *</label>
<textarea name="description" required><?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['description']) : ''; ?></textarea>
<label>Période (ex : 2023 - 2025) *</label>
<input type="text" name="duration" required value="<?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['duration']) : ''; ?>">
<label>Lien de l'image *</label>
<input type="url" name="imageurl" placeholder="https://..." required value="<?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['image_url']) : ''; ?>">
<label>Page détaillée en markdown (optionnel, prioritaire sur le PDF et le lien externe)</label>
<textarea name="markdown" class="markdown-textarea" placeholder="# Mon école&#10;&#10;Description complète en **markdown**..."><?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['markdown'] ?? '') : ''; ?></textarea>
<label>Document PDF (optionnel, utilisé si pas de markdown)</label>
<?php if ($ecoleAModifier && !empty($ecoleAModifier['pdf_filename'])): ?>
<div class="pdf-current">
<i class="fa-solid fa-file-pdf"></i>
<span><?php echo htmlspecialchars($ecoleAModifier['pdf_filename']); ?></span>
<a href="../scripts/viewPdf.php?type=ecole&id=<?php echo (int) $ecoleAModifier['id']; ?>" target="_blank" rel="noopener noreferrer">Voir</a>
<label class="checkbox-row pdf-remove-row">
<input type="checkbox" name="remove_pdf"> Supprimer ce PDF
</label>
</div>
<?php endif; ?>
<div class="pdf-dropzone" data-input="pdf-school">
<i class="fa-solid fa-cloud-arrow-up"></i>
<p>Glisse un PDF ici, ou <span class="pdf-dropzone-browse">clique pour choisir un fichier</span></p>
<span class="pdf-dropzone-filename"></span>
<input type="file" name="pdf" id="pdf-school" accept="application/pdf,.pdf" hidden>
</div>
<label>Lien externe (optionnel, utilisé si pas de markdown ni de PDF)</label>
<input type="url" name="linkurl" placeholder="https://..." value="<?php echo $ecoleAModifier ? htmlspecialchars($ecoleAModifier['link_url'] ?? '') : ''; ?>">
<input type="submit" name="submitschool" value="<?php echo $ecoleAModifier ? 'Modifier l\'école' : '+ Ajouter l\'école'; ?>" class="btn-submit">
</form>
</div>
</details>
<!-- Liste des écoles avec modification / suppression -->
<?php afficherEcolesAdmin(); ?>
</section>
<!-- ========== SECTION COMPÉTENCES ========== -->
<section id="admin-competences" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-code"></i> Compétences</h2>
<details class="admin-block" <?php echo $skillAModifier ? 'open' : ''; ?>>
<summary><?php echo $skillAModifier ? 'Modifier la catégorie : ' . htmlspecialchars($skillAModifier['title']) : '+ Nouvelle catégorie de compétences'; ?></summary>
<div class="project-form-card">
<!-- on envoie les données à addSkill.php -->
<form method="POST" action="<?php echo $skillAModifier ? '../scripts/updateSkill.php' : '../scripts/addSkill.php'; ?>">
<?php if ($skillAModifier): ?>
<input type="hidden" name="id" value="<?php echo (int) $skillAModifier['id']; ?>">
<?php endif; ?>
<label>Titre de la catégorie *</label>
<input type="text" name="title" required value="<?php echo $skillAModifier ? htmlspecialchars($skillAModifier['title']) : ''; ?>">
<label>Icône de la catégorie (classes Font Awesome, ex : fa-solid fa-terminal)</label>
<input type="text" name="icon" placeholder="fa-solid fa-star" value="<?php echo $skillAModifier ? htmlspecialchars($skillAModifier['icon']) : ''; ?>">
<label>Compétences (une par ligne, format : "icone|texte" ou juste "texte") *</label>
<textarea name="items" placeholder="fa-brands fa-python|Python&#10;fa-solid fa-terminal|Bash&#10;Ansible" required><?php echo $skillAModifier ? htmlspecialchars($skillAModifier['items']) : ''; ?></textarea>
<label>Position (ordre d'affichage, du plus petit au plus grand)</label>
<input type="text" name="position" value="<?php echo $skillAModifier ? (int) $skillAModifier['position'] : '0'; ?>">
<input type="submit" name="submitskill" value="<?php echo $skillAModifier ? 'Modifier la compétence' : '+ Ajouter la compétence'; ?>" class="btn-submit">
</form>
</div>
</details>
<!-- Liste des compétences avec modification / suppression -->
<div class="skills-section skills-section-admin">
<?php afficherSkillsAdmin(); ?>
</div>
</section>
<!-- ========== SECTION NOTIFICATION ========== -->
<section id="admin-notification" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-bell"></i> Notification de la page d'accueil</h2>
<div class="project-container">
<div class="project-form-card">
<!-- on envoie les données à updateSettings.php -->
<form method="POST" action="../scripts/updateSettings.php">
<label>Titre (affiché en gras)</label>
<input type="text" name="notice_title" value="<?php echo htmlspecialchars($noticeTitle); ?>">
<label>Texte</label>
<textarea name="notice_text"><?php echo htmlspecialchars($noticeText); ?></textarea>
<!-- Checkbox pour activer / désactiver la notification sans la supprimer -->
<label class="checkbox-row">
<input type="checkbox" name="notice_enabled" value="1" <?php echo $noticeEnabled === '1' ? 'checked' : ''; ?>>
Afficher la notification sur la page d'accueil
</label>
<input type="submit" name="submitsettings" value="Enregistrer la notification" class="btn-submit">
</form>
</div>
</div>
</section>
<!-- ========== SECTION UTILISATEURS ========== -->
<section id="admin-utilisateurs" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-users"></i> Utilisateurs</h2>
<details class="admin-block">
<summary>+ Nouveau compte</summary>
<div class="project-form-card">
<!-- en envoie les données addUser.php -->
<form method="POST" action="../scripts/addUser.php">
<label>Nom du compte</label>
<input type="text" name="user" required>
<!-- role admin ou user -->
<label>Rôle (admin/user)</label>
<input type="text" name="role" placeholder="user" required>
<!-- mot de passe -->
<label>Mot de passe</label>
<input type="password" name="passwd" required>
<!-- Le bouton qui déclenche le PHP en haut de page -->
<input type="submit" name="submituser" value="Ajouter l'utilisateur" class="btn-submit">
</form>
</div>
</details>
<!-- Affichage de la liste des utilisateurs -->
<?php afficherUsersAdmin(); ?>
</section>
<!-- ========== SECTION DONNÉES (EXPORT / IMPORT) ========== -->
<section id="admin-donnees" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-database"></i> Données du site</h2>
<div class="project-container">
<div class="project-form-card">
<!-- Export : télécharge toutes les données en JSON -->
<h3 class="admin-data-title"><i class="fa-solid fa-download"></i> Exporter</h3>
<p class="admin-data-text">
Télécharge une sauvegarde JSON de toutes les données du site
(projets, expériences, écoles, compétences, notification, utilisateurs).
</p>
<a class="btn-submit admin-export-btn" href="../scripts/exportData.php">
<i class="fa-solid fa-download"></i> Exporter les données (JSON)
</a>
<hr class="admin-data-separator">
<!-- Import : écrase les données actuelles avec celles du fichier -->
<h3 class="admin-data-title"><i class="fa-solid fa-upload"></i> Importer</h3>
<p class="admin-data-text admin-data-warning">
⚠️ L'import <strong>écrase définitivement</strong> les données actuelles des tables
présentes dans le fichier. Fais un export avant, au cas où !
</p>
<form method="POST" action="../scripts/importData.php" enctype="multipart/form-data"
onsubmit="return confirm('ATTENTION : les données actuelles du site vont être écrasées par celles du fichier. Continuer ?');">
<input type="file" name="jsonfile" accept=".json,application/json" required>
<input type="submit" value="Importer les données" class="btn-submit btn-delete">
</form>
</div>
</div>
</section>
</div>
<!-- Gestion des zones de drag & drop PDF (projets, expériences, écoles) -->
<script>
(function () {
document.querySelectorAll('.pdf-dropzone').forEach(function (zone) {
var input = document.getElementById(zone.dataset.input);
var filenameEl = zone.querySelector('.pdf-dropzone-filename');
function afficherFichier(file) {
if (!file) return;
if (file.type !== 'application/pdf') {
filenameEl.textContent = 'Ce fichier n\'est pas un PDF.';
zone.classList.remove('has-file');
return;
}
filenameEl.textContent = file.name;
zone.classList.add('has-file');
}
// Clic sur la zone = ouvre le sélecteur de fichier
// Garde-fou : input.click() déclenche lui-même un évènement "click" qui remonte
// (bubble) jusqu'à la zone puisque l'input est à l'intérieur de celle-ci. Sans ce
// test, ce clic remonté relance input.click() une seconde fois => double sélecteur.
zone.addEventListener('click', function (e) {
if (e.target === input) return;
input.click();
});
input.addEventListener('change', function () {
afficherFichier(input.files[0]);
});
['dragenter', 'dragover'].forEach(function (evt) {
zone.addEventListener(evt, function (e) {
e.preventDefault();
zone.classList.add('dragging');
});
});
['dragleave', 'drop'].forEach(function (evt) {
zone.addEventListener(evt, function (e) {
e.preventDefault();
zone.classList.remove('dragging');
});
});
zone.addEventListener('drop', function (e) {
var file = e.dataTransfer.files[0];
if (file) {
input.files = e.dataTransfer.files;
afficherFichier(file);
}
});
});
})();
</script>
<?php
// On ajoute ici le footer
require_once('../elements/footer.php'); ?>