Dofus
This commit is contained in:
@@ -11,7 +11,9 @@ function listeProjets() {
|
||||
// Essayer
|
||||
try {
|
||||
// Requête SQL qui récupère titre, tags, description, image_url
|
||||
$stmt = $dbh->prepare('SELECT id, title, status, tags, description, image_url FROM projects ORDER BY id DESC');
|
||||
// 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');
|
||||
// Execution de la requête SQL
|
||||
$stmt->execute();
|
||||
// Retourne toutes les données collectés dans un ARRAY
|
||||
@@ -27,7 +29,7 @@ function recupererProjetParId($id) {
|
||||
global $dbh;
|
||||
|
||||
try {
|
||||
$stmt = $dbh->prepare('SELECT id, title, status, tags, description, image_url FROM projects WHERE id = :id');
|
||||
$stmt = $dbh->prepare('SELECT * FROM projects WHERE id = :id');
|
||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
@@ -50,10 +52,14 @@ function afficherProjets() {
|
||||
// Pour chaque projet dans le ARRAY
|
||||
foreach ($projets as $projet) {
|
||||
// On récupère le titre, la description et l'image et le tableau avec les tags
|
||||
$id = (int) $projet['id'];
|
||||
$titre = htmlspecialchars($projet['title']);
|
||||
$description = nl2br(htmlspecialchars($projet['description']));
|
||||
$image = htmlspecialchars($projet['image_url']);
|
||||
$status = htmlspecialchars($projet['status'] ?? '');
|
||||
$markdown = trim($projet['markdown'] ?? '');
|
||||
$lien = trim($projet['link_url'] ?? '');
|
||||
$pdf = trim($projet['pdf_filename'] ?? '');
|
||||
$tags = [];
|
||||
|
||||
// S'il y a des tags alors on les sépares grâce à la virgule que l'user à ajouté dans le champs (tags1,tags2,etcc...)
|
||||
@@ -88,8 +94,21 @@ function afficherProjets() {
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
// Ajout de la description et fin des classes
|
||||
// Ajout de la description et fin des classes
|
||||
echo '<p>' . $description . '</p>';
|
||||
|
||||
// Indicateur de page détaillée : markdown > PDF > lien externe > rien
|
||||
// Le lien rend toute la carte cliquable (voir CSS .card-link)
|
||||
if ($markdown !== '') {
|
||||
echo '<a class="card-link" href="./details.php?type=projet&id=' . $id . '"><i class="fa-solid fa-file-lines"></i> Voir la page détaillée</a>';
|
||||
} elseif ($pdf !== '') {
|
||||
echo '<a class="card-link" href="./details.php?type=projet&id=' . $id . '"><i class="fa-solid fa-file-pdf"></i> Voir le PDF</a>';
|
||||
} elseif ($lien !== '') {
|
||||
echo '<a class="card-link" href="' . $lien . '" target="_blank" rel="noopener noreferrer"><i class="fa-solid fa-arrow-up-right-from-square"></i> Voir le lien externe</a>';
|
||||
} else {
|
||||
echo '<span class="card-link card-link-none"><i class="fa-solid fa-eye-slash"></i> Pas de page détaillée</span>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo '</article>';
|
||||
@@ -144,7 +163,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 . '">Modifier</a>';
|
||||
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 '</div>';
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ function listeEcoles() {
|
||||
global $dbh;
|
||||
|
||||
try {
|
||||
$stmt = $dbh->prepare('SELECT id, title, status, tags, description, duration, image_url FROM schools ORDER BY id DESC');
|
||||
// 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');
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
} catch (PDOException $e) {
|
||||
@@ -23,7 +25,7 @@ function recupererEcoleParId($id) {
|
||||
global $dbh;
|
||||
|
||||
try {
|
||||
$stmt = $dbh->prepare('SELECT id, title, status, tags, description, duration, image_url FROM schools WHERE id = :id');
|
||||
$stmt = $dbh->prepare('SELECT * FROM schools WHERE id = :id');
|
||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
@@ -40,6 +42,9 @@ function afficherEcoleCard($ecole, $admin = false) {
|
||||
$duration = htmlspecialchars($ecole['duration']);
|
||||
$image = htmlspecialchars($ecole['image_url']);
|
||||
$status = htmlspecialchars($ecole['status'] ?? '');
|
||||
$markdown = trim($ecole['markdown'] ?? '');
|
||||
$lien = trim($ecole['link_url'] ?? '');
|
||||
$pdf = trim($ecole['pdf_filename'] ?? '');
|
||||
$tags = [];
|
||||
|
||||
// S'il y a des tags alors on les sépares grâce à la virgule (tags1,tags2,etc...)
|
||||
@@ -76,10 +81,23 @@ function afficherEcoleCard($ecole, $admin = false) {
|
||||
|
||||
echo '<p>' . $description . '</p>';
|
||||
|
||||
// Indicateur de page détaillée (uniquement sur la page publique) : markdown > PDF > lien externe > rien
|
||||
if (!$admin) {
|
||||
if ($markdown !== '') {
|
||||
echo '<a class="card-link" href="./details.php?type=ecole&id=' . $id . '"><i class="fa-solid fa-file-lines"></i> Voir la page détaillée</a>';
|
||||
} elseif ($pdf !== '') {
|
||||
echo '<a class="card-link" href="./details.php?type=ecole&id=' . $id . '"><i class="fa-solid fa-file-pdf"></i> Voir le PDF</a>';
|
||||
} elseif ($lien !== '') {
|
||||
echo '<a class="card-link" href="' . $lien . '" target="_blank" rel="noopener noreferrer"><i class="fa-solid fa-arrow-up-right-from-square"></i> Voir le lien externe</a>';
|
||||
} else {
|
||||
echo '<span class="card-link card-link-none"><i class="fa-solid fa-eye-slash"></i> Pas de page détaillée</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// Boutons de modification / suppression pour le panel admin
|
||||
if ($admin) {
|
||||
echo '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_school=' . $id . '">Modifier</a>';
|
||||
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 '</div>';
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ function afficherSkillCard($skill, $admin = false) {
|
||||
// Boutons de modification / suppression pour le panel admin
|
||||
if ($admin) {
|
||||
echo '<div class="admin-actions">';
|
||||
echo '<a class="btn-submit btn-edit" href="./admin.php?edit_skill=' . $id . '">Modifier</a>';
|
||||
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 '</div>';
|
||||
}
|
||||
|
||||
+23
-4
@@ -14,7 +14,9 @@ function listeXP() {
|
||||
// Essayer
|
||||
try {
|
||||
// Requête SQL qui récupère titre, type, tags, description, durée, image_url
|
||||
$stmt = $dbh->prepare('SELECT id, title, status, xptype, tags, description, duration, image_url FROM experiences ORDER BY id DESC');
|
||||
// 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');
|
||||
// Execution de la requête SQL
|
||||
$stmt->execute();
|
||||
// Retourne toutes les données collectés dans un ARRAY
|
||||
@@ -31,7 +33,7 @@ function recupererXPParId($id) {
|
||||
|
||||
try {
|
||||
// Préparation de la requête préparée
|
||||
$stmt = $dbh->prepare('SELECT id, title, status, xptype, tags, description, duration, image_url FROM experiences WHERE id = :id');
|
||||
$stmt = $dbh->prepare('SELECT * FROM experiences WHERE id = :id');
|
||||
|
||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
@@ -58,12 +60,16 @@ function afficherXP() {
|
||||
// Pour chaque experience dans le ARRAY
|
||||
foreach ($experiences as $experience) {
|
||||
// On récupère le titre, le type, la description, la durée et l'image et le tableau avec les tags
|
||||
$id = (int) $experience['id'];
|
||||
$titre = htmlspecialchars($experience['title']);
|
||||
$xptype = htmlspecialchars($experience['xptype']);
|
||||
$description = nl2br(htmlspecialchars($experience['description']));
|
||||
$duration = htmlspecialchars($experience['duration']);
|
||||
$image = htmlspecialchars($experience['image_url']);
|
||||
$status = htmlspecialchars($experience['status'] ?? '');
|
||||
$markdown = trim($experience['markdown'] ?? '');
|
||||
$lien = trim($experience['link_url'] ?? '');
|
||||
$pdf = trim($experience['pdf_filename'] ?? '');
|
||||
$tags = [];
|
||||
|
||||
|
||||
@@ -107,8 +113,21 @@ function afficherXP() {
|
||||
}
|
||||
|
||||
|
||||
// Ajout de la description et fin des classes
|
||||
// Ajout de la description et fin des classes
|
||||
echo '<p>' . $description . '</p>';
|
||||
|
||||
// Indicateur de page détaillée : markdown > PDF > lien externe > rien
|
||||
// Le lien rend toute la carte cliquable (voir CSS .card-link)
|
||||
if ($markdown !== '') {
|
||||
echo '<a class="card-link" href="./details.php?type=experience&id=' . $id . '"><i class="fa-solid fa-file-lines"></i> Voir la page détaillée</a>';
|
||||
} elseif ($pdf !== '') {
|
||||
echo '<a class="card-link" href="./details.php?type=experience&id=' . $id . '"><i class="fa-solid fa-file-pdf"></i> Voir le PDF</a>';
|
||||
} elseif ($lien !== '') {
|
||||
echo '<a class="card-link" href="' . $lien . '" target="_blank" rel="noopener noreferrer"><i class="fa-solid fa-arrow-up-right-from-square"></i> Voir le lien externe</a>';
|
||||
} else {
|
||||
echo '<span class="card-link card-link-none"><i class="fa-solid fa-eye-slash"></i> Pas de page détaillée</span>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
|
||||
@@ -175,7 +194,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 . '">Modifier</a>';
|
||||
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 '</div>';
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
// Fonction utilitaire de validation/lecture d'un PDF envoyé par formulaire (drag & drop ou clic)
|
||||
// Utilisée par les scripts d'ajout/modification de projets, expériences et écoles
|
||||
|
||||
// Taille max acceptée par l'application : à ajuster avec upload_max_filesize / post_max_size
|
||||
// (php.ini) et max_allowed_packet (MySQL) si besoin d'accepter des PDF plus lourds
|
||||
define('PDF_MAX_SIZE', 15 * 1024 * 1024); // 15 Mo
|
||||
|
||||
// Détecte le cas où le fichier envoyé dépasse post_max_size (php.ini)
|
||||
// Dans ce cas PHP vide $_POST et $_FILES AVANT même d'exécuter le script : sans cette
|
||||
// détection, le formulaire échoue silencieusement (aucun champ, aucune erreur visible)
|
||||
function postDepasseLimite() {
|
||||
$contentLength = isset($_SERVER['CONTENT_LENGTH']) ? (int) $_SERVER['CONTENT_LENGTH'] : 0;
|
||||
return $_SERVER['REQUEST_METHOD'] === 'POST' && $contentLength > 0 && empty($_POST) && empty($_FILES);
|
||||
}
|
||||
|
||||
// Message d'erreur explicite à afficher quand postDepasseLimite() est vrai
|
||||
function messageDepassementPost() {
|
||||
return "Le fichier envoyé est trop volumineux pour le serveur (limite actuelle : "
|
||||
. ini_get('post_max_size') . "). Réduis la taille du PDF, ou augmente "
|
||||
. "post_max_size et upload_max_filesize côté serveur.";
|
||||
}
|
||||
|
||||
// Vérifie et lit un fichier PDF envoyé en upload ($_FILES)
|
||||
// Retourne :
|
||||
// - null si aucun fichier n'a été envoyé (ce n'est pas une erreur)
|
||||
// - une chaîne de texte si le fichier est invalide (message d'erreur à afficher)
|
||||
// - ['filename' => ..., 'data' => ...] si le PDF est valide
|
||||
function lireUploadPdf($champ = 'pdf') {
|
||||
if (!isset($_FILES[$champ]) || $_FILES[$champ]['error'] === UPLOAD_ERR_NO_FILE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($_FILES[$champ]['error'] !== UPLOAD_ERR_OK) {
|
||||
return "Erreur lors de l'envoi du PDF (code " . $_FILES[$champ]['error'] . ").";
|
||||
}
|
||||
|
||||
if ($_FILES[$champ]['size'] > PDF_MAX_SIZE) {
|
||||
return "Le PDF dépasse la taille maximale autorisée (15 Mo).";
|
||||
}
|
||||
|
||||
// Vérification de l'extension
|
||||
$extension = strtolower(pathinfo($_FILES[$champ]['name'], PATHINFO_EXTENSION));
|
||||
if ($extension !== 'pdf') {
|
||||
return "Le fichier doit être un PDF (extension .pdf).";
|
||||
}
|
||||
|
||||
// Vérification du type réel du fichier (pas seulement l'extension, pour éviter
|
||||
// qu'un fichier malveillant renommé en .pdf soit accepté)
|
||||
// (pas de finfo_close : déprécié depuis PHP 8.5, la ressource se libère toute seule)
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = finfo_file($finfo, $_FILES[$champ]['tmp_name']);
|
||||
|
||||
if ($mime !== 'application/pdf') {
|
||||
return "Le fichier envoyé n'est pas un PDF valide.";
|
||||
}
|
||||
|
||||
$contenu = file_get_contents($_FILES[$champ]['tmp_name']);
|
||||
if ($contenu === false) {
|
||||
return "Impossible de lire le fichier envoyé.";
|
||||
}
|
||||
|
||||
return [
|
||||
'filename' => basename($_FILES[$champ]['name']),
|
||||
'data' => $contenu
|
||||
];
|
||||
}
|
||||
+49
-4
@@ -1,21 +1,36 @@
|
||||
<?php
|
||||
// On cache les webhooks discord => Correction de faille de sécurité
|
||||
require_once('../../idDiscord.php');
|
||||
// __DIR__ = chemin absolu du dossier functions : le require fonctionne ainsi quel que soit
|
||||
// le dossier depuis lequel le script est appelé (pages/, scripts/, racine...)
|
||||
require_once(__DIR__ . '/../../idDiscord.php');
|
||||
// EXEMPLE de a quoi ressemble idDiscord :
|
||||
// define('ID_DISCORD1', 'https://discord.com/api/webhooks/URL_WEBHOOK1');
|
||||
// define('ID_DISCORD2', 'https://discord.com/api/webhooks/URL_WEBHOOK2');
|
||||
|
||||
|
||||
// Fonction qui va ENVOYER des logs sur le webhook discord
|
||||
function sendLog($action, $user="") {
|
||||
function sendLog($action, $user = "") {
|
||||
// Si le webhook n'est pas configuré on ne bloque pas le site
|
||||
if (!defined('ID_DISCORD1') || ID_DISCORD1 == '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$webhook = ID_DISCORD1; // URL du webhook Discord
|
||||
|
||||
// Si l'utilisateur n'est pas précisé on prend celui de la session (connecté)
|
||||
if ($user == "" && isset($_SESSION['username'])) {
|
||||
$user = $_SESSION['username'];
|
||||
}
|
||||
|
||||
$contenuLog = "**Nouvelle log du portfolio**\n\n";
|
||||
$contenuLog .= "**Action :** " . $action . "\n";
|
||||
if ($user != "") {
|
||||
$contenuLog .= "**Utilisateur :** " . $user . "\n";
|
||||
} else {
|
||||
$contenuLog .= "**Utilisateur :** non défini". "\n";
|
||||
$contenuLog .= "**Utilisateur :** non défini" . "\n";
|
||||
}
|
||||
$contenuLog .= "**IP :** " . ($_SERVER['REMOTE_ADDR'] ?? 'inconnue') . "\n";
|
||||
$contenuLog .= "**Date :** " . date('d/m/Y H:i:s');
|
||||
|
||||
$data = [
|
||||
"content" => $contenuLog
|
||||
@@ -28,6 +43,36 @@ function sendLog($action, $user="") {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $dataJson);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
// Timeouts courts : si Discord est lent ou down, le site ne doit pas être ralenti
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 800);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 1500);
|
||||
|
||||
// CORRECTION DU BUG : curl_exec() n'était jamais appelé, la requête n'était
|
||||
// donc jamais envoyée à Discord. C'est pour ça que les logs n'ont jamais marché.
|
||||
// (pas de curl_close : déprécié depuis PHP 8.5, la ressource se libère toute seule)
|
||||
curl_exec($ch);
|
||||
$codeHttp = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
// Discord renvoie 204 quand le message est bien envoyé
|
||||
return $codeHttp == 204;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// Log de visite de page : appelée par le header sur toutes les pages
|
||||
// Anti-spam : Discord limite à ~30 messages/minute par webhook, donc une même page
|
||||
// n'est loguée qu'une fois par minute et par visiteur (stocké dans sa session)
|
||||
function sendPageLog() {
|
||||
$page = basename($_SERVER['PHP_SELF'] ?? 'inconnue');
|
||||
|
||||
if (!isset($_SESSION['pages_log'])) {
|
||||
$_SESSION['pages_log'] = [];
|
||||
}
|
||||
|
||||
$maintenant = time();
|
||||
if (isset($_SESSION['pages_log'][$page]) && ($maintenant - $_SESSION['pages_log'][$page]) < 60) {
|
||||
return; // Page déjà loguée il y a moins d'une minute pour ce visiteur
|
||||
}
|
||||
|
||||
$_SESSION['pages_log'][$page] = $maintenant;
|
||||
sendLog("Visite de la page " . $page);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user