diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30efe56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Config Claude Code (local, pas utile au dépôt) +.claude/ + +# macOS +.DS_Store + +# Éditeurs +.vscode/ +.idea/ diff --git a/captcha.php b/captcha.php index db19866..4bc8f56 100644 --- a/captcha.php +++ b/captcha.php @@ -1,5 +1,5 @@ @@ -31,6 +40,22 @@ $currentPage = basename($_SERVER['PHP_SELF']); <?php echo htmlspecialchars($pageTitle); ?> + + + + + + + + + + + + + + + + diff --git a/functions/listLogos.php b/functions/listLogos.php index 8f335ae..73f5cef 100644 --- a/functions/listLogos.php +++ b/functions/listLogos.php @@ -118,7 +118,7 @@ function afficherLogosAdmin() { echo ''; echo '
'; echo 'Modifier'; - echo 'Supprimer'; + echo 'Supprimer'; echo '
'; echo ''; } diff --git a/functions/listProject.php b/functions/listProject.php index da36552..ecdc508 100644 --- a/functions/listProject.php +++ b/functions/listProject.php @@ -10,15 +10,22 @@ function listeProjets() { // Essayer try { - // Requête SQL qui récupère titre, tags, description, image_url - // SELECT * : les colonnes markdown / link_url sont optionnelles (la page fonctionne - // même si la migration SQL n'a pas encore été exécutée) - $stmt = $dbh->prepare('SELECT * FROM projects ORDER BY id DESC'); + // On liste les colonnes explicitement (pas de SELECT *) : le PDF (pdf_data) est + // stocké à part dans project_pdfs (voir migration Juillet 2026 (6)), on ne récupère + // ici que son nom de fichier via LEFT JOIN, sans jamais charger son contenu binaire + // (jusqu'à 15 Mo par PDF) en mémoire pour une simple liste de projets + $stmt = $dbh->prepare(' + SELECT p.id, p.title, p.status, p.tags, p.description, p.image_url, p.markdown, p.link_url, p.created_at, + pp.pdf_filename + FROM projects p + LEFT JOIN project_pdfs pp ON pp.project_id = p.id + ORDER BY p.id DESC + '); // Execution de la requête SQL $stmt->execute(); // Retourne toutes les données collectés dans un ARRAY return $stmt->fetchAll(PDO::FETCH_ASSOC); - } catch (PDOException $e) { // En cas d'erreur + } catch (PDOException $e) { // En cas d'erreur die("Erreur de connexion à la base de donnée portfolio :".$e->getMessage()); return []; } @@ -29,7 +36,15 @@ function recupererProjetParId($id) { global $dbh; try { - $stmt = $dbh->prepare('SELECT * FROM projects WHERE id = :id'); + // Même principe que listeProjets() : le PDF est joint pour son nom de fichier + // uniquement, jamais son contenu binaire + $stmt = $dbh->prepare(' + SELECT p.id, p.title, p.status, p.tags, p.description, p.image_url, p.markdown, p.link_url, p.created_at, + pp.pdf_filename + FROM projects p + LEFT JOIN project_pdfs pp ON pp.project_id = p.id + WHERE p.id = :id + '); $stmt->bindValue(':id', $id, PDO::PARAM_INT); $stmt->execute(); return $stmt->fetch(PDO::FETCH_ASSOC); @@ -164,7 +179,7 @@ function afficherProjetsAdmin() { // Boutons qui vont permettrent la modification / Suppresion echo '
'; echo 'Modifier'; - echo 'Supprimer'; + echo 'Supprimer'; echo '
'; echo ''; diff --git a/functions/listSchool.php b/functions/listSchool.php index facfd56..b871ec6 100644 --- a/functions/listSchool.php +++ b/functions/listSchool.php @@ -10,9 +10,17 @@ function listeEcoles() { global $dbh; try { - // SELECT * : les colonnes markdown / link_url sont optionnelles (la page fonctionne - // même si la migration SQL n'a pas encore été exécutée) - $stmt = $dbh->prepare('SELECT * FROM schools ORDER BY id DESC'); + // On liste les colonnes explicitement (pas de SELECT *) : le PDF (pdf_data) est + // stocké à part dans school_pdfs (voir migration Juillet 2026 (6)), on ne récupère + // ici que son nom de fichier via LEFT JOIN, sans jamais charger son contenu binaire + // (jusqu'à 15 Mo par PDF) en mémoire pour une simple liste d'écoles + $stmt = $dbh->prepare(' + SELECT s.id, s.title, s.status, s.tags, s.description, s.duration, s.image_url, s.markdown, s.link_url, s.created_at, + sp.pdf_filename + FROM schools s + LEFT JOIN school_pdfs sp ON sp.school_id = s.id + ORDER BY s.id DESC + '); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { @@ -25,7 +33,15 @@ function recupererEcoleParId($id) { global $dbh; try { - $stmt = $dbh->prepare('SELECT * FROM schools WHERE id = :id'); + // Même principe que listeEcoles() : le PDF est joint pour son nom de fichier + // uniquement, jamais son contenu binaire + $stmt = $dbh->prepare(' + SELECT s.id, s.title, s.status, s.tags, s.description, s.duration, s.image_url, s.markdown, s.link_url, s.created_at, + sp.pdf_filename + FROM schools s + LEFT JOIN school_pdfs sp ON sp.school_id = s.id + WHERE s.id = :id + '); $stmt->bindValue(':id', $id, PDO::PARAM_INT); $stmt->execute(); return $stmt->fetch(PDO::FETCH_ASSOC); @@ -98,7 +114,7 @@ function afficherEcoleCard($ecole, $admin = false) { if ($admin) { echo '
'; echo 'Modifier'; - echo 'Supprimer'; + echo 'Supprimer'; echo '
'; } diff --git a/functions/listSkills.php b/functions/listSkills.php index 7b0562b..6972ceb 100644 --- a/functions/listSkills.php +++ b/functions/listSkills.php @@ -99,7 +99,7 @@ function afficherSkillCard($skill, $admin = false) { if ($admin) { echo '
'; echo 'Modifier'; - echo 'Supprimer'; + echo 'Supprimer'; echo '
'; } diff --git a/functions/listStats.php b/functions/listStats.php new file mode 100644 index 0000000..1015090 --- /dev/null +++ b/functions/listStats.php @@ -0,0 +1,27 @@ + 0, 'experiences' => 0]; + + try { + $stats['projets'] = (int) $dbh->query('SELECT COUNT(*) FROM projects')->fetchColumn(); + } catch (PDOException $e) { + // Table pas encore créée : on laisse le compteur à 0 + } + + try { + $stats['experiences'] = (int) $dbh->query('SELECT COUNT(*) FROM experiences')->fetchColumn(); + } catch (PDOException $e) { + // Table pas encore créée : on laisse le compteur à 0 + } + + return $stats; +} diff --git a/functions/listUser.php b/functions/listUser.php index 1c3c69c..f7c0470 100644 --- a/functions/listUser.php +++ b/functions/listUser.php @@ -19,7 +19,7 @@ function afficherUsersAdmin() echo '

Créé le : ' . htmlspecialchars($user["created_at"]) . '

'; echo '
'; - echo 'Supprimer'; + echo 'Supprimer'; echo '
'; echo ''; diff --git a/functions/listXP.php b/functions/listXP.php index 6cf7551..dc24e65 100644 --- a/functions/listXP.php +++ b/functions/listXP.php @@ -13,15 +13,22 @@ function listeXP() { // Essayer try { - // Requête SQL qui récupère titre, type, tags, description, durée, image_url - // SELECT * : les colonnes markdown / link_url sont optionnelles (la page fonctionne - // même si la migration SQL n'a pas encore été exécutée) - $stmt = $dbh->prepare('SELECT * FROM experiences ORDER BY id DESC'); + // On liste les colonnes explicitement (pas de SELECT *) : le PDF (pdf_data) est + // stocké à part dans experience_pdfs (voir migration Juillet 2026 (6)), on ne + // récupère ici que son nom de fichier via LEFT JOIN, sans jamais charger son contenu + // binaire (jusqu'à 15 Mo par PDF) en mémoire pour une simple liste d'expériences + $stmt = $dbh->prepare(' + SELECT e.id, e.title, e.status, e.xptype, e.tags, e.description, e.duration, e.image_url, e.markdown, e.link_url, e.created_at, + ep.pdf_filename + FROM experiences e + LEFT JOIN experience_pdfs ep ON ep.experience_id = e.id + ORDER BY e.id DESC + '); // Execution de la requête SQL $stmt->execute(); // Retourne toutes les données collectés dans un ARRAY return $stmt->fetchAll(PDO::FETCH_ASSOC); - } catch (PDOException $e) { // En cas d'erreur + } catch (PDOException $e) { // En cas d'erreur die("Erreur de connexion à la base de donnée portfolio :".$e->getMessage()); return []; } @@ -32,13 +39,20 @@ function recupererXPParId($id) { global $dbh; try { - // Préparation de la requête préparée - $stmt = $dbh->prepare('SELECT * FROM experiences WHERE id = :id'); + // Même principe que listeXP() : le PDF est joint pour son nom de fichier + // uniquement, jamais son contenu binaire + $stmt = $dbh->prepare(' + SELECT e.id, e.title, e.status, e.xptype, e.tags, e.description, e.duration, e.image_url, e.markdown, e.link_url, e.created_at, + ep.pdf_filename + FROM experiences e + LEFT JOIN experience_pdfs ep ON ep.experience_id = e.id + WHERE e.id = :id + '); $stmt->bindValue(':id', $id, PDO::PARAM_INT); $stmt->execute(); return $stmt->fetch(PDO::FETCH_ASSOC); - // En cas d'erreur => renvoyer un message avec l'erreur + // En cas d'erreur => renvoyer un message avec l'erreur } catch (PDOException $e) { die("Erreur de connexion à la base de donnée portfolio :" . $e->getMessage()); // Retourner false en cas d'eerreur @@ -195,7 +209,7 @@ function afficherXPAdmin() { // Boutons qui vont permettrent la modification / Suppresion echo '
'; echo 'Modifier'; - echo 'Supprimer'; + echo 'Supprimer'; echo '
'; echo ''; diff --git a/functions/security.php b/functions/security.php new file mode 100644 index 0000000..dbea165 --- /dev/null +++ b/functions/security.php @@ -0,0 +1,48 @@ + 0, + 'path' => '/', + 'httponly' => true, + 'samesite' => 'Lax', + 'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', + ]); +} + +// Anti-clickjacking : interdit d'afficher le site dans une