167 lines
6.7 KiB
PHP
167 lines
6.7 KiB
PHP
<?php
|
|
|
|
// Page de détails d'un projet / d'une expérience / d'une école
|
|
// Le contenu markdown (stocké en BDD, éditable depuis le panel admin) est rendu
|
|
// côté navigateur avec marked.js puis nettoyé avec DOMPurify (sécurité XSS)
|
|
|
|
// Connexion BDD (avant le header car on doit pouvoir rediriger si l'élément n'existe pas)
|
|
require_once('../functions/databaseConnection.php');
|
|
|
|
// Types autorisés : liste blanche pour empêcher toute injection de nom de table
|
|
// (le PDF est stocké dans une table associée depuis la migration Juillet 2026 (6) : on la
|
|
// joint juste pour le nom de fichier, jamais pour son contenu binaire)
|
|
$types = [
|
|
'projet' => ['table' => 'projects', 'pdf' => 'project_pdfs', 'pdf_colonne' => 'project_id', 'retour' => './projects.php', 'label' => 'Projet'],
|
|
'experience' => ['table' => 'experiences', 'pdf' => 'experience_pdfs', 'pdf_colonne' => 'experience_id', 'retour' => './xp.php', 'label' => 'Expérience'],
|
|
'ecole' => ['table' => 'schools', 'pdf' => 'school_pdfs', 'pdf_colonne' => 'school_id', 'retour' => './xp.php', 'label' => 'École'],
|
|
];
|
|
|
|
$type = $_GET['type'] ?? '';
|
|
$id = $_GET['id'] ?? '';
|
|
|
|
// Type inconnu ou id invalide : retour à l'accueil
|
|
if (!isset($types[$type]) || !is_numeric($id)) {
|
|
header('Location: ./home.php');
|
|
exit;
|
|
}
|
|
|
|
$infos = $types[$type];
|
|
|
|
// Récupération de l'élément demandé
|
|
// (pdf_data n'est volontairement pas sélectionné : le PDF est servi à part par viewPdf.php
|
|
// dans une <iframe>, inutile de charger son contenu binaire en mémoire ici)
|
|
$colonnesCommunes = 'e.id, e.title, e.status, e.tags, e.description, e.image_url, e.markdown, e.link_url, p.pdf_filename';
|
|
$colonnesParType = [
|
|
'projet' => $colonnesCommunes,
|
|
'experience' => $colonnesCommunes . ', e.xptype, e.duration',
|
|
'ecole' => $colonnesCommunes . ', e.duration',
|
|
];
|
|
|
|
try {
|
|
$stmt = $dbh->prepare(
|
|
'SELECT ' . $colonnesParType[$type] . '
|
|
FROM `' . $infos['table'] . '` e
|
|
LEFT JOIN `' . $infos['pdf'] . '` p ON p.`' . $infos['pdf_colonne'] . '` = e.id
|
|
WHERE e.id = :id'
|
|
);
|
|
$stmt->bindValue(':id', (int) $id, PDO::PARAM_INT);
|
|
$stmt->execute();
|
|
$element = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
} catch (PDOException $e) {
|
|
$element = false;
|
|
}
|
|
|
|
$hasMarkdown = $element && trim($element['markdown'] ?? '') !== '';
|
|
$hasPdf = $element && trim($element['pdf_filename'] ?? '') !== '';
|
|
|
|
// Élément inexistant, ou sans markdown ni PDF : retour à la liste
|
|
if (!$element || (!$hasMarkdown && !$hasPdf)) {
|
|
header('Location: ' . $infos['retour']);
|
|
exit;
|
|
}
|
|
|
|
// Titre de l'onglet
|
|
$pageTitle = $element['title'];
|
|
|
|
// On ajoute le header
|
|
require_once('../elements/header.php');
|
|
|
|
// Préparation des données pour l'affichage
|
|
$titre = htmlspecialchars($element['title']);
|
|
$image = htmlspecialchars($element['image_url'] ?? '');
|
|
$status = htmlspecialchars($element['status'] ?? '');
|
|
$duration = htmlspecialchars($element['duration'] ?? '');
|
|
$xptype = htmlspecialchars($element['xptype'] ?? '');
|
|
$tags = [];
|
|
if (!empty($element['tags'])) {
|
|
$tags = array_filter(array_map('trim', explode(',', $element['tags'])));
|
|
}
|
|
?>
|
|
|
|
<div class="details-page">
|
|
|
|
<!-- Bouton retour vers la liste -->
|
|
<a class="btn btn-ghost details-back" href="<?php echo $infos['retour']; ?>">
|
|
<i class="fa-solid fa-arrow-left"></i> Retour
|
|
</a>
|
|
|
|
<article class="details-card">
|
|
|
|
<?php if (!empty($image)): ?>
|
|
<div class="details-banner project-banner">
|
|
<img src="<?php echo $image; ?>" alt="Image : <?php echo $titre; ?>">
|
|
<?php if (!empty($status)): ?>
|
|
<div class="project-state"><?php echo $status; ?></div>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="details-content">
|
|
|
|
<span class="details-type"><?php echo $infos['label']; ?></span>
|
|
<h1><?php echo $titre; ?></h1>
|
|
|
|
<!-- Métadonnées : type / durée si renseignés -->
|
|
<?php if (!empty($xptype) || !empty($duration)): ?>
|
|
<p class="details-meta">
|
|
<?php if (!empty($xptype)): ?><span><i class="fa-solid fa-tag"></i> <?php echo $xptype; ?></span><?php endif; ?>
|
|
<?php if (!empty($duration)): ?><span><i class="fa-solid fa-clock"></i> <?php echo $duration; ?></span><?php endif; ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
|
|
<?php if (!empty($tags)): ?>
|
|
<div class="project-tags">
|
|
<?php foreach ($tags as $tag): ?>
|
|
<span class="tag"><?php echo htmlspecialchars($tag); ?></span>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($hasMarkdown): ?>
|
|
<!-- Le markdown brut est placé ici (échappé), puis rendu en HTML par le script plus bas -->
|
|
<div id="markdown-source" hidden><?php echo htmlspecialchars($element['markdown']); ?></div>
|
|
<div class="markdown-content" id="markdown-target">
|
|
<p>Chargement du contenu...</p>
|
|
</div>
|
|
<?php elseif ($hasPdf): ?>
|
|
<!-- Mini lecteur PDF : le fichier est servi par viewPdf.php depuis la base de données -->
|
|
<div class="pdf-viewer-wrap">
|
|
<div class="pdf-viewer-toolbar">
|
|
<span><i class="fa-solid fa-file-pdf"></i> <?php echo htmlspecialchars($element['pdf_filename']); ?></span>
|
|
<a class="btn btn-ghost pdf-download-btn" href="../scripts/viewPdf.php?type=<?php echo $type; ?>&id=<?php echo (int) $id; ?>&download=1" download>
|
|
<i class="fa-solid fa-download"></i> Télécharger
|
|
</a>
|
|
</div>
|
|
<iframe
|
|
class="pdf-viewer"
|
|
src="../scripts/viewPdf.php?type=<?php echo $type; ?>&id=<?php echo (int) $id; ?>"
|
|
title="Aperçu PDF : <?php echo $titre; ?>"
|
|
></iframe>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
|
|
</article>
|
|
|
|
</div>
|
|
|
|
<?php if ($hasMarkdown): ?>
|
|
<!-- marked.js : convertit le markdown en HTML / DOMPurify : nettoie le HTML généré (sécurité) -->
|
|
<script src="https://cdn.jsdelivr.net/npm/marked@12/marked.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/dompurify@3/dist/purify.min.js"></script>
|
|
<script>
|
|
(function () {
|
|
var source = document.getElementById('markdown-source');
|
|
var cible = document.getElementById('markdown-target');
|
|
// textContent décode automatiquement les entités HTML => on récupère le markdown d'origine
|
|
var markdown = source.textContent;
|
|
cible.innerHTML = DOMPurify.sanitize(marked.parse(markdown, { breaks: true }));
|
|
})();
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
// Ajout du footer
|
|
require_once('../elements/footer.php'); ?>
|