Fix lag
This commit is contained in:
@@ -118,7 +118,7 @@ function afficherLogosAdmin() {
|
||||
echo '</div>';
|
||||
echo '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_logo=' . $id . '#admin-logos">Modifier</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeLogo.php?id=' . $id . '" onclick="return confirm(\'Voulez-vous vraiment supprimer ce logo ?\');">Supprimer</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeLogo.php?id=' . $id . '&csrf_token=' . urlencode(csrfToken()) . '" onclick="return confirm(\'Voulez-vous vraiment supprimer ce logo ?\');">Supprimer</a>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
@@ -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 '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_project=' . $id . '#admin-projets">Modifier</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeProject.php?id=' . $id . '" onclick="return confirm(\'Voulez-vous vraiment supprimer ce projet ?\');">Supprimer</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeProject.php?id=' . $id . '&csrf_token=' . urlencode(csrfToken()) . '" onclick="return confirm(\'Voulez-vous vraiment supprimer ce projet ?\');">Supprimer</a>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
@@ -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 '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_school=' . $id . '#admin-ecoles">Modifier</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeSchool.php?id=' . $id . '" onclick="return confirm(\'Voulez-vous vraiment supprimer cette école ?\');">Supprimer</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeSchool.php?id=' . $id . '&csrf_token=' . urlencode(csrfToken()) . '" onclick="return confirm(\'Voulez-vous vraiment supprimer cette école ?\');">Supprimer</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ function afficherSkillCard($skill, $admin = false) {
|
||||
if ($admin) {
|
||||
echo '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_skill=' . $id . '#admin-competences">Modifier</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeSkill.php?id=' . $id . '" onclick="return confirm(\'Voulez-vous vraiment supprimer cette catégorie de compétences ?\');">Supprimer</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeSkill.php?id=' . $id . '&csrf_token=' . urlencode(csrfToken()) . '" onclick="return confirm(\'Voulez-vous vraiment supprimer cette catégorie de compétences ?\');">Supprimer</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
// Ajout de connexion a la bdd
|
||||
require_once('../functions/databaseConnection.php');
|
||||
|
||||
// Fonction qui récupère les chiffres clés affichés sur la home (bandeau de stats)
|
||||
// Chaque compteur retombe sur 0 en cas d'erreur (table pas encore créée...) plutôt
|
||||
// que de bloquer la page d'accueil
|
||||
function recupererStatsHome() {
|
||||
global $dbh;
|
||||
|
||||
$stats = ['projets' => 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;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ function afficherUsersAdmin()
|
||||
echo '<p><strong>Créé le :</strong> ' . htmlspecialchars($user["created_at"]) . '</p>';
|
||||
|
||||
echo '<div class="admin-actions">';
|
||||
echo '<a href="../scripts/removeUser.php?id=' . $user["id"] . '" class="btn-submit btn-delete" onclick="return confirm(\'Supprimer cet utilisateur ?\')">Supprimer</a>';
|
||||
echo '<a href="../scripts/removeUser.php?id=' . $user["id"] . '&csrf_token=' . urlencode(csrfToken()) . '" class="btn-submit btn-delete" onclick="return confirm(\'Supprimer cet utilisateur ?\')">Supprimer</a>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
+23
-9
@@ -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 '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_xp=' . $id . '#admin-experiences">Modifier</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeXP.php?id=' . $id . '" onclick="return confirm(\'Voulez-vous vraiment supprimer cette expérience ?\');">Supprimer</a>';
|
||||
echo '<a class="btn-submit btn-delete" href="../scripts/removeXP.php?id=' . $id . '&csrf_token=' . urlencode(csrfToken()) . '" onclick="return confirm(\'Voulez-vous vraiment supprimer cette expérience ?\');">Supprimer</a>';
|
||||
echo '</div>';
|
||||
|
||||
echo '</div>';
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
// Sécurité transverse : cookie de session durci, en-têtes anti-clickjacking et
|
||||
// protection CSRF. A inclure à la place d'un simple session_start() sur toute
|
||||
// page ou script qui utilise une session (remplace l'appel direct partout).
|
||||
|
||||
// Règle les paramètres du cookie de session AVANT de démarrer la session (ils
|
||||
// n'ont aucun effet une fois la session déjà démarrée) :
|
||||
// - httponly : empêche le JavaScript de lire le cookie (limite le vol de session via une XSS)
|
||||
// - samesite=Lax : bloque une partie des requêtes intersites (CSRF)
|
||||
// - secure : n'est activé que si la connexion est déjà en HTTPS (sinon le cookie ne partirait jamais en HTTP)
|
||||
function configureSecureSessionCookies() {
|
||||
session_set_cookie_params([
|
||||
'lifetime' => 0,
|
||||
'path' => '/',
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
'secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off',
|
||||
]);
|
||||
}
|
||||
|
||||
// Anti-clickjacking : interdit d'afficher le site dans une <iframe> d'un autre site
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
configureSecureSessionCookies();
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Génère (si besoin) et retourne le jeton CSRF de la session en cours, à
|
||||
// placer dans un champ caché de chaque formulaire admin ou dans les liens
|
||||
// GET qui déclenchent une action (suppression, export...)
|
||||
function csrfToken() {
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
// Vérifie le jeton CSRF reçu (POST ou GET) ; arrête le script si invalide.
|
||||
// A appeler juste après la vérification du rôle admin dans chaque script d'action.
|
||||
function requireCsrfToken() {
|
||||
$recu = $_POST['csrf_token'] ?? $_GET['csrf_token'] ?? '';
|
||||
if (empty($_SESSION['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $recu)) {
|
||||
http_response_code(403);
|
||||
die("Requête refusée : jeton de sécurité invalide ou expiré. Recharge la page et réessaie.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user