diff --git a/AGENTS.md b/AGENTS.md
index bf6f401..76a1a5e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -4,7 +4,7 @@
**Dépôt Gitea officiel :** `https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme.git`
**Chemin de déploiement VPS :** `/var/www/fastuser/data/www/cgt-fha.fr/wp-content/themes/cgt-fha/`
**Ressource Skills locale :** `../skills/` (`../skills/wp-performance/SKILL.md` et `../skills/wp-block-themes/SKILL.md`)
-**Ressource graphique locale :** `../ressources/banniere union locale noir.png` (à copier dans `assets/img/banniere-ul-noir.png` et utiliser comme logo par défaut de l'en-tête)
+**Ressources graphiques locales :** `../ressources/banniere union locale noir.png` (`assets/img/banniere-ul-noir.png` pour l'en-tête), `../ressources/banniere union locale blanc.png` (`assets/img/banniere-ul-blanc.png` pour le pied de page), logos officiels du réseau CGT (`logo-cgt-09.jpg`, `logo-cgt.png`, `logo-nvo.png`, `logo-ihs.jpg`, `logo-indecosa.png`) et `screenshot.png` (`1200x900`).
**Maquette de référence visuelle :** L'utilisateur fournit l'image de maquette validée en pièce jointe. Respecte fidèlement sa hiérarchie visuelle, ses proportions, ses espacements et son style graphique.
---
@@ -22,9 +22,11 @@ Tu es l'unique développeur de ce thème WordPress sur mesure. Ce thème remplac
- CSS moderne natif (`CSS Custom Properties`, `CSS Grid`, `Flexbox`, mobile-first). Aucune bibliothèque CSS externe (pas de Bootstrap ni Tailwind CDN)[cite: 6].
- JavaScript *Vanilla* ES6 minimaliste uniquement pour le menu mobile accessible et les onglets de l'admin (pas de jQuery)[cite: 6].
- Aucune police Google Fonts chargée via CDN tiers : utilise des polices système modernes (`system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`) ou des fichiers hébergés localement[cite: 6].
-- **100 % Modifiable sans toucher au code :**
+- **100 % Modifiable sans toucher au code & Préservation stricte des réglages :**
- En dehors des articles locaux (publiés dans WordPress) et des actualités confédérales extraites automatiquement depuis `cgt.fr`, **tous les textes, images, icônes, liens, boutons et coordonnées du thème doivent être modifiables depuis l'administration WordPress** (*Apparence > Réglages UL CGT* + *Apparence > Menus* + *Médiathèque*).
- - **Pré-remplissage obligatoire par défaut :** Toutes les options doivent posséder des valeurs par défaut complètes (définies via `wp_parse_args()`) afin que le site soit immédiatement fonctionnel et complet dès l'activation du thème, sans aucune zone vide.
+ - **Pré-remplissage par défaut & Non-écrasement strict :** Toutes les options possèdent des valeurs par défaut complètes lues dynamiquement via `wp_parse_args( get_option( 'cgt_fha_options', array() ), $defaults )`. Aucun hook (`after_switch_theme`, `upgrader_process_complete`, `init`) ne doit écraser les options enregistrées en base (`wp_options`), les menus ou les médias lors des mises à jour ; d'éventuelles évolutions de structure passent uniquement par la fonction versionnée non destructive `cgt_fha_maybe_migrate_options()`.
+- **Identité territoriale Foix & Haute-Ariège + Syndicat Multipros :**
+ - L'Union Locale CGT de Foix et de la Haute-Ariège fédère l'ensemble des syndicats CGT de son périmètre et assure le rattachement direct des salarié·e·s isolé·e·s (TPE/PME sans section syndicale) via son syndicat multiprofessionnel (*Multipros*).
- **Intégration propre avec les extensions sœurs (Gitea) :**
- Le thème gère le visuel, le SEO/OpenGraph de base, le bandeau de mobilisation et l'encart d'actualités confédérales[cite: 6].
- Il prévoit des hooks/emplacements propres (`do_action(...)` / support de shortcodes) pour accueillir les sorties des extensions `cgt-wp-calendar` (Agenda) et `cgt-wp-forms` (Formulaires Défense, Adhésion, Formation), tout en affichant un rendu cohérent même si ces extensions ne sont pas encore activées[cite: 6].
@@ -88,9 +90,9 @@ Placé **immédiatement sous le Hero et avant les actualités** (fond blanc `#ff
3. **Carte 3 :** Icône bulles d'échange / livre (SVG propre) • Titre `S'INFORMER & SE FORMER` • Texte par défaut : *« Connaître les actualités des luttes en Haute-Ariège et bénéficier de vos droits à la formation syndicale (CFESES). »*
- *Note :* Les icônes (sélecteur parmi une bibliothèque SVG intégrée), titres et textes de ces 3 cartes sont modifiables dans l'admin WordPress.
-### Section 3 : Vitrine 1 — « SUR LE TERRAIN EN HAUTE-ARIÈGE » (Actualités Locales)
+### Section 3 : Vitrine 1 — « SUR LE TERRAIN À FOIX ET EN HAUTE-ARIÈGE » (Actualités Locales)
- **Priorité éditoriale locale :** Affiche automatiquement les **3 derniers articles publiés** dans le WordPress de l'UL (`WP_Query` sur `post_type => 'post'`, `posts_per_page => 3`)[cite: 6].
-- **Titre de section :** `SUR LE TERRAIN EN HAUTE-ARIÈGE` centré en majuscules (`
`).
+- **Titre de section :** `SUR LE TERRAIN À FOIX ET EN HAUTE-ARIÈGE` centré en majuscules (``), personnalisable dans l'admin.
- **Mise en page des 3 cartes** (fidèle à la maquette) :
- Grille 3 colonnes (`gap: 1.5rem`).
- Image à la une en haut avec coins arrondis (`border-radius: 12px; height: 190px; object-fit: cover; width: 100%;`), avec fallback SVG propre si un article n'a pas d'image.
@@ -110,43 +112,43 @@ Placé **immédiatement sous le Hero et avant les actualités** (fond blanc `#ff
- **Cache & WP-Cron :** Stockage dans le transient `cgt_fha_confederal_feed` (expiration 3h) + sauvegarde de secours `cgt_fha_confederal_feed_backup` conservée en cas d'indisponibilité réseau. Événement `WP-Cron` toutes les 2 heures + bouton *"Actualiser les actus confédérales"* dans la barre d'admin WordPress (`admin_bar_menu`).
- **Rendu HTML/CSS fidèle à la maquette :**
- **Bloc supérieur « À LA UNE » :**
- - Conteneur centré (`max-width: 880px; margin: 0 auto 2.5rem auto;`), disposition horizontale Flexbox (`align-items: center`).
- - À gauche (`flex: 0 0 58%`) : Image de l'article (`border-radius: 14px; height: 290px; object-fit: cover;`) avec un badge overlay discret en bas à droite `À LA UNE`.
+ - Conteneur centré aligné sur la largeur de la grille des 3 cartes ci-dessous (`max-width: 1040px; margin: 0 auto 2.5rem auto;`), disposition horizontale Flexbox (`align-items: center`).
+ - À gauche (`flex: 0 0 58%`) : Image de l'article (`border-radius: 14px; height: 290px; object-fit: cover;`) avec un badge overlay discret en bas à droite `À LA UNE` (`right: 56px;` pour ne jamais être masqué par le chevauchement).
- À droite (`flex: 1; margin-left: -40px; position: relative; z-index: 2;`) : **Boîte blanche arrondie chevauchant le bord droit de l'image** (`background: #fff; border: 1px solid #cbd5e1; border-radius: 14px; padding: 1.75rem; box-shadow: 0 10px 25px rgba(0,0,0,0.06); text-align: center;`) contenant :
- Le titre de l'article (ou surtitre `À LA UNE` + titre).
- La description / chapô nettoyé.
- Le bouton pilule encadré (`border: 1.5px solid #1a1a1a; border-radius: 999px; padding: 6px 28px; font-weight: 700;`) : **« LIRE SUR CGT.FR ↗ »** (`target="_blank" rel="noopener noreferrer"`).
- *Responsive mobile (`< 768px`) :* `flex-direction: column`, `margin-left: 0`, `margin-top: -24px`.
- **Grille inférieure (3 colonnes : ARTICLE 1, ARTICLE 2, ARTICLE 3) :**
- - Grille CSS `grid-template-columns: repeat(3, 1fr); gap: 1.5rem;` (1 colonne sous `768px`).
+ - Grille CSS `grid-template-columns: repeat(3, 1fr); gap: 1.5rem; max-width: 1040px; margin: 0 auto;` (1 colonne sous `768px`).
- Chaque carte affiche l'image en haut (`height: 190px; width: 100%; object-fit: cover; border-radius: 14px;`), puis une **boîte blanche arrondie qui remonte par-dessus le bas de l'image** (`margin-top: -24px; position: relative; z-index: 2; background: #fff; border: 1px solid #cbd5e1; border-radius: 14px; padding: 1.25rem; box-shadow: 0 6px 18px rgba(0,0,0,0.05); display: flex; flex-direction: column; text-align: center; flex: 1;`) contenant :
- Le titre de l'article en gras.
- La description courte (~120 caractères).
- - En bas (`margin-top: auto; padding-top: 1rem;`), le bouton pilule encadré **« LIEN ↗ »** (`target="_blank" rel="noopener noreferrer"`).
+ - En bas (`margin-top: auto; padding-top: 1rem;`), le bouton pilule encadré **« Lire la suite → »** (`target="_blank" rel="noopener noreferrer"`).
### Section 5 : Parcours « SALARIÉS ISOLÉS & TPE : VOUS N'ÊTES PAS SEULS »
- **Fond :** Blanc (`#ffffff`).
- **Contenu (entièrement éditable dans l'admin) :**
- Bloc pédagogique destiné aux salariés des petites entreprises (< 11 salariés), commerces, artisanat ou chantiers sans CSE[cite: 6].
- - Met en avant la **confidentialité absolue** de toute démarche vis-à-vis de l'employeur, le rôle du syndicat **Multipros**, et l'accompagnement par les **Conseillers du Salarié** lors des entretiens préalables[cite: 6].
+ - Met en avant la **confidentialité absolue** de toute démarche vis-à-vis de l'employeur, le rattachement direct au syndicat **Multipros** de l'Union Locale, et l'accompagnement par les **Conseillers du Salarié** lors des entretiens préalables[cite: 6].
- Bouton d'action rouge CGT : *« Contacter l'UL en toute confidentialité »*[cite: 6].
### Section 6 : Vie Militante, Formation Syndicale (CFESES) & Appli Mobile
- **Fond :** Gris doux (`#f1f3f5`).
-- **Grille 2 colonnes (`grid-template-columns: 1fr 1fr; gap: 2rem;`) :**
+- **Grille adaptative (2 colonnes si l'Appli Mobile est activée, 1 colonne centrée sinon) :**
1. **Colonne gauche — Formation Syndicale (CFESES) :** Rappelle le droit légal de chaque salarié au **Congé de Formation Économique, Sociale, Environnementale et Syndicale** (maintien de salaire, demande à remettre à l'employeur 30 jours avant) et l'aide à la vie syndicale[cite: 6, 8].
- 2. **Colonne droite — L'Application Mobile UL CGT Foix Haute-Ariège :** Présente l'application Android/KMP (actualités locales, alertes mobilisation Push, carte de syndiqué virtuelle & 13 timbres 100 % hors-ligne) avec emplacement pour le lien de téléchargement direct (`.apk` / Play Store) et l'image QR Code (configurables dans l'admin)[cite: 8].
+ 2. **Colonne droite — L'Application Mobile UL CGT Foix Haute-Ariège (désactivable et désactivée par défaut) :** Présente l'application Android/KMP (actualités locales, alertes mobilisation Push, carte de syndiqué virtuelle & 13 timbres 100 % hors-ligne) avec emplacement pour le lien de téléchargement direct (`.apk` / Play Store) et l'image QR Code (configurables et activables dans l'admin)[cite: 8].
### Section 7 : Réseau Syndical CGT
- **Fond :** Blanc (`#ffffff`).
-- Bandeau de liens structurés vers l'écosystème confédéral et départemental : **UD CGT 09**, **Confédération CGT (`cgt.fr`)**, **La NVO (`nvo.fr`)**, **IHS CGT**, **INDECOSA-CGT**[cite: 6].
+- Grille de **5 cartes verticales** (logo officiel circulaire/encadré en haut, titre, description courte, bouton *« Visiter le site ↗ »*) vers l'écosystème confédéral et départemental : **UD CGT 09**, **Confédération CGT (`cgt.fr`)**, **La NVO (`nvo.fr`)**, **IHS CGT**, **INDECOSA-CGT**[cite: 6].
---
## 5. PIED DE PAGE (`footer.php`) — SOUVERAIN & PRATIQUE
- **Aucune mention** *"Propulsé par WordPress"* ou *"Thème..."*[cite: 6].
- **Fond sombre (`#1c1e21`), texte blanc/gris clair, grille 3 colonnes :**
- 1. **Union Locale CGT Foix Haute-Ariège :** Logo (version claire/monochrome), courte présentation, et appel militant : *« Une lutte dans votre entreprise, une info à partager ? Contactez l'UL »*.
+ 1. **Union Locale CGT Foix Haute-Ariège :** Logo version blanche (`assets/img/banniere-ul-blanc.png`, modifiable dans l'admin), courte présentation (union de tous les syndicats du périmètre & syndicat Multipros pour les isolé·e·s), et appel militant : *« Une lutte dans votre entreprise, une info à partager ? Contactez l'UL »*.
2. **Nos Locaux & Permanences (visible sur 100 % des pages) :**[cite: 6]
- Adresse : `Espace Sabart, 10 Av. de l'Ayroule, 09400 Tarascon-sur-Ariège` (modifiable dans l'admin)[cite: 7].
- Téléphone cliquable : `07 60 49 64 69`[cite: 7]
@@ -179,10 +181,15 @@ Crée une page de réglages complète et intuitive dans **Apparence > Réglages
---
-## 8. GESTION DE VERSIONS & PUBLICATION GITEA
-- **Dépôt distant :** `https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme.git` (branche principale : `main`).
-- **Fichier `.gitignore` :** Crée un fichier `.gitignore` propre à la racine pour exclure les fichiers système (`.DS_Store`, `Thumbs.db`), les éditeurs (`.vscode/`, `.idea/`), et d'éventuels dossiers temporaires, tout en conservant le code source du thème et le fichier `AGENTS.md`.
-- **Compatibilité déploiement VPS :** Assure-toi que tous les chemins dans le thème utilisent exclusivement les fonctions natives WordPress (`get_template_directory()`, `get_template_directory_uri()`) afin que le déploiement automatisé vers `/var/www/fastuser/data/www/cgt-fha.fr/wp-content/themes/cgt-fha/` fonctionne sans aucune dépendance de chemin en dur[cite: 6].
+## 8. GESTION DE VERSIONS, MISE À JOUR GITEA & PUBLICATION
+- **Dépôt distant public :** `https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme.git` (branche principale : `main`).
+- **Mise à jour en un clic depuis WordPress (`inc/class-cgt-gitea-updater.php`) :**
+ - Vérification directe sans jeton d'authentification sur `https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme/raw/branch/main/style.css` (cache Transient 6 h).
+ - Téléchargement via `https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme/archive/main.zip` et renommage automatique du dossier extrait vers `get_stylesheet()` (`upgrader_source_selection`, compatible mises à jour unitaires et groupées).
+ - Préservation stricte de `wp_options` (`cgt_fha_options`), des menus et des médias lors de chaque mise à jour.
+- **Documentation & Aperçu :** `README.md` (documentation d'utilisation et de mise à jour en français) et `screenshot.png` (`1200x900px` pour *Apparence > Thèmes*).
+- **Fichier `.gitignore` :** Exclut les fichiers système (`.DS_Store`, `Thumbs.db`), les éditeurs (`.vscode/`, `.idea/`), et d'éventuels dossiers temporaires, tout en conservant le code source du thème, `README.md` et `AGENTS.md`.
+- **Compatibilité déploiement VPS :** Tous les chemins dans le thème utilisent exclusivement les fonctions natives WordPress (`get_template_directory()`, `get_template_directory_uri()`, `get_stylesheet()`) afin que le déploiement automatisé vers `/var/www/fastuser/data/www/cgt-fha.fr/wp-content/themes/cgt-fha/` fonctionne sans aucune dépendance de chemin en dur[cite: 6].
---
@@ -190,6 +197,8 @@ Crée une page de réglages complète et intuitive dans **Apparence > Réglages
```text
/
├── AGENTS.md # Ce cahier des charges
+├── README.md # Documentation du thème & procédure de mise à jour Gitea
+├── screenshot.png # Aperçu visuel 1200x900px dans Apparence > Thèmes
├── .gitignore # Exclusions propres pour Gitea
├── style.css # En-tête thème WP + Variables CSS + Design System complet
├── theme.json # Palette couleurs CGT (#d20012) & réglages éditeur
@@ -202,10 +211,13 @@ Crée une page de réglages complète et intuitive dans **Apparence > Réglages
├── page.php # Gabarit de page standard (Contact, Calendrier, Syndiquez-vous...)
├── archive.php # Archives par catégorie/étiquette locale
├── 404.php # Page d'erreur 404 utile avec recherche et liens vers l'UL
+├── index.php # Gabarit de secours WordPress requis
├── inc/
│ ├── class-cgt-confederal-feed.php # Scraper cgt.fr + cache Transients + WP-Cron + rendu wireframe
-│ ├── theme-options.php # Page d'admin WP : Réglages UL CGT (onglets + médias WP)
+│ ├── class-cgt-gitea-updater.php # Mise à jour WordPress en un clic depuis le dépôt public Gitea
+│ ├── theme-options.php # Page d'admin WP : Réglages UL CGT + migration non destructive
│ └── seo-performance.php # Titres SEO locaux, OpenGraph, alerte indexation, nettoyage WP
└── assets/
├── js/main.js # Menu mobile accessible & onglets admin (Vanilla JS)
- └── img/ # Visuels par défaut (hero-ariege.jpg/svg, logo-ul.svg, placeholder.svg)
\ No newline at end of file
+ └── img/ # Bannières UL (noir/blanc), hero-ariege.jpg, logos réseau CGT, SVG
+```
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..083a0b7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# Thème WordPress `cgt-wp-theme` — Union Locale CGT Foix Haute-Ariège
+
+Thème WordPress sur mesure, rapide et souverain conçu pour le site officiel de l'**Union Locale CGT Foix Haute-Ariège** (`https://cgt-fha.fr/`).
+
+L'Union Locale CGT de Foix et de la Haute-Ariège rassemble tous les syndicats CGT de son périmètre et assure le rattachement direct des salarié·e·s isolé·e·s (TPE, PME, commerces, artisanat, associations sans section syndicale) grâce à son **syndicat multiprofessionnel (Multipros)**.
+
+---
+
+## Fonctionnalités principales
+
+- **Bandeau d'alerte mobilisation auto-expirant :** bandeau rouge CGT pleine largeur paramétrable (texte, lien, date et heure d'expiration automatique).
+- **Vitrine locale prioritaire :** mise en avant des 3 derniers articles locaux publiés dans WordPress (*Sur le terrain à Foix et en Haute-Ariège*), avec hooks prêts pour l'agenda (`cgt-wp-calendar`) et les formulaires militants (`cgt-wp-forms`).
+- **Actualité confédérale `cgt.fr` automatisée :** récupération directe depuis `https://www.cgt.fr/` et `/actualites` (1 article *À la une* + 3 articles récents) avec cache Transient (3 h), sauvegarde hors-ligne, tâche `WP-Cron` (2 h) et bouton de rafraîchissement manuel — sans polluer `wp_posts`.
+- **Parcours militants & Réseau syndical :** section dédiée aux salarié·e·s isolé·e·s & TPE (*Multipros*), rappel du droit à la formation syndicale (*CFESES*), bloc Application Mobile activable à la demande et cartes illustrées du réseau CGT (UD CGT 09, Confédération CGT, La NVO, IHS CGT, INDECOSA-CGT).
+- **SEO local, OpenGraph & Performance :** balises ``, `` et OpenGraph natifs (`fr_FR`), alerte si l'indexation WordPress est bloquée, chargement léger sans jQuery, sans Bootstrap/Tailwind CDN ni Google Fonts externes (conforme RGPD).
+- **Administration 100 % paramétrable & préservation des réglages :** tous les textes, visuels, icônes, coordonnées, menus et liens sont modifiables depuis **Apparence > Réglages UL CGT**. Lors des mises à jour, **100 % des personnalisations enregistrées en base (`wp_options`), les menus et les médias sont strictement préservés**.
+
+---
+
+## Installation
+
+1. Téléchargez l'archive du thème depuis le dépôt Gitea (`https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme/archive/main.zip`) ou clonez le dépôt dans `wp-content/themes/` :
+ ```bash
+ git clone https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme.git cgt-fha
+ ```
+2. Dans l'administration WordPress, rendez-vous dans **Apparence > Thèmes** et activez **Union Locale CGT Foix Haute-Ariège**.
+3. Personnalisez vos textes, logos, coordonnées et le bandeau mobilisation dans **Apparence > Réglages UL CGT**, puis configurez vos menus dans **Apparence > Menus**.
+
+---
+
+## Mise à jour en un clic depuis WordPress (Gitea public)
+
+Le module `inc/class-cgt-gitea-updater.php` connecte nativement WordPress au dépôt public Gitea sans nécessiter aucun jeton d'authentification :
+
+1. **Détection automatique :** WordPress interroge périodiquement (ou lors d'un clic sur *Vérifier à nouveau* dans **Tableau de bord > Mises à jour**) l'en-tête `Version:` de `https://git.cgt-fha.fr/Union_Locale_CGT_Foix_Haute-Ariege/cgt-wp-theme/raw/branch/main/style.css`.
+2. **Téléchargement & renommage transparent :** lorsque vous lancez la mise à jour en un clic depuis WordPress, l'archive `/archive/main.zip` est téléchargée puis automatiquement renommée vers le dossier actif du thème (`get_stylesheet()`, par exemple `cgt-fha`).
+3. **Préservation totale des données :** aucune mise à jour ne réinitialise `cgt_fha_options`, les menus WordPress ou les médias téléversés. Les réglages restent lus dynamiquement via `wp_parse_args( get_option( 'cgt_fha_options' ), $defaults )`.
+
+### Publier une nouvelle version sur Gitea
+
+1. Modifiez les fichiers du thème selon vos besoins.
+2. Incrémentez le numéro de version dans :
+ - l'en-tête `Version:` de `style.css` ;
+ - la constante `CGT_FHA_VERSION` dans `functions.php` (et `CGT_FHA_SCHEMA_VERSION` dans `inc/theme-options.php` en cas de migration structurelle non destructive).
+3. Validez et poussez sur la branche `main` :
+ ```bash
+ git add .
+ git commit -m "Release vX.Y.Z"
+ git push origin main
+ ```
+4. Dans WordPress (**Tableau de bord > Mises à jour** ou **Apparence > Thèmes**), cliquez sur **Mettre à jour**.
diff --git a/functions.php b/functions.php
index b70cbd8..c420438 100644
--- a/functions.php
+++ b/functions.php
@@ -9,7 +9,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
-define( 'CGT_FHA_VERSION', '1.2.0' );
+define( 'CGT_FHA_VERSION', '1.2.1' );
define( 'CGT_FHA_DIR', get_template_directory() );
define( 'CGT_FHA_URI', get_template_directory_uri() );
@@ -19,6 +19,7 @@ define( 'CGT_FHA_URI', get_template_directory_uri() );
require_once CGT_FHA_DIR . '/inc/theme-options.php';
require_once CGT_FHA_DIR . '/inc/class-cgt-confederal-feed.php';
require_once CGT_FHA_DIR . '/inc/seo-performance.php';
+require_once CGT_FHA_DIR . '/inc/class-cgt-gitea-updater.php';
/**
* Configuration des supports WordPress et menus.
diff --git a/inc/class-cgt-gitea-updater.php b/inc/class-cgt-gitea-updater.php
new file mode 100644
index 0000000..e603049
--- /dev/null
+++ b/inc/class-cgt-gitea-updater.php
@@ -0,0 +1,209 @@
+checked ) ) {
+ return $transient;
+ }
+
+ $theme_slug = get_stylesheet();
+ $current_version = (string) wp_get_theme( $theme_slug )->get( 'Version' );
+ if ( '' === $current_version ) {
+ return $transient;
+ }
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $force_check = ! empty( $_GET['force-check'] );
+ $remote_version = self::get_remote_version( $force_check );
+ if ( null === $remote_version || '' === $remote_version ) {
+ return $transient;
+ }
+
+ $update_data = array(
+ 'theme' => $theme_slug,
+ 'new_version' => $remote_version,
+ 'url' => self::REPO_URL,
+ 'package' => self::PACKAGE_URL,
+ );
+
+ if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
+ $transient->response = array();
+ }
+ if ( ! isset( $transient->no_update ) || ! is_array( $transient->no_update ) ) {
+ $transient->no_update = array();
+ }
+
+ if ( version_compare( $remote_version, $current_version, '>' ) ) {
+ $transient->response[ $theme_slug ] = $update_data;
+ unset( $transient->no_update[ $theme_slug ] );
+ } else {
+ $transient->no_update[ $theme_slug ] = $update_data;
+ unset( $transient->response[ $theme_slug ] );
+ }
+
+ return $transient;
+ }
+
+ /**
+ * Récupère la version distante dans style.css sur la branche main (avec cache Transient).
+ *
+ * @param bool $force_check Ignore le cache si une vérification manuelle est demandée.
+ * @return string|null
+ */
+ private static function get_remote_version( bool $force_check = false ): ?string {
+ if ( ! $force_check ) {
+ $cached = get_transient( self::VERSION_TRANSIENT );
+ if ( is_string( $cached ) && '' !== $cached ) {
+ return $cached;
+ }
+ if ( false !== get_transient( self::ERROR_TRANSIENT ) ) {
+ return null;
+ }
+ }
+
+ $response = wp_remote_get(
+ self::RAW_STYLE_URL,
+ array(
+ 'timeout' => 10,
+ 'sslverify' => true,
+ 'headers' => array(
+ 'Accept' => 'text/plain',
+ ),
+ 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url( '/' ),
+ )
+ );
+
+ if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
+ set_transient( self::ERROR_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS );
+ return null;
+ }
+
+ $body = (string) wp_remote_retrieve_body( $response );
+ if ( '' === $body || ! preg_match( '/^\s*\*?\s*Version\s*:\s*([0-9]+\.[0-9]+(?:\.[0-9]+)*(?:-[A-Za-z0-9._-]+)?)/mi', $body, $matches ) ) {
+ set_transient( self::ERROR_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS );
+ return null;
+ }
+
+ $remote_version = trim( $matches[1] );
+ delete_transient( self::ERROR_TRANSIENT );
+ set_transient( self::VERSION_TRANSIENT, $remote_version, self::CACHE_TTL );
+
+ return $remote_version;
+ }
+
+ /**
+ * Renomme automatiquement le dossier extrait de l'archive Gitea vers get_stylesheet().
+ *
+ * @param string|WP_Error $source Chemin du dossier extrait.
+ * @param string $remote_source Dossier temporaire parent.
+ * @param WP_Upgrader $upgrader Instance du gestionnaire de mise à jour.
+ * @param array $hook_extra Métadonnées sur l'élément mis à jour.
+ * @return string|WP_Error
+ */
+ public static function fix_extracted_folder_name( $source, $remote_source, $upgrader, $hook_extra ) {
+ if ( is_wp_error( $source ) || empty( $source ) || empty( $remote_source ) ) {
+ return $source;
+ }
+
+ $theme_slug = get_stylesheet();
+ $is_single = isset( $hook_extra['theme'] ) && $hook_extra['theme'] === $theme_slug;
+ $is_bulk = isset( $hook_extra['themes'] ) && is_array( $hook_extra['themes'] ) && in_array( $theme_slug, $hook_extra['themes'], true );
+
+ if ( ! $is_single && ! $is_bulk ) {
+ return $source;
+ }
+
+ // Vérification d'intégrité (indispensable en mise à jour groupée) sur le Text Domain du thème.
+ $style_path = trailingslashit( (string) $source ) . 'style.css';
+ if ( ! file_exists( $style_path ) ) {
+ return $source;
+ }
+
+ $header_chunk = file_get_contents( $style_path, false, null, 0, 2048 );
+ if ( false === $header_chunk || ! preg_match( '/^\s*\*?\s*Text\s+Domain\s*:\s*cgt-fha\b/mi', $header_chunk ) ) {
+ return $source;
+ }
+
+ $corrected_source = trailingslashit( (string) $remote_source ) . $theme_slug . '/';
+ if ( untrailingslashit( (string) $source ) === untrailingslashit( $corrected_source ) ) {
+ return $source;
+ }
+
+ global $wp_filesystem;
+ if ( ! $wp_filesystem ) {
+ require_once ABSPATH . 'wp-admin/includes/file.php';
+ WP_Filesystem();
+ }
+
+ if ( ! $wp_filesystem ) {
+ return $source;
+ }
+
+ if ( $wp_filesystem->is_dir( $corrected_source ) ) {
+ $wp_filesystem->delete( $corrected_source, true );
+ }
+
+ if ( ! $wp_filesystem->move( (string) $source, $corrected_source, true ) ) {
+ return new WP_Error(
+ 'cgt_fha_rename_failed',
+ __( 'Impossible de renommer l\'archive du thème vers le dossier actif.', 'cgt-fha' )
+ );
+ }
+
+ return $corrected_source;
+ }
+
+ /**
+ * Purge uniquement les transients de version après une mise à jour de thème.
+ *
+ * @param WP_Upgrader $upgrader Instance du gestionnaire de mise à jour.
+ * @param array $hook_extra Métadonnées de la mise à jour terminée.
+ */
+ public static function purge_version_cache( $upgrader, $hook_extra ): void {
+ if ( 'theme' !== ( $hook_extra['type'] ?? '' ) ) {
+ return;
+ }
+
+ delete_transient( self::VERSION_TRANSIENT );
+ delete_transient( self::ERROR_TRANSIENT );
+ }
+}
+
+CGT_FHA_Gitea_Updater::init();
diff --git a/inc/theme-options.php b/inc/theme-options.php
index 2eb3e91..5a8d71f 100644
--- a/inc/theme-options.php
+++ b/inc/theme-options.php
@@ -13,7 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
const CGT_FHA_OPTION_KEY = 'cgt_fha_options';
-const CGT_FHA_SCHEMA_VERSION = '1.2.0';
+const CGT_FHA_SCHEMA_VERSION = '1.2.1';
/**
* Valeurs par défaut complètes de toutes les options du thème.
@@ -183,67 +183,46 @@ function cgt_fha_get_options( bool $fresh = false ): array {
}
/**
- * Migration automatique 1.0.0 -> 1.1.0 pour les sites où le thème est déjà activé.
+ * Migration structurelle versionnée et non destructive des options du thème.
+ *
+ * Aucun hook (init, after_switch_theme, upgrader_process_complete) ne doit jamais
+ * écraser les personnalisations existantes enregistrées dans cgt_fha_options.
+ * Toutes les valeurs manquantes sont fournies dynamiquement via wp_parse_args()
+ * dans cgt_fha_get_options().
*/
-add_action( 'init', 'cgt_fha_maybe_upgrade_options', 5 );
-function cgt_fha_maybe_upgrade_options(): void {
- if ( CGT_FHA_SCHEMA_VERSION === get_option( 'cgt_fha_schema_version', '1.0.0' ) ) {
+add_action( 'init', 'cgt_fha_maybe_migrate_options', 5 );
+function cgt_fha_maybe_migrate_options(): void {
+ $installed_version = (string) get_option( 'cgt_fha_schema_version', '' );
+ if ( CGT_FHA_SCHEMA_VERSION === $installed_version ) {
return;
}
- $defaults = cgt_fha_get_defaults();
- $saved = get_option( CGT_FHA_OPTION_KEY, array() );
+ $saved = get_option( CGT_FHA_OPTION_KEY, false );
- if ( is_array( $saved ) && ! empty( $saved ) ) {
- // Bascule du logo de pied de page vers la bannière texte blanc si ancien défaut ou vide.
- if ( empty( $saved['footer_logo_url'] ) || false !== strpos( (string) $saved['footer_logo_url'], 'logo-ul-light.svg' ) ) {
- $saved['footer_logo_url'] = $defaults['footer_logo_url'];
- }
+ if ( is_array( $saved ) && ! empty( $saved ) && '' !== $installed_version ) {
+ /**
+ * Table de correspondance réservée aux futures migrations structurelles
+ * (ex. 'ancienne_cle' => 'nouvelle_cle'). Ne modifie jamais une clé
+ * cible déjà personnalisée par l'utilisateur.
+ *
+ * @var array $renamed_keys
+ */
+ $renamed_keys = array();
+ $migrated = false;
- // Désactive le bloc Application Mobile par défaut tant que le contenu n'est pas prêt.
- $saved['app_enabled'] = 0;
-
- // Remplace les anciennes chaînes par défaut 1.0.0 si elles n'ont pas été personnalisées.
- $old_v1_defaults = array(
- 'local_news_title' => 'SUR LE TERRAIN EN HAUTE-ARIÈGE',
- 'footer_desc' => "Syndicat interprofessionnel de proximité au service des salarié·e·s, privé·e·s d'emploi et retraité·e·s de Foix et de la Haute-Ariège.",
- 'mission_1_text' => "Construire le syndicat dans votre entreprise, préparer les élections CSE et ne plus rester isolé face à l'employeur.",
- 'mission_2_text' => "Faire respecter vos droits, vos salaires et vos conditions de travail, avec l'appui de nos conseillers du salarié et juristes.",
- 'mission_3_text' => 'Connaître les actualités des luttes en Haute-Ariège et bénéficier de vos droits à la formation syndicale (CFESES).',
- 'tpe_badge' => 'Salariés sans CSE · TPE / PME',
- 'tpe_text' => "Vous travaillez dans une petite entreprise (< 11 salariés), un commerce, l'artisanat ou sur un chantier sans représentant du personnel ? L'Union Locale CGT vous accueille et vous défend.",
- 'tpe_point_1' => "Confidentialité absolue : aucune démarche n'est portée à la connaissance de votre employeur sans votre accord explicite.",
- 'tpe_point_2' => "Syndicat Multipros : adhérez et organisez-vous collectivement même dans une très petite entreprise dépourvue de section syndicale.",
- 'tpe_point_3' => 'Conseillers du Salarié : accompagnement gratuit lors des entretiens préalables à un licenciement ou une rupture.',
- 'network_2_label' => 'Confédération CGT (cgt.fr)',
- 'network_3_label' => 'La NVO (nvo.fr)',
- );
-
- foreach ( $old_v1_defaults as $key => $old_val ) {
- if ( isset( $saved[ $key ] ) && $saved[ $key ] === $old_val ) {
- $saved[ $key ] = $defaults[ $key ];
+ foreach ( $renamed_keys as $old_key => $new_key ) {
+ if ( array_key_exists( $old_key, $saved ) && ! array_key_exists( $new_key, $saved ) ) {
+ $saved[ $new_key ] = $saved[ $old_key ];
+ $migrated = true;
}
}
- // Remplace les anciens placeholders SVG du Réseau CGT par les 5 logos officiels (1.2.0).
- for ( $i = 1; $i <= 5; $i++ ) {
- $logo_key = 'network_' . $i . '_logo';
- $cur_logo = (string) ( $saved[ $logo_key ] ?? '' );
- if ( '' === $cur_logo || ( false !== strpos( $cur_logo, 'network-' ) && false !== strpos( $cur_logo, '.svg' ) ) ) {
- $saved[ $logo_key ] = $defaults[ $logo_key ];
- }
+ if ( $migrated ) {
+ update_option( CGT_FHA_OPTION_KEY, $saved );
+ cgt_fha_get_options( true );
}
-
- $saved = wp_parse_args( $saved, $defaults );
- update_option( CGT_FHA_OPTION_KEY, $saved );
- cgt_fha_get_options( true );
}
- // Purge le cache confédéral pour forcer le nouvel extracteur sur la page d'accueil.
- delete_transient( 'cgt_fha_confederal_feed' );
- delete_transient( 'cgt_fha_confederal_feed_lock' );
- delete_option( 'cgt_fha_confederal_feed_backup' );
-
update_option( 'cgt_fha_schema_version', CGT_FHA_SCHEMA_VERSION, true );
}
@@ -294,15 +273,11 @@ function cgt_fha_register_settings(): void {
}
/**
- * Pré-remplit l'option en base lors de l'activation du thème (autoload = true, taille ~5 Ko).
+ * Remarque : aucun hook after_switch_theme n'écrit dans cgt_fha_options.
+ * Toutes les valeurs par défaut sont appliquées dynamiquement à la lecture
+ * via wp_parse_args() dans cgt_fha_get_options(), préservant 100 % des
+ * réglages enregistrés en base lors d'une réactivation ou mise à jour.
*/
-add_action( 'after_switch_theme', 'cgt_fha_seed_default_options' );
-function cgt_fha_seed_default_options(): void {
- if ( false === get_option( CGT_FHA_OPTION_KEY, false ) ) {
- add_option( CGT_FHA_OPTION_KEY, cgt_fha_get_defaults(), '', 'yes' );
- }
- update_option( 'cgt_fha_schema_version', CGT_FHA_SCHEMA_VERSION, true );
-}
/**
* Nettoyage et validation de toutes les options soumises dans l'admin.
@@ -311,10 +286,11 @@ function cgt_fha_seed_default_options(): void {
* @return array
*/
function cgt_fha_sanitize_options( $input ): array {
- $input = is_array( $input ) ? $input : array();
- $defaults = cgt_fha_get_defaults();
- $existing = cgt_fha_get_options( true );
- $clean = array();
+ $input = is_array( $input ) ? $input : array();
+ $defaults = cgt_fha_get_defaults();
+ $existing = cgt_fha_get_options( true );
+ $is_form_submit = ! empty( $input['_form_submit'] );
+ $clean = array();
$text_fields = array(
'phone',
@@ -387,7 +363,7 @@ function cgt_fha_sanitize_options( $input ): array {
$clean['email'] = isset( $input['email'] )
? sanitize_email( wp_unslash( (string) $input['email'] ) )
- : $defaults['email'];
+ : (string) ( $existing['email'] ?? $defaults['email'] );
$url_fields = array(
'facebook_url',
@@ -438,18 +414,36 @@ function cgt_fha_sanitize_options( $input ): array {
'network_5_enabled',
);
foreach ( $checkbox_fields as $field ) {
- $clean[ $field ] = ! empty( $input[ $field ] ) ? 1 : 0;
+ if ( $is_form_submit ) {
+ $clean[ $field ] = ! empty( $input[ $field ] ) ? 1 : 0;
+ } else {
+ $clean[ $field ] = array_key_exists( $field, $input )
+ ? ( ! empty( $input[ $field ] ) ? 1 : 0 )
+ : (int) ( $existing[ $field ] ?? $defaults[ $field ] );
+ }
}
// Validation datetime-local (YYYY-MM-DDTHH:MM).
- $raw_expires = isset( $input['banner_expires'] ) ? sanitize_text_field( wp_unslash( (string) $input['banner_expires'] ) ) : $defaults['banner_expires'];
- $clean['banner_expires'] = preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/', $raw_expires ) ? $raw_expires : $defaults['banner_expires'];
+ if ( isset( $input['banner_expires'] ) ) {
+ $raw_expires = sanitize_text_field( wp_unslash( (string) $input['banner_expires'] ) );
+ $clean['banner_expires'] = preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/', $raw_expires )
+ ? $raw_expires
+ : (string) ( $existing['banner_expires'] ?? $defaults['banner_expires'] );
+ } else {
+ $clean['banner_expires'] = (string) ( $existing['banner_expires'] ?? $defaults['banner_expires'] );
+ }
// Validation des icônes de mission.
$valid_icons = array_keys( cgt_fha_get_mission_icon_choices() );
foreach ( array( 'mission_1_icon', 'mission_2_icon', 'mission_3_icon' ) as $icon_key ) {
- $chosen = isset( $input[ $icon_key ] ) ? sanitize_key( (string) $input[ $icon_key ] ) : $defaults[ $icon_key ];
- $clean[ $icon_key ] = in_array( $chosen, $valid_icons, true ) ? $chosen : $defaults[ $icon_key ];
+ if ( isset( $input[ $icon_key ] ) ) {
+ $chosen = sanitize_key( (string) $input[ $icon_key ] );
+ $clean[ $icon_key ] = in_array( $chosen, $valid_icons, true )
+ ? $chosen
+ : (string) ( $existing[ $icon_key ] ?? $defaults[ $icon_key ] );
+ } else {
+ $clean[ $icon_key ] = (string) ( $existing[ $icon_key ] ?? $defaults[ $icon_key ] );
+ }
}
return $clean;
@@ -570,6 +564,7 @@ function cgt_fha_render_admin_page(): void {