Sauvegarde

This commit is contained in:
Esteban
2026-07-02 17:44:40 +02:00
parent 481fbc9498
commit 97caf2275c
10 changed files with 362 additions and 18 deletions
+24
View File
@@ -123,6 +123,30 @@ ALTER TABLE schools
ADD COLUMN markdown LONGTEXT NULL, ADD COLUMN markdown LONGTEXT NULL,
ADD COLUMN link_url VARCHAR(255) NULL; ADD COLUMN link_url VARCHAR(255) NULL;
-- ============================================================
-- Juillet 2026 (4) : bandeaux de logos sur la page d'accueil
-- ("Outils que je sais manipuler" et "Ils m'ont fait confiance"),
-- gérés depuis le panel Admin
-- ============================================================
-- Une seule table pour les deux bandeaux, distingués par la colonne category :
-- - 'tool' => bandeau "Outils que je sais manipuler"
-- - 'partner' => bandeau "Ils m'ont fait confiance"
-- link_url est facultatif : si renseigné, le logo devient cliquable (nouvel onglet)
CREATE TABLE IF NOT EXISTS logo_banners (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
category ENUM('tool', 'partner') NOT NULL,
name VARCHAR(100) NOT NULL,
logo_url VARCHAR(255) NOT NULL,
link_url VARCHAR(255) NULL,
position INT NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);
-- Table créée vide volontairement : les logos sont ajoutés depuis le panel Admin
-- (section Logos) pour éviter d'afficher des images devinées/potentiellement cassées
-- ============================================================ -- ============================================================
-- Juillet 2026 (3) : PDF en drag & drop pour les projets, -- Juillet 2026 (3) : PDF en drag & drop pour les projets,
-- expériences et écoles, lu depuis un mini lecteur intégré -- expériences et écoles, lu depuis un mini lecteur intégré
-14
View File
@@ -1,14 +0,0 @@
; Config PHP pour le conteneur Docker (image php:8.2-apache)
; Copiée dans /usr/local/etc/php/conf.d/ par le Dockerfile généré par scriptsite.sh
; Augmente les limites d'upload pour permettre le dépôt de PDF depuis le panel admin
; post_max_size doit toujours être >= upload_max_filesize
upload_max_filesize = 20M
post_max_size = 20M
; Coupe l'affichage des erreurs PHP dans le HTML en production (sécurité : ne pas exposer
; les chemins serveur aux visiteurs) et les envoie dans les logs Apache/Docker à la place.
; Bonus : évite les warnings HTML qui provoquaient "headers already sent" une fois affichés
; avant le session_start()/header() des scripts.
display_errors = Off
log_errors = On
+2 -2
View File
@@ -51,8 +51,8 @@ if (isset($_GET['success']) && $_GET['success'] === '1') {
// Éléments qui apparaissent en glissant vers le haut quand ils entrent dans l'écran // Éléments qui apparaissent en glissant vers le haut quand ils entrent dans l'écran
var cibles = document.querySelectorAll( var cibles = document.querySelectorAll(
'.project-card, .quick-link-card, .skill-category, .contact-card, ' + '.project-card, .quick-link-card, .skill-category, .contact-card, ' +
'.login-card, .project-form-card, .cgu-card, ' + '.login-card, .project-form-card, .cgu-card, .logo-admin-item, ' +
'.page-header, .quick-links-header, .skills-header, .projects-title' '.page-header, .quick-links-header, .skills-header, .projects-title, .logo-banner-header'
); );
var observer = new IntersectionObserver(function (entries) { var observer = new IntersectionObserver(function (entries) {
+111
View File
@@ -0,0 +1,111 @@
<?php
// Ajout de connexion a la bdd
require_once('../functions/databaseConnection.php');
// Fonction qui va faire une requête SQL afin de collecter les logos
// $category vaut 'tool', 'partner', ou null pour récupérer les deux catégories
// (toutes catégories confondues, utilisé par le panel admin)
// Retourne un tableau vide si la table n'existe pas encore (pas de die() pour que la page
// d'accueil continue de fonctionner tant que le script SQL n'a pas été exécuté)
function listeLogos($category = null) {
global $dbh;
try {
if ($category !== null) {
$stmt = $dbh->prepare('SELECT id, category, name, logo_url, link_url, position FROM logo_banners WHERE category = :category ORDER BY position ASC, id ASC');
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
} else {
$stmt = $dbh->prepare('SELECT id, category, name, logo_url, link_url, position FROM logo_banners ORDER BY category ASC, position ASC, id ASC');
}
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return [];
}
}
// Fonction qui permet de récupérer un logo par id (pour la modification dans le panel Admin)
function recupererLogoParId($id) {
global $dbh;
try {
$stmt = $dbh->prepare('SELECT id, category, name, logo_url, link_url, position FROM logo_banners WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
return false;
}
}
// Fonction qui affiche le bandeau défilant d'une catégorie de logos sur la page publique
// N'affiche rien si la catégorie est vide (la section entière est masquée côté page d'accueil)
function afficherLogos($category) {
$logos = listeLogos($category);
if (empty($logos)) {
return;
}
$sens = $category === 'partner' ? ' logo-marquee-reverse' : '';
echo '<div class="logo-marquee' . $sens . '">';
echo '<div class="logo-track">';
// On répète deux fois la liste : le défilement CSS boucle sur -50%, ce qui donne
// un effet de scroll infini fluide et sans coupure (voir styles/primary_css.css)
for ($repetition = 0; $repetition < 2; $repetition++) {
foreach ($logos as $logo) {
$nom = htmlspecialchars($logo['name']);
$image = htmlspecialchars($logo['logo_url']);
$lien = trim($logo['link_url'] ?? '');
if ($lien !== '') {
echo '<a class="logo-tile" href="' . htmlspecialchars($lien) . '" target="_blank" rel="noopener noreferrer" title="' . $nom . '">';
} else {
echo '<div class="logo-tile" title="' . $nom . '">';
}
echo '<img src="' . $image . '" alt="' . $nom . '" loading="lazy">';
echo $lien !== '' ? '</a>' : '</div>';
}
}
echo '</div>';
echo '</div>';
}
// Fonction qui affiche la liste des logos (toutes catégories) avec boutons de
// modification / suppression, pour le panel admin
function afficherLogosAdmin() {
$logos = listeLogos();
if (empty($logos)) {
echo '<p class="admin-notice">Aucun logo pour le moment.</p>';
return;
}
echo '<div class="logo-admin-grid">';
foreach ($logos as $logo) {
$id = (int) $logo['id'];
$nom = htmlspecialchars($logo['name']);
$image = htmlspecialchars($logo['logo_url']);
$badge = $logo['category'] === 'partner' ? 'Entreprise' : 'Outil';
echo '<div class="logo-admin-item">';
echo '<div class="logo-admin-preview"><img src="' . $image . '" alt="' . $nom . '"></div>';
echo '<div class="logo-admin-info">';
echo '<span class="tag">' . $badge . '</span>';
echo '<p>' . $nom . '</p>';
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 '</div>';
echo '</div>';
}
echo '</div>';
}
+50 -1
View File
@@ -13,6 +13,7 @@ require_once('../functions/listXP.php');
require_once('../functions/listSchool.php'); require_once('../functions/listSchool.php');
require_once('../functions/listSkills.php'); require_once('../functions/listSkills.php');
require_once('../functions/listSettings.php'); require_once('../functions/listSettings.php');
require_once('../functions/listLogos.php');
// L'utilisateur doit être administrateur // L'utilisateur doit être administrateur
@@ -26,6 +27,7 @@ $projetAModifier = null;
$xpAModifier = null; $xpAModifier = null;
$ecoleAModifier = null; $ecoleAModifier = null;
$skillAModifier = null; $skillAModifier = null;
$logoAModifier = null;
// Si on modifie XP, Projets, École ou Compétence // Si on modifie XP, Projets, École ou Compétence
@@ -41,6 +43,9 @@ if (isset($_GET['edit_school']) && is_numeric($_GET['edit_school'])) {
if (isset($_GET['edit_skill']) && is_numeric($_GET['edit_skill'])) { if (isset($_GET['edit_skill']) && is_numeric($_GET['edit_skill'])) {
$skillAModifier = recupererSkillParId((int) $_GET['edit_skill']); $skillAModifier = recupererSkillParId((int) $_GET['edit_skill']);
} }
if (isset($_GET['edit_logo']) && is_numeric($_GET['edit_logo'])) {
$logoAModifier = recupererLogoParId((int) $_GET['edit_logo']);
}
// Valeurs actuelles de la notification de la page d'accueil // Valeurs actuelles de la notification de la page d'accueil
$noticeTitle = recupererSetting('site_notice_title') ?? ''; $noticeTitle = recupererSetting('site_notice_title') ?? '';
@@ -55,7 +60,7 @@ $noticeEnabled = recupererSetting('site_notice_enabled') ?? '1';
<!-- Titre du panneau --> <!-- Titre du panneau -->
<div class="page-header"> <div class="page-header">
<h1>Panneau d'administration</h1> <h1>Panneau d'administration</h1>
<p>Gère le contenu du site : projets, expériences, écoles, compétences, notification et utilisateurs.</p> <p>Gère le contenu du site : projets, expériences, écoles, compétences, logos, notification et utilisateurs.</p>
</div> </div>
<!-- Sous-navigation : accès rapide à chaque catégorie --> <!-- Sous-navigation : accès rapide à chaque catégorie -->
@@ -64,6 +69,7 @@ $noticeEnabled = recupererSetting('site_notice_enabled') ?? '1';
<a href="#admin-experiences"><i class="fa-solid fa-briefcase"></i> Expériences</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-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-competences"><i class="fa-solid fa-code"></i> Compétences</a>
<a href="#admin-logos"><i class="fa-solid fa-images"></i> Logos</a>
<a href="#admin-notification"><i class="fa-solid fa-bell"></i> Notification</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-utilisateurs"><i class="fa-solid fa-users"></i> Utilisateurs</a>
<a href="#admin-donnees"><i class="fa-solid fa-database"></i> Données</a> <a href="#admin-donnees"><i class="fa-solid fa-database"></i> Données</a>
@@ -311,6 +317,49 @@ $noticeEnabled = recupererSetting('site_notice_enabled') ?? '1';
</div> </div>
</section> </section>
<!-- ========== SECTION LOGOS (bandeaux page d'accueil) ========== -->
<section id="admin-logos" class="admin-section">
<h2 class="admin-section-title"><i class="fa-solid fa-images"></i> Logos (page d'accueil)</h2>
<details class="admin-block" <?php echo $logoAModifier ? 'open' : ''; ?>>
<summary><?php echo $logoAModifier ? 'Modifier le logo : ' . htmlspecialchars($logoAModifier['name']) : '+ Nouveau logo'; ?></summary>
<div class="project-form-card">
<!-- on envoie les données à addLogo.php -->
<form method="POST" action="<?php echo $logoAModifier ? '../scripts/updateLogo.php' : '../scripts/addLogo.php'; ?>">
<?php if ($logoAModifier): ?>
<input type="hidden" name="id" value="<?php echo (int) $logoAModifier['id']; ?>">
<?php endif; ?>
<label>Bandeau *</label>
<select name="category">
<option value="tool" <?php echo (!$logoAModifier || $logoAModifier['category'] === 'tool') ? 'selected' : ''; ?>>Outils que je sais manipuler</option>
<option value="partner" <?php echo ($logoAModifier && $logoAModifier['category'] === 'partner') ? 'selected' : ''; ?>>Ils m'ont fait confiance</option>
</select>
<label>Nom (outil ou entreprise) *</label>
<input type="text" name="name" required placeholder="Docker, Proxmox, Nom de l'entreprise..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['name']) : ''; ?>">
<label>Lien du logo *</label>
<input type="url" name="logourl" placeholder="https://..." required value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['logo_url']) : ''; ?>">
<label>Lien externe (optionnel, rend le logo cliquable)</label>
<input type="url" name="linkurl" placeholder="https://..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['link_url'] ?? '') : ''; ?>">
<label>Position (ordre d'affichage, du plus petit au plus grand)</label>
<input type="text" name="position" value="<?php echo $logoAModifier ? (int) $logoAModifier['position'] : '0'; ?>">
<input type="submit" name="submitlogo" value="<?php echo $logoAModifier ? 'Modifier le logo' : '+ Ajouter le logo'; ?>" class="btn-submit">
</form>
</div>
</details>
<!-- Liste des logos avec modification / suppression -->
<?php afficherLogosAdmin(); ?>
</section>
<!-- ========== SECTION NOTIFICATION ========== --> <!-- ========== SECTION NOTIFICATION ========== -->
<section id="admin-notification" class="admin-section"> <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> <h2 class="admin-section-title"><i class="fa-solid fa-bell"></i> Notification de la page d'accueil</h2>
+1 -1
View File
@@ -13,7 +13,7 @@ require_once("../elements/header.php");
<div class="contact-card"> <div class="contact-card">
<div class="contact-info"> <div class="contact-info">
<p class="contact-title">Contact</p> <p class="contact-title">Contact</p>
<p class="contact-name">Moussu Esteban</p> <p class="contact-name">Esteban MOUSSU</p>
<p class="contact-line"><i class="fa-solid fa-envelope"></i> esteban.moussu@ik.me</p> <p class="contact-line"><i class="fa-solid fa-envelope"></i> esteban.moussu@ik.me</p>
<p class="contact-line"><i class="fa-solid fa-location-dot"></i> 20 Grande rue</p> <p class="contact-line"><i class="fa-solid fa-location-dot"></i> 20 Grande rue</p>
<p class="contact-line">52500 Broncourt</p> <p class="contact-line">52500 Broncourt</p>
+24
View File
@@ -12,6 +12,10 @@ require_once('./../elements/header.php');
// La notification est maintenant dynamique : modifiable depuis le panel Admin (table settings) // La notification est maintenant dynamique : modifiable depuis le panel Admin (table settings)
require_once('../functions/listSettings.php'); require_once('../functions/listSettings.php');
// Bandeaux de logos ("Outils que je sais manipuler" / "Ils m'ont fait confiance"),
// gérés depuis le panel Admin (table logo_banners)
require_once('../functions/listLogos.php');
// Affichage de la notification (si elle est activée dans le panel Admin) // Affichage de la notification (si elle est activée dans le panel Admin)
afficherNotice(); afficherNotice();
?> ?>
@@ -35,6 +39,16 @@ afficherNotice();
</a> </a>
</section> </section>
<?php if (!empty(listeLogos('tool'))): ?>
<!-- Bandeau défilant des outils maîtrisés, géré depuis le panel Admin -->
<section class="logo-banner-section">
<div class="logo-banner-header">
<h2>Outils que je sais manipuler</h2>
</div>
<?php afficherLogos('tool'); ?>
</section>
<?php endif; ?>
<!-- Section d'exploration rapide --> <!-- Section d'exploration rapide -->
<section class="quick-links-section" id="explorer"> <section class="quick-links-section" id="explorer">
<div class="quick-links-header"> <div class="quick-links-header">
@@ -79,6 +93,16 @@ afficherNotice();
</div> </div>
</section> </section>
<?php if (!empty(listeLogos('partner'))): ?>
<!-- Bandeau défilant des entreprises partenaires, géré depuis le panel Admin -->
<section class="logo-banner-section">
<div class="logo-banner-header">
<h2>Ils m'ont fait confiance</h2>
</div>
<?php afficherLogos('partner'); ?>
</section>
<?php endif; ?>
<?php <?php
// Ajout du footer // Ajout du footer
require_once('../elements/footer.php'); require_once('../elements/footer.php');
+59
View File
@@ -0,0 +1,59 @@
<?php
session_start();
require_once '../functions/databaseConnection.php';
require_once('../functions/sendLogs.php');
// Pour faire cette fonction l'utilisateur doit être administrateur
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Location: ../pages/home.php');
exit;
}
// La requête doit être un POST sinon on l'a refuse
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Récupération des données dans la requete POST
// La catégorie est restreinte à 'tool' ou 'partner' (sinon on retombe sur 'tool')
$category = ($_POST['category'] ?? '') === 'partner' ? 'partner' : 'tool';
$name = trim($_POST['name'] ?? '');
$logo_url = trim($_POST['logourl'] ?? '');
$link_url = trim($_POST['linkurl'] ?? '');
$position = (int) ($_POST['position'] ?? 0);
// On vérifie que les champs obligatoires ne sont pas vide
if (!empty($name) && !empty($logo_url)) {
try {
// On prépare la requête SQL pour insérer le nouveau logo
$stmt = $dbh->prepare('INSERT INTO logo_banners (category, name, logo_url, link_url, position) VALUES (:category, :name, :logo_url, :link_url, :position)');
// On execute la requete préparée
$stmt->execute([
':category' => $category,
':name' => $name,
':logo_url' => $logo_url,
':link_url' => $link_url,
':position' => $position
]);
// Log Discord
sendLog("Ajout d'un logo (" . $category . ") : " . $name);
// Redirection vers home.php dès que la requete est executée
header('Location: ../pages/home.php');
exit;
} catch (PDOException $e) {
// En cas d'erreur :
echo "Erreur pendant l'ajout du logo: " . $e->getMessage();
exit;
}
} else {
echo "Veuillez remplir tous les champs obligatoires.";
exit;
}
} else {
// Si on y accède de façon peu légitime sans requête POST on refuse
header('Location: ../pages/home.php');
exit;
}
?>
+37
View File
@@ -0,0 +1,37 @@
<?php
// On démarre la session
session_start();
// On ajoute la DB
require_once('../functions/databaseConnection.php');
// On ajoute les logs Discord
require_once('../functions/sendLogs.php');
// Si l'utilisateur n'est pas admin il ne peut pas executer le reste du script
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Location: ../pages/home.php');
exit;
}
//L'id doit être collecté sinon l'utilisateur ne peux pas executer le reste du script
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header('Location: ../pages/admin.php');
exit;
}
// On collecte l'id et on le met en variable locale
$id = (int) $_GET['id'];
try { // On essaie d'executer une requête qui est préparée
$stmt = $dbh->prepare('DELETE FROM logo_banners WHERE id = :id');
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute(); // Sinon on renvoie l'erreur
} catch (PDOException $e) {
die("Erreur lors de la suppression du logo :" . $e->getMessage());
}
// Log Discord
sendLog("Suppression du logo id " . $id);
// On renvoie l'utilisateur sur admin.php, directement sur la section Logos
header('Location: ../pages/admin.php#admin-logos');
exit;
+54
View File
@@ -0,0 +1,54 @@
<?php
// On démarre la session
session_start();
// On ajoute la DB
require_once('../functions/databaseConnection.php');
// On ajoute les logs Discord
require_once('../functions/sendLogs.php');
// Si l'utilisateur n'est pas admin il ne peut pas executer le reste du script
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
header('Location: ../pages/home.php');
exit;
}
if ( // Si les champs sont vides on renvoie vers admin.php
!isset($_POST['id']) || !is_numeric($_POST['id']) ||
empty($_POST['name']) ||
empty($_POST['logourl'])
) {
header('Location: ../pages/admin.php');
exit;
}
// On récupère les données pour les mettres en variables locales
$id = (int) $_POST['id'];
$category = ($_POST['category'] ?? '') === 'partner' ? 'partner' : 'tool';
$name = trim($_POST['name']);
$logourl = trim($_POST['logourl']);
$linkurl = trim($_POST['linkurl'] ?? '');
$position = (int) ($_POST['position'] ?? 0);
try { // On essaie d'executer une requête qui est préparée
$stmt = $dbh->prepare('
UPDATE logo_banners
SET category = :category, name = :name, logo_url = :logo_url, link_url = :link_url, position = :position
WHERE id = :id
');
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
$stmt->bindValue(':logo_url', $logourl, PDO::PARAM_STR);
$stmt->bindValue(':link_url', $linkurl, PDO::PARAM_STR);
$stmt->bindValue(':position', $position, PDO::PARAM_INT);
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
$stmt->execute(); // Sinon on renvoie l'erreur
} catch (PDOException $e) {
die("Erreur lors de la modification du logo :" . $e->getMessage());
}
// Log Discord
sendLog("Modification du logo : " . $name . " (id " . $id . ")");
// On renvoie l'utilisateur sur admin.php, directement sur la section Logos
header('Location: ../pages/admin.php#admin-logos');
exit;