Sauvegarde
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "portfolio-php",
|
||||||
|
"runtimeExecutable": "php",
|
||||||
|
"runtimeArgs": ["-d", "post_max_size=20M", "-d", "upload_max_filesize=20M", "-S", "localhost:8090"],
|
||||||
|
"port": 8090
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"mcp__Claude_Preview__preview_screenshot",
|
||||||
|
"mcp__Claude_Preview__preview_start",
|
||||||
|
"mcp__Claude_Preview__preview_stop",
|
||||||
|
"Bash(php -l *)",
|
||||||
|
"mcp__Claude_Preview__preview_console_logs",
|
||||||
|
"mcp__Claude_Preview__preview_resize"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -166,3 +166,17 @@ ADD COLUMN pdf_filename VARCHAR(255) NULL;
|
|||||||
ALTER TABLE schools
|
ALTER TABLE schools
|
||||||
ADD COLUMN pdf_data LONGBLOB NULL,
|
ADD COLUMN pdf_data LONGBLOB NULL,
|
||||||
ADD COLUMN pdf_filename VARCHAR(255) NULL;
|
ADD COLUMN pdf_filename VARCHAR(255) NULL;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- Juillet 2026 (5) : icône Devicon en alternative à l'image pour
|
||||||
|
-- le bandeau de logos (surtout utile pour "Outils que je sais
|
||||||
|
-- manipuler" : Devicon est un équivalent gratuit de Font Awesome
|
||||||
|
-- dédié aux logos d'outils/langages/frameworks, cf. devicon.dev)
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- icon : classes CSS Devicon (ex: "devicon-docker-plain colored"), prioritaire sur logo_url
|
||||||
|
-- si les deux sont renseignés. logo_url devient facultatif : il faut au moins l'un des deux
|
||||||
|
-- (validé côté PHP), pour les outils absents de Devicon (Proxmox, GLPI, FOG...)
|
||||||
|
ALTER TABLE logo_banners
|
||||||
|
ADD COLUMN icon VARCHAR(100) NULL AFTER name,
|
||||||
|
MODIFY COLUMN logo_url VARCHAR(255) NULL;
|
||||||
@@ -52,6 +52,9 @@ $currentPage = basename($_SERVER['PHP_SELF']);
|
|||||||
<link rel="stylesheet" href="../styles/primary_css.css">
|
<link rel="stylesheet" href="../styles/primary_css.css">
|
||||||
<!-- Ajout de Font Awesome pour les icones de home.php-->
|
<!-- Ajout de Font Awesome pour les icones de home.php-->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
||||||
|
<!-- Devicon : équivalent gratuit de Font Awesome dédié aux logos d'outils/langages/frameworks,
|
||||||
|
utilisé pour le bandeau "Outils que je sais manipuler" de la page d'accueil -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/devicon@2.15.1/devicon.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|||||||
+23
-7
@@ -12,11 +12,13 @@ function listeLogos($category = null) {
|
|||||||
global $dbh;
|
global $dbh;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// SELECT * : la colonne icon est optionnelle (le bandeau continue de fonctionner
|
||||||
|
// avec les logos déjà en place même si la migration SQL n'a pas encore été exécutée)
|
||||||
if ($category !== null) {
|
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 = $dbh->prepare('SELECT * FROM logo_banners WHERE category = :category ORDER BY position ASC, id ASC');
|
||||||
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
|
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
|
||||||
} else {
|
} 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 = $dbh->prepare('SELECT * FROM logo_banners ORDER BY category ASC, position ASC, id ASC');
|
||||||
}
|
}
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
@@ -30,7 +32,7 @@ function recupererLogoParId($id) {
|
|||||||
global $dbh;
|
global $dbh;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$stmt = $dbh->prepare('SELECT id, category, name, logo_url, link_url, position FROM logo_banners WHERE id = :id');
|
$stmt = $dbh->prepare('SELECT * FROM logo_banners WHERE id = :id');
|
||||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
@@ -39,6 +41,20 @@ function recupererLogoParId($id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fonction interne qui affiche le contenu visuel d'une tuile : une icône Devicon si elle est
|
||||||
|
// renseignée (prioritaire), sinon l'image. Ne retourne rien si aucun des deux n'est présent.
|
||||||
|
function afficherLogoVisuel($logo) {
|
||||||
|
$icone = trim($logo['icon'] ?? '');
|
||||||
|
$image = trim($logo['logo_url'] ?? '');
|
||||||
|
$nom = htmlspecialchars($logo['name']);
|
||||||
|
|
||||||
|
if ($icone !== '') {
|
||||||
|
echo '<i class="' . htmlspecialchars($icone) . '"></i>';
|
||||||
|
} elseif ($image !== '') {
|
||||||
|
echo '<img src="' . htmlspecialchars($image) . '" alt="' . $nom . '" loading="lazy">';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fonction qui affiche le bandeau défilant d'une catégorie de logos sur la page publique
|
// 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)
|
// N'affiche rien si la catégorie est vide (la section entière est masquée côté page d'accueil)
|
||||||
function afficherLogos($category) {
|
function afficherLogos($category) {
|
||||||
@@ -58,7 +74,6 @@ function afficherLogos($category) {
|
|||||||
for ($repetition = 0; $repetition < 2; $repetition++) {
|
for ($repetition = 0; $repetition < 2; $repetition++) {
|
||||||
foreach ($logos as $logo) {
|
foreach ($logos as $logo) {
|
||||||
$nom = htmlspecialchars($logo['name']);
|
$nom = htmlspecialchars($logo['name']);
|
||||||
$image = htmlspecialchars($logo['logo_url']);
|
|
||||||
$lien = trim($logo['link_url'] ?? '');
|
$lien = trim($logo['link_url'] ?? '');
|
||||||
|
|
||||||
if ($lien !== '') {
|
if ($lien !== '') {
|
||||||
@@ -67,7 +82,7 @@ function afficherLogos($category) {
|
|||||||
echo '<div class="logo-tile" title="' . $nom . '">';
|
echo '<div class="logo-tile" title="' . $nom . '">';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '<img src="' . $image . '" alt="' . $nom . '" loading="lazy">';
|
afficherLogoVisuel($logo);
|
||||||
echo $lien !== '' ? '</a>' : '</div>';
|
echo $lien !== '' ? '</a>' : '</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,11 +106,12 @@ function afficherLogosAdmin() {
|
|||||||
foreach ($logos as $logo) {
|
foreach ($logos as $logo) {
|
||||||
$id = (int) $logo['id'];
|
$id = (int) $logo['id'];
|
||||||
$nom = htmlspecialchars($logo['name']);
|
$nom = htmlspecialchars($logo['name']);
|
||||||
$image = htmlspecialchars($logo['logo_url']);
|
|
||||||
$badge = $logo['category'] === 'partner' ? 'Entreprise' : 'Outil';
|
$badge = $logo['category'] === 'partner' ? 'Entreprise' : 'Outil';
|
||||||
|
|
||||||
echo '<div class="logo-admin-item">';
|
echo '<div class="logo-admin-item">';
|
||||||
echo '<div class="logo-admin-preview"><img src="' . $image . '" alt="' . $nom . '"></div>';
|
echo '<div class="logo-admin-preview">';
|
||||||
|
afficherLogoVisuel($logo);
|
||||||
|
echo '</div>';
|
||||||
echo '<div class="logo-admin-info">';
|
echo '<div class="logo-admin-info">';
|
||||||
echo '<span class="tag">' . $badge . '</span>';
|
echo '<span class="tag">' . $badge . '</span>';
|
||||||
echo '<p>' . $nom . '</p>';
|
echo '<p>' . $nom . '</p>';
|
||||||
|
|||||||
+115
-1
@@ -32,6 +32,41 @@ function recupererSkillParId($id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Icônes Devicon dont la couleur de marque "colored" est noire ou quasi-noire (vérifié dans
|
||||||
|
// devicon.min.css : linux #000, apple #000, bash #293138, ansible #1a1918) : invisibles sur
|
||||||
|
// la tuile sombre du bandeau. On les affiche dans la couleur claire par défaut à la place.
|
||||||
|
const DEVICON_COLORED_TROP_SOMBRE = ['devicon-linux-plain', 'devicon-apple-plain', 'devicon-bash-plain', 'devicon-ansible-plain'];
|
||||||
|
|
||||||
|
// Fonction interne qui affiche le visuel (icône ou image) d'un item de compétence.
|
||||||
|
// Format accepté avant le "|" : soit des classes d'icône (Font Awesome ou Devicon), soit
|
||||||
|
// "img:URL" pour utiliser une vraie image de logo (pour les outils absents des polices
|
||||||
|
// d'icônes, ex : GLPI, FOG, Proxmox, VirtualBox, Guacamole).
|
||||||
|
// $colorise active les vraies couleurs de marque des icônes Devicon (utilisé uniquement
|
||||||
|
// pour le bandeau de la home : sur la page Compétences les icônes restent en orange
|
||||||
|
// uniforme, cohérent avec le reste de la liste)
|
||||||
|
function afficherVisuelItem($itemIcone, $classe = '', $colorise = false) {
|
||||||
|
if (strpos($itemIcone, 'img:') === 0) {
|
||||||
|
$image = htmlspecialchars(substr($itemIcone, 4));
|
||||||
|
echo '<img class="' . $classe . '" src="' . $image . '" alt="" loading="lazy">';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($colorise && strpos($itemIcone, 'devicon-') === 0 && strpos($itemIcone, 'colored') === false) {
|
||||||
|
$estTropSombre = false;
|
||||||
|
foreach (DEVICON_COLORED_TROP_SOMBRE as $slug) {
|
||||||
|
if (strpos($itemIcone, $slug) === 0) {
|
||||||
|
$estTropSombre = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$estTropSombre) {
|
||||||
|
$itemIcone .= ' colored';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<i class="' . $classe . ' ' . htmlspecialchars($itemIcone) . '"></i>';
|
||||||
|
}
|
||||||
|
|
||||||
// Fonction interne qui affiche une carte de compétences (utilisée par la page publique et le panel admin)
|
// Fonction interne qui affiche une carte de compétences (utilisée par la page publique et le panel admin)
|
||||||
// Les items sont stockés un par ligne, au format "classes-icone|Texte" (icône optionnelle)
|
// Les items sont stockés un par ligne, au format "classes-icone|Texte" (icône optionnelle)
|
||||||
function afficherSkillCard($skill, $admin = false) {
|
function afficherSkillCard($skill, $admin = false) {
|
||||||
@@ -53,7 +88,9 @@ function afficherSkillCard($skill, $admin = false) {
|
|||||||
if (strpos($item, '|') !== false) {
|
if (strpos($item, '|') !== false) {
|
||||||
list($itemIcone, $label) = array_map('trim', explode('|', $item, 2));
|
list($itemIcone, $label) = array_map('trim', explode('|', $item, 2));
|
||||||
}
|
}
|
||||||
echo '<li><i class="' . htmlspecialchars($itemIcone) . '"></i> ' . htmlspecialchars($label) . '</li>';
|
echo '<li>';
|
||||||
|
afficherVisuelItem($itemIcone, 'skill-item-visual');
|
||||||
|
echo ' ' . htmlspecialchars($label) . '</li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo '</ul>';
|
echo '</ul>';
|
||||||
@@ -96,3 +133,80 @@ function afficherSkillsAdmin() {
|
|||||||
}
|
}
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Items exclus du bandeau de la home au cas par cas, en plus du filtre automatique
|
||||||
|
// (icône Devicon "réelle" mais jugée pas assez reconnaissable seule, sans le texte à côté)
|
||||||
|
const OUTILS_EXCLUS_DU_BANDEAU = ['Bash'];
|
||||||
|
|
||||||
|
// Fonction qui récupère tous les items de compétences représentant un vrai outil identifiable
|
||||||
|
// (icône Devicon ou image "img:"), toutes catégories confondues. Les items en texte seul
|
||||||
|
// SONT EXCLUS, ainsi que les icônes Font Awesome génériques (fa-solid/fa-brands) : dans un
|
||||||
|
// bandeau qui n'affiche pas le texte à côté (juste une info-bulle au survol), une icône
|
||||||
|
// générique comme un pictogramme "carte" pour "Adressage IP" ou "carnet de contact" pour
|
||||||
|
// "LDAP" ne représente aucun outil reconnaissable — ça reste illisible pour un visiteur qui
|
||||||
|
// ne survole pas. Ces items restent affichés normalement sur la page Compétences (avec leur
|
||||||
|
// texte à côté, donc sans ambiguïté), seul le bandeau de la home les filtre.
|
||||||
|
function listeIconesCompetences() {
|
||||||
|
$categories = listeSkills();
|
||||||
|
$resultats = [];
|
||||||
|
|
||||||
|
foreach ($categories as $categorie) {
|
||||||
|
$items = array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $categorie['items'])));
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (strpos($item, '|') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($icone, $label) = array_map('trim', explode('|', $item, 2));
|
||||||
|
if ($icone === '' || $label === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seuls les vrais logos (Devicon ou image) sont assez reconnaissables sans texte
|
||||||
|
$estUnVraiLogo = strpos($icone, 'devicon-') === 0 || strpos($icone, 'img:') === 0;
|
||||||
|
if (!$estUnVraiLogo) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_array($label, OUTILS_EXCLUS_DU_BANDEAU, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultats[] = ['icon' => $icone, 'label' => $label];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resultats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction qui affiche le bandeau défilant des outils, directement à partir des icônes
|
||||||
|
// de la page Compétences (voir listeIconesCompetences). Réutilise les mêmes classes CSS
|
||||||
|
// que les bandeaux de logo_banners pour un rendu visuel identique (voir primary_css.css).
|
||||||
|
// "logo-marquee-tools" désactive le filtre grayscale/survol : contrairement aux logos
|
||||||
|
// d'entreprises, on veut que les vrais outils soient reconnaissables immédiatement.
|
||||||
|
function afficherOutilsCompetences() {
|
||||||
|
$outils = listeIconesCompetences();
|
||||||
|
|
||||||
|
if (empty($outils)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '<div class="logo-marquee logo-marquee-tools">';
|
||||||
|
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
|
||||||
|
for ($repetition = 0; $repetition < 2; $repetition++) {
|
||||||
|
foreach ($outils as $outil) {
|
||||||
|
$label = htmlspecialchars($outil['label']);
|
||||||
|
|
||||||
|
echo '<div class="logo-tile" title="' . $label . '">';
|
||||||
|
afficherVisuelItem($outil['icon'], '', true);
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo '</div>';
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
|||||||
+19
-11
@@ -319,10 +319,15 @@ $noticeEnabled = recupererSetting('site_notice_enabled') ?? '1';
|
|||||||
|
|
||||||
<!-- ========== SECTION LOGOS (bandeaux page d'accueil) ========== -->
|
<!-- ========== SECTION LOGOS (bandeaux page d'accueil) ========== -->
|
||||||
<section id="admin-logos" class="admin-section">
|
<section id="admin-logos" class="admin-section">
|
||||||
<h2 class="admin-section-title"><i class="fa-solid fa-images"></i> Logos (page d'accueil)</h2>
|
<h2 class="admin-section-title"><i class="fa-solid fa-images"></i> Logos partenaires (page d'accueil)</h2>
|
||||||
|
<p class="admin-data-text admin-section-intro">
|
||||||
|
Le bandeau "Outils que je sais manipuler" est désormais dérivé automatiquement de la
|
||||||
|
page <a href="#admin-competences">Compétences</a> (icône dans le champ "icone|texte" de chaque item).
|
||||||
|
Cette section ne gère plus que le bandeau "Ils m'ont fait confiance".
|
||||||
|
</p>
|
||||||
|
|
||||||
<details class="admin-block" <?php echo $logoAModifier ? 'open' : ''; ?>>
|
<details class="admin-block" <?php echo $logoAModifier ? 'open' : ''; ?>>
|
||||||
<summary><?php echo $logoAModifier ? 'Modifier le logo : ' . htmlspecialchars($logoAModifier['name']) : '+ Nouveau logo'; ?></summary>
|
<summary><?php echo $logoAModifier ? 'Modifier le logo : ' . htmlspecialchars($logoAModifier['name']) : '+ Nouvelle entreprise'; ?></summary>
|
||||||
|
|
||||||
<div class="project-form-card">
|
<div class="project-form-card">
|
||||||
<!-- on envoie les données à addLogo.php -->
|
<!-- on envoie les données à addLogo.php -->
|
||||||
@@ -332,17 +337,20 @@ $noticeEnabled = recupererSetting('site_notice_enabled') ?? '1';
|
|||||||
<input type="hidden" name="id" value="<?php echo (int) $logoAModifier['id']; ?>">
|
<input type="hidden" name="id" value="<?php echo (int) $logoAModifier['id']; ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<label>Bandeau *</label>
|
<!-- Seul le bandeau "Ils m'ont fait confiance" est géré ici -->
|
||||||
<select name="category">
|
<input type="hidden" name="category" value="partner">
|
||||||
<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>
|
<label>Nom de l'entreprise *</label>
|
||||||
<input type="text" name="name" required placeholder="Docker, Proxmox, Nom de l'entreprise..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['name']) : ''; ?>">
|
<input type="text" name="name" required placeholder="Nom de l'entreprise..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['name']) : ''; ?>">
|
||||||
|
|
||||||
<label>Lien du logo *</label>
|
<label>
|
||||||
<input type="url" name="logourl" placeholder="https://..." required value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['logo_url']) : ''; ?>">
|
Icône Devicon (facultatif, rarement utile pour une entreprise — parcourir sur
|
||||||
|
<a href="https://devicon.dev" target="_blank" rel="noopener noreferrer">devicon.dev</a>)
|
||||||
|
</label>
|
||||||
|
<input type="text" name="icon" placeholder="devicon-docker-plain colored" value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['icon'] ?? '') : ''; ?>">
|
||||||
|
|
||||||
|
<label>Lien de l'image (utilisé si pas d'icône)</label>
|
||||||
|
<input type="url" name="logourl" placeholder="https://..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['logo_url'] ?? '') : ''; ?>">
|
||||||
|
|
||||||
<label>Lien externe (optionnel, rend le logo cliquable)</label>
|
<label>Lien externe (optionnel, rend le logo cliquable)</label>
|
||||||
<input type="url" name="linkurl" placeholder="https://..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['link_url'] ?? '') : ''; ?>">
|
<input type="url" name="linkurl" placeholder="https://..." value="<?php echo $logoAModifier ? htmlspecialchars($logoAModifier['link_url'] ?? '') : ''; ?>">
|
||||||
|
|||||||
+8
-5
@@ -12,10 +12,13 @@ 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"),
|
// Bandeau "Ils m'ont fait confiance", géré depuis le panel Admin (table logo_banners)
|
||||||
// gérés depuis le panel Admin (table logo_banners)
|
|
||||||
require_once('../functions/listLogos.php');
|
require_once('../functions/listLogos.php');
|
||||||
|
|
||||||
|
// Bandeau "Outils que je sais manipuler" : dérivé directement des icônes de la page
|
||||||
|
// Compétences, pas de saisie séparée (voir listeIconesCompetences)
|
||||||
|
require_once('../functions/listSkills.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();
|
||||||
?>
|
?>
|
||||||
@@ -39,13 +42,13 @@ afficherNotice();
|
|||||||
</a>
|
</a>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if (!empty(listeLogos('tool'))): ?>
|
<?php if (!empty(listeIconesCompetences())): ?>
|
||||||
<!-- Bandeau défilant des outils maîtrisés, géré depuis le panel Admin -->
|
<!-- Bandeau défilant des outils, dérivé de la page Compétences -->
|
||||||
<section class="logo-banner-section">
|
<section class="logo-banner-section">
|
||||||
<div class="logo-banner-header">
|
<div class="logo-banner-header">
|
||||||
<h2>Outils que je sais manipuler</h2>
|
<h2>Outils que je sais manipuler</h2>
|
||||||
</div>
|
</div>
|
||||||
<?php afficherLogos('tool'); ?>
|
<?php afficherOutilsCompetences(); ?>
|
||||||
</section>
|
</section>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|||||||
+8
-5
@@ -16,21 +16,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
// La catégorie est restreinte à 'tool' ou 'partner' (sinon on retombe sur 'tool')
|
// La catégorie est restreinte à 'tool' ou 'partner' (sinon on retombe sur 'tool')
|
||||||
$category = ($_POST['category'] ?? '') === 'partner' ? 'partner' : 'tool';
|
$category = ($_POST['category'] ?? '') === 'partner' ? 'partner' : 'tool';
|
||||||
$name = trim($_POST['name'] ?? '');
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$icon = trim($_POST['icon'] ?? '');
|
||||||
$logo_url = trim($_POST['logourl'] ?? '');
|
$logo_url = trim($_POST['logourl'] ?? '');
|
||||||
$link_url = trim($_POST['linkurl'] ?? '');
|
$link_url = trim($_POST['linkurl'] ?? '');
|
||||||
$position = (int) ($_POST['position'] ?? 0);
|
$position = (int) ($_POST['position'] ?? 0);
|
||||||
|
|
||||||
// On vérifie que les champs obligatoires ne sont pas vide
|
// On vérifie que les champs obligatoires ne sont pas vides : il faut un nom, et
|
||||||
if (!empty($name) && !empty($logo_url)) {
|
// au moins l'un des deux visuels (icône Devicon ou image)
|
||||||
|
if (!empty($name) && (!empty($icon) || !empty($logo_url))) {
|
||||||
try {
|
try {
|
||||||
// On prépare la requête SQL pour insérer le nouveau logo
|
// 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)');
|
$stmt = $dbh->prepare('INSERT INTO logo_banners (category, name, icon, logo_url, link_url, position) VALUES (:category, :name, :icon, :logo_url, :link_url, :position)');
|
||||||
|
|
||||||
// On execute la requete préparée
|
// On execute la requete préparée
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
':category' => $category,
|
':category' => $category,
|
||||||
':name' => $name,
|
':name' => $name,
|
||||||
':logo_url' => $logo_url,
|
':icon' => $icon !== '' ? $icon : null,
|
||||||
|
':logo_url' => $logo_url !== '' ? $logo_url : null,
|
||||||
':link_url' => $link_url,
|
':link_url' => $link_url,
|
||||||
':position' => $position
|
':position' => $position
|
||||||
]);
|
]);
|
||||||
@@ -48,7 +51,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo "Veuillez remplir tous les champs obligatoires.";
|
echo "Veuillez remplir le nom et au moins une icône ou une image.";
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
|
|||||||
if ( // Si les champs sont vides on renvoie vers admin.php
|
if ( // Si les champs sont vides on renvoie vers admin.php
|
||||||
!isset($_POST['id']) || !is_numeric($_POST['id']) ||
|
!isset($_POST['id']) || !is_numeric($_POST['id']) ||
|
||||||
empty($_POST['name']) ||
|
empty($_POST['name']) ||
|
||||||
empty($_POST['logourl'])
|
(empty($_POST['icon']) && empty($_POST['logourl']))
|
||||||
) {
|
) {
|
||||||
header('Location: ../pages/admin.php');
|
header('Location: ../pages/admin.php');
|
||||||
exit;
|
exit;
|
||||||
@@ -25,19 +25,21 @@ if ( // Si les champs sont vides on renvoie vers admin.php
|
|||||||
$id = (int) $_POST['id'];
|
$id = (int) $_POST['id'];
|
||||||
$category = ($_POST['category'] ?? '') === 'partner' ? 'partner' : 'tool';
|
$category = ($_POST['category'] ?? '') === 'partner' ? 'partner' : 'tool';
|
||||||
$name = trim($_POST['name']);
|
$name = trim($_POST['name']);
|
||||||
$logourl = trim($_POST['logourl']);
|
$icon = trim($_POST['icon'] ?? '');
|
||||||
|
$logourl = trim($_POST['logourl'] ?? '');
|
||||||
$linkurl = trim($_POST['linkurl'] ?? '');
|
$linkurl = trim($_POST['linkurl'] ?? '');
|
||||||
$position = (int) ($_POST['position'] ?? 0);
|
$position = (int) ($_POST['position'] ?? 0);
|
||||||
|
|
||||||
try { // On essaie d'executer une requête qui est préparée
|
try { // On essaie d'executer une requête qui est préparée
|
||||||
$stmt = $dbh->prepare('
|
$stmt = $dbh->prepare('
|
||||||
UPDATE logo_banners
|
UPDATE logo_banners
|
||||||
SET category = :category, name = :name, logo_url = :logo_url, link_url = :link_url, position = :position
|
SET category = :category, name = :name, icon = :icon, logo_url = :logo_url, link_url = :link_url, position = :position
|
||||||
WHERE id = :id
|
WHERE id = :id
|
||||||
');
|
');
|
||||||
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
|
$stmt->bindValue(':category', $category, PDO::PARAM_STR);
|
||||||
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
|
$stmt->bindValue(':name', $name, PDO::PARAM_STR);
|
||||||
$stmt->bindValue(':logo_url', $logourl, PDO::PARAM_STR);
|
$stmt->bindValue(':icon', $icon !== '' ? $icon : null, $icon !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||||
|
$stmt->bindValue(':logo_url', $logourl !== '' ? $logourl : null, $logourl !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL);
|
||||||
$stmt->bindValue(':link_url', $linkurl, PDO::PARAM_STR);
|
$stmt->bindValue(':link_url', $linkurl, PDO::PARAM_STR);
|
||||||
$stmt->bindValue(':position', $position, PDO::PARAM_INT);
|
$stmt->bindValue(':position', $position, PDO::PARAM_INT);
|
||||||
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
$stmt->bindValue(':id', $id, PDO::PARAM_INT);
|
||||||
|
|||||||
+58
-8
@@ -1349,8 +1349,10 @@ img {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pas de fond/bordure : les logos flottent directement sur le fond de la page,
|
/* Carte derrière chaque logo, avec un fond SOMBRE FIXE (pas var(--surface), qui devient
|
||||||
comme un bandeau "trusted by" classique */
|
blanc en thème clair) : certains logos PNG ont un fond noir non transparent intégré à
|
||||||
|
l'image, qui doit rester sur un fond sombre constant pour ne pas ressortir moche
|
||||||
|
(carré noir visible) quel que soit le thème actif sur le reste du site */
|
||||||
.logo-tile {
|
.logo-tile {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1359,17 +1361,21 @@ img {
|
|||||||
width: 160px;
|
width: 160px;
|
||||||
height: 90px;
|
height: 90px;
|
||||||
padding: 18px 24px;
|
padding: 18px 24px;
|
||||||
background: transparent;
|
background: #14141c;
|
||||||
border: none;
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: var(--radius);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.logo-tile:hover {
|
a.logo-tile:hover {
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
|
border-color: var(--accent-border);
|
||||||
|
background: #1c1c26;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-tile img {
|
.logo-tile img,
|
||||||
|
.logo-tile i {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
@@ -1377,17 +1383,36 @@ a.logo-tile:hover {
|
|||||||
transition: filter 0.25s ease;
|
transition: filter 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-tile:hover img {
|
/* Taille de la glyphe pour les icônes Devicon (les images gardent leur taille naturelle) */
|
||||||
|
/* Couleur fixe (pas var(--text), qui devient sombre en thème clair et rendrait
|
||||||
|
l'icône invisible sur le fond de tuile qui lui reste volontairement sombre) */
|
||||||
|
.logo-tile i {
|
||||||
|
font-size: 44px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #e8e8f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-tile:hover img,
|
||||||
|
.logo-tile:hover i {
|
||||||
filter: grayscale(0) opacity(1);
|
filter: grayscale(0) opacity(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sur mobile le survol n'existe pas : on affiche les logos en couleur directement */
|
/* Sur mobile le survol n'existe pas : on affiche les logos en couleur directement */
|
||||||
@media (hover: none) {
|
@media (hover: none) {
|
||||||
.logo-tile img {
|
.logo-tile img,
|
||||||
|
.logo-tile i {
|
||||||
filter: grayscale(0) opacity(0.85);
|
filter: grayscale(0) opacity(0.85);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bandeau "Outils" : pas de niveaux de gris, on veut que les vrais logos des outils
|
||||||
|
soient reconnaissables immédiatement, sans avoir à survoler chaque tuile */
|
||||||
|
.logo-marquee-tools .logo-tile img,
|
||||||
|
.logo-marquee-tools .logo-tile i {
|
||||||
|
filter: none;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 620px) {
|
@media (max-width: 620px) {
|
||||||
.logo-tile {
|
.logo-tile {
|
||||||
width: 130px;
|
width: 130px;
|
||||||
@@ -1446,6 +1471,11 @@ a.logo-tile:hover {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logo-admin-preview i {
|
||||||
|
font-size: 40px;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
.logo-admin-info {
|
.logo-admin-info {
|
||||||
padding: 14px 16px 4px;
|
padding: 14px 16px 4px;
|
||||||
}
|
}
|
||||||
@@ -1564,6 +1594,14 @@ a.logo-tile:hover {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Vraie image de logo (format "img:URL") pour les outils absents des polices d'icônes */
|
||||||
|
.skill-item-visual {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
object-fit: contain;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Boutons admin dans les cartes de compétences */
|
/* Boutons admin dans les cartes de compétences */
|
||||||
.skill-category .admin-actions {
|
.skill-category .admin-actions {
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
@@ -1955,6 +1993,18 @@ a.card-link::after {
|
|||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Texte d'introduction sous un titre de section admin (voir Logos partenaires) */
|
||||||
|
.admin-section-intro {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
padding: 0 clamp(16px, 4vw, 40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-section-intro a {
|
||||||
|
color: var(--accent-light);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* Bloc repliable contenant chaque formulaire */
|
/* Bloc repliable contenant chaque formulaire */
|
||||||
.admin-block {
|
.admin-block {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
|
|||||||
Reference in New Issue
Block a user