Files
folio/CHANGELOG.md
T
cedricAbonnel e8b361e720 feat : sparklines 14j stats + filtre IPs LAN (v1.6.27)
- Admin stats : sparklines SVG par page (120×28 px, courbe + dégradé),
  carte « Pages les plus visitées » en pleine largeur
- AccessLogParser : données par jour (pages_by_day) sur 14 jours
- AccessLogParser : IPs privées/LAN exclues de la répartition réseau
- ArticleManager : suppression opérateur nullsafe superflu (PHPStan)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:50:53 +02:00

418 lines
20 KiB
Markdown

# Changelog
Toutes les modifications notables sont documentées ici.
Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) — versionnage [semver](https://semver.org/lang/fr/).
---
## [1.6.27] - 2026-05-19
### Ajouté
- Admin stats : sparklines SVG 14 jours par page dans « Pages les plus visitées » — courbe + dégradé, carte pleine largeur (#101)
### Corrigé
- Admin stats : IPs privées/LAN exclues de la répartition par réseau (Uptime Kuma et hairpin NAT ne polluent plus les stats) (#102)
---
## [1.6.26] - 2026-05-16
### Ajouté
- Page publique `/books` — catalogue de tous les livres avec ≥ 1 article publié, cards cover/titre/description/nombre de pages (#99)
- Accueil : section « Livres » (max 6) après les redécouvertes avec lien « Voir tous → /books » (#100)
---
## [1.6.25] - 2026-05-16
### Ajouté
- Admin : onglet « IA » — statut provider/clé, sélecteur `anthropic`/`claude_code`, champ modèle, procédure d'installation CLI, sauvegarde dans `site_settings.json` (#97)
- `AiService` : support du provider Claude Code CLI via `proc_open` + lecture provider/modèle depuis `SiteSettings` (#97)
- Éditeur : bouton IA unique « Analyser et proposer » — un seul appel retourne l'analyse critique et la réécriture via séparateur `===CRITIQUE===/===REWRITE===` (#96)
### Corrigé
- Éditeur IA : boutons placés dans `wizard/step1.php` (la vraie page d'édition) ; `ai-editor.js` adapté pour `#wz-content` et extraction du titre depuis le Markdown (#96)
- Sécurité CSP : extraction du `<script>` inline de `comments_section.php` vers `comments.js` (#95)
- Sécurité CSP : remplacement du `onclick` inline dans `wizard/step6.php` par `data-confirm-discard` + listener dans `admin.js` (#95)
- Sécurité CSP : remplacement du `oninput` inline dans `post_confirm.php` par un `addEventListener` dans `post_confirm.js` (#95)
---
## [1.6.24] - 2026-05-16
### Ajouté
- Éditeur : intégration IA — service `AiService`, route `ai_query`, script `ai-editor.js`, clé `ANTHROPIC_API_KEY` dans `.env` (#96)
---
## [1.6.23] - 2026-05-16
### Ajouté
- Section « Historique » dans la sidebar des articles (connectés) : liste des révisions avec lien vers le diff (#82)
---
## [1.6.22] - 2026-05-16
### Ajouté
- Widget de notation ★ (1-5 étoiles) sur les articles, accessible aux utilisateurs connectés ; affiche la moyenne et le nombre de votes pour tous (#13)
- Admin `flux` : onglet listant tous les flux RSS agrégés avec action de suppression admin (#87)
---
## [1.6.21] - 2026-05-16
### Ajouté
- Feed RSS : balise `<media:thumbnail>` avec l'image de couverture de l'article (namespace `media:`) (#90)
- Admin livres : slug généré automatiquement depuis le titre à la création (#89)
- Admin livres : champ de filtre texte en temps réel sur le sélecteur « Ajouter une page » (#89)
### Corrigé
- `autoSeoDesc` : décodage des entités HTML (`&amp;`, `&nbsp;`…) + suppression du titre en tête de description (#91)
- `post_confirm.js` : guard null sur `#confirm-slug` absent (étape 5 du wizard) — plus d'erreur JS (#91)
---
## [1.6.20] - 2026-05-16
### Ajouté
- Barre de partage sur les articles publiés : mail, X, LinkedIn, Mastodon, copie de lien, Web Share API mobile (#47)
- Déduplication des images uploadées par hardlink si même hash+taille existe déjà dans un autre article (#35)
---
## [1.6.19] - 2026-05-16
### Ajouté
- `admin/articles` : clic sur la ligne entière pour cocher/décocher la case de sélection bulk (#86)
- Cache HTTP `Last-Modified` + réponse `304 Not Modified` pour les articles publiés (#18)
- Fingerprinting des assets CSS/JS dans `layout.php` (`?v=<hash>`) pour invalidation automatique du cache navigateur (#18)
- Cache fichier `_cache/articles_list.json` pour `getAll()` — invalidé à chaque écriture, élimine le scan complet par requête (#16)
- Logging des 404 dans `DATA_PATH/_logs/not_found.json` (url, referer, user-agent, date) (#52)
### Corrigé
- `case 'view'` : les accès refusés (brouillon, avant-première, catégorie privée) utilisent désormais `templates/404.php` au lieu d'un `echo` nu (#52)
---
## [1.6.18] - 2026-05-16
### Ajouté
- Lien magique : page de confirmation GET avant consommation POST — protège contre les scanners email (#27)
- Lien magique : notification email à l'auteur de l'article lors de la vérification d'un commentaire (#44)
- Lien magique : rate limit par IP (`MAGIC_MAX_PER_IP_HOUR`, défaut 10/h) en plus du rate limit par email (#23)
- `ArticleManager::duplicate()` + route `/duplicate/{uuid}` + bouton ⧉ dans `admin/articles` (#7)
- Cache du rendu Markdown par article (`_cache/content_rendered.json`, invalidé sur `mtime` de `index.md`) (#17)
- Lazy loading (`loading="lazy"`) sur toutes les images du contenu Markdown (#21)
---
## [1.6.17] - 2026-05-16
### Ajouté
- RSS : élément `<content:encoded>` avec HTML complet par article + namespace `content` (#42)
- RSS : filtre `?category=nom` — flux filtré par catégorie, titre et description du channel adaptés (#43)
- Commentaires : cookie `cmt_name` / `cmt_email` (1 an) pour pré-remplir le formulaire à la prochaine visite (#51)
- `flux/` : bandeau d'alerte admin listant les feeds en erreur (URL, label, email) (#45)
- `admin/emails` : bouton « Voir ↗ » ouvre le contenu HTML de l'email dans un nouvel onglet via `/admin/email-preview/{id}` (#37)
### Modifié
- RSS : `<description>` utilise désormais le champ `plain` pré-calculé (fix : contenu vide depuis v1.6.14) (#42)
---
## [1.6.16] - 2026-05-16
### Ajouté
- `SearchLogParser` : paramètre `$days` (7 ou 14) — cache distinct par période, filtre logFiles par date (#46)
- `admin/searches` : boutons 7 j / 14 j pour choisir la fenêtre d'analyse (#46)
### Modifié
- `SearchLogParser` : tri par visiteurs uniques (IPs distinctes) au lieu de hits bruts — colonne renommée « Visiteurs » (#41)
- URL inconnue / article introuvable : redirection 302 vers `/search?q=…` au lieu de page 404 (#57)
- `edit_tags` : sections « Abréviations » et « Noms composés » masquées si des valeurs connues existent pour le type (#48)
---
## [1.6.15] - 2026-05-16
### Ajouté
- `admin/articles` : champ de recherche par titre (`filter_search`), cumulable avec les autres filtres (#85)
- `admin/articles` : colonne « ★ À la une » avec toggle rapide par ligne et filtre `filter_featured` (#84)
- `post/` : date de modification affichée sous la date de publication si l'article a été modifié après sa mise en ligne (#81)
### Modifié
- `sources/` : bouton « ← Modifier » remplacé par « ← Retour à l'article » pointant vers `post/<slug>` (#83)
---
## [1.6.14] - 2026-05-15
### Modifié
- Perf : `getAll()` ne charge plus le contenu Markdown — `loadArticle()` reçoit `$withContent = false` dans `loadAll()`, seul `getByUuid()` lit encore `index.md` (#24)
- Perf : `search_index.json` enrichi du champ `featured` ; `rebuildSearchIndex()` lit `index.md` directement (indépendant du cache article)
- Perf : excerpts dans `post_list`, `author_articles`, `author_profile` proviennent du champ `plain` pré-calculé — plus de passage par Parsedown (#24)
---
## [1.6.13] - 2026-05-15
### Ajouté
- Typographie : guillemets droits convertis en guillemets courbes (`"``"` / `"`, `'``'` / `'`) dans le rendu des articles — blocs `<code>` et `<pre>` préservés (#15)
### Corrigé
- Suppression du dead code : `AuthService`, `UserRepository` et `Domain\User` — incompatibles avec le système de session actuel, aucune référence active (#19)
- Factorisation des helpers `env()` et `db()` dans `src/helpers.php`, chargé par `config/config.php` — plus de triple définition dans les pages login/OIDC (#22)
---
## [1.6.12] - 2026-05-15
### Ajouté
- Wizard édition — étape SEO : sélecteur d'**image de couverture** (og:image) désormais affiché en mode édition (était limité à la création) — sélection parmi les fichiers images existants, appliquée immédiatement via `setCover()` et mémorisée dans le draft overlay
---
## [1.6.11] - 2026-05-15
### Corrigé
- En mode édition, le slug de l'article n'est plus jamais modifié : suppression du `hidden[slug]` dans l'étape 6 de confirmation et du bloc qui le propagait dans le draft overlay
---
## [1.6.10] - 2026-05-15
### Corrigé
- Suppression d'article échouait silencieusement quand le répertoire UUID appartenait à un autre utilisateur que `www-data` (permissions `2755` → groupe sans écriture) — `removeDir()` échouait sans erreur visible et l'article restait accessible
- `ArticleManager::delete()` retourne maintenant `bool` : si le répertoire existe encore après tentative de suppression, les index ne sont pas reconstruits et l'utilisateur est redirigé vers l'article avec un message d'erreur
- `removeDir()` : erreurs PHP supprimées silencieusement (`@unlink`, `@rmdir`, `@scandir`) pour éviter les warnings qui cassaient les redirects
- `mkArticleDir()` : nouvelle méthode privée qui crée les répertoires d'articles avec `chmod 0775` explicite (contourne le umask), garantissant que `www-data` (groupe) a toujours les droits d'écriture
---
## [Unreleased]
---
## [1.6.9] - 2026-05-15
### Ajouté
- `/admin/articles` : tri par **Titre** (A→Z / Z→A) et par **Date** (publication) en cliquant les en-têtes de colonne — indicateur ↑ / ↓ sur la colonne active, paramètres `sort` et `dir` préservés lors du filtrage
---
## [1.6.8] - 2026-05-15
### Corrigé
- Tous les scripts inline déplacés vers des fichiers JS statiques (`density-fouc.js`, `density.js`, `trending-home.js`, `admin-stats.js`) — conformité CSP `script-src 'self'` (varlog)
- `onclick` / `onchange` inline dans `admin.php` migrés vers `admin.js`
- Densité M (980 px) définie comme valeur par défaut au lieu de L (pleine largeur)
---
## [1.6.7] - 2026-05-15
### Ajouté
- Sélecteur de densité L / M / S sur la page liste : pleine largeur (défaut), normal (980 px), compact (660 px) — préférence persistée dans `localStorage`
---
## [1.6.6] - 2026-05-15
### Modifié
- Page d'accueil "Meilleures audiences" : chargement AJAX depuis le flux RSS XML `/trending?period=1h` (DOMParser côté client, plus de rendu PHP)
- `/admin/stats` section "Pages les plus visitées" : chargement AJAX depuis le flux RSS XML `/trending?period=14d` — plus de parsing de logs direct pour cette colonne
- `/admin/stats` : suppression de `topGrouped` pour les pages ; seuls les livres (`/book/`) et l'ASN conservent le parsing log côté serveur
---
## [1.6.5] - 2026-05-15
### Modifié
- `/tendances` et page d'accueil (rubrique "Meilleures audiences") : lecture seule du cache généré par `/trending?period=…` — plus aucun parsing de logs en dehors du flux RSS
- Rubrique renommée "Meilleures audiences · 1 heure" (ex "Tendances · 10 derniers jours")
---
## [1.6.4] - 2026-05-15
### Ajouté
- `src/TrendingParser.php` : parseur de logs Apache comptant les visiteurs uniques (IPs distinctes, HTTP 200) par article, avec support multi-préfixes et méthode `topGrouped()` (un seul parse pour pages + livres)
- `public/trending.php` : flux RSS des 50 articles les plus consultés, paramétrable par période (`?period=10m|20m|30m|1h|8h|1d|7d|14d|30d|1y`), cache TTL adaptatif
- `public/tendances.php` : page publique présentant les tendances par période, les flux RSS disponibles et la méthodologie
- Route `/tendances` dans `.htaccess`
### Modifié
- `/admin/stats` : utilise `TrendingParser` (visiteurs uniques) au lieu d'`AccessLogParser` (hits bruts) pour les pages et les livres ; label mis à jour
- Page d'accueil — rubrique Tendances : source principale désormais les logs Apache sur 1 heure (cache 12 min), fallback sur le score pondéré DB si les logs ne sont pas lisibles
---
## [1.6.3] - 2026-05-15
### Ajouté
- `scripts/server/folio-upgrade.sh` : script de déploiement serveur (clone fresh, permissions, composer, migrations SQL, `.sessions`, `safe.directory`) appelé par `sudo` depuis le bouton admin "Mettre à jour"
- `UpdateChecker::getLastUpgradeLog()` : affiche le journal de la dernière mise à jour dans l'admin (`<details>`)
### Modifié
- `run_engine_update` : délègue entièrement le déploiement au script `sudo /usr/local/bin/folio-upgrade.sh` — supprime le `git pull` inline qui ne fonctionnait pas avec les contraintes de permissions root
- `run_content_migrations` ajouté aux actions `noindex`
- Stats admin (`/admin/stats`) : cache 60 s dans `DATA_PATH/.stats_cache.json` pour le parsing des logs Apache et le lookup ASN
---
## [1.6.2] - 2026-05-15
### Corrigé
- `oidc/start.php` : garde explicite après `session_start()` — erreur 500 immédiate si `session.save_path` est inaccessible, évite un flux OIDC condamné à l'échec silencieux
- `oidc/callback.php` : même garde de session ; `error_log` en cas d'échec du contrôle de state pour faciliter le diagnostic
- `consignes.md` : règle ajoutée — pool PHP-FPM avec `user = www-data`, pas le compte admin personnel
---
## [1.6.1] - 2026-05-15
### Corrigé
- `login/index.php`, `login/magic.php`, `logout.php` : ordre de chargement corrigé (`config.php` avant `bootstrap.php`) pour que `SESSION_NAME` soit défini avant `session_start()`
- `data/site/` retiré du suivi git du moteur (contenu site-spécifique) ; `.gitignore` mis à jour
---
## [1.6.0] - 2026-05-15
### Ajouté
- Admin → Dashboard : bouton unique **Mettre à jour** (git pull + migrations SQL + migrations contenu) remplace les boutons séparés
- Branche `dev` : branche d'intégration permanente pour le développement quotidien
### Corrigé
- `run_engine_update` : vérifie que le remote git `origin` correspond à `FOLIO_REPO_URL` avant tout `git pull` (évite le pull sur le mauvais dépôt)
---
## [1.5.0] - 2026-05-15
### Ajouté
- Admin → Site : configuration de l'URL du dépôt Folio et de la branche suivie pour les mises à jour, sans modifier le `.env` (`folio_repo_url`, `folio_update_branch` dans `site_settings.json`)
- `APP_TIMEZONE` : fuseau horaire configurable via `.env` (défaut `Europe/Paris`), appliqué globalement dans `bootstrap.php`
### Corrigé
- Bouton « Vérifier » masqué avec message explicatif si `FOLIO_REPO_URL` n'est pas configuré (ni dans `.env` ni dans l'admin)
- `FOLIO_REPO_URL` et `FOLIO_UPDATE_BRANCH` documentés dans `.env.example`
- `scripts/push.sh` : ne pousse plus directement sur `main` — pousse sur la branche courante pour forcer le passage par une PR
---
## [1.4.0] - 2026-05-15
### Ajouté
- **`DATA_PATH`** : chemin des articles configurable via `.env`, indépendant du document root — permet de stocker `/data` hors de l'arborescence web (ex. `/srv/data/folio`)
- **`DataGit`** : auto-commit git sur toutes les écritures articles et livres (création, modification, suppression, métadonnées, tags, fichiers, liens…) sauf `autosave` — no-op silencieux si `DATA_PATH` n'est pas un dépôt git
- **Admin — Moteur Folio** : affiche la branche suivie pour les mises à jour (`FOLIO_UPDATE_BRANCH`, défaut `main`), la date du dernier contrôle, et un bouton **Vérifier** pour forcer la vérification sans attendre le TTL du cache (1 h)
### Modifié
- `UpdateChecker` : branche cible configurable via `FOLIO_UPDATE_BRANCH` (plus de `main` hardcodé dans l'URL Gitea)
---
## [1.3.0] - 2026-05-15
### Ajouté
- Onglet **Statistiques** dans l'admin : pages les plus visitées, livres consultés, répartition par AS (#64)
- `AccessLogParser` : lecture des logs Apache (plain, `.gz`, `.tar.gz`), cache 10 min
- `AsnLookup` : résolution ASN via ip-api.com (batch, cache 30 j), détection LAN automatique
- Filtrage des AS par groupes configurables (motifs case-insensitive, formulaire admin)
- Pattern de log configurable via l'UI (onglet Recherches) avec support glob
### Corrigé
- Permissions rsync : `--chmod=Fug+rw,Fo-w` assure la lisibilité groupe sur les fichiers déployés
- `saveSiteSettings()` et `saveSmtpSettings()` : retournent un `bool` et affichent une erreur si l'écriture échoue
- `scripts/setup.sh` : script d'initialisation Folio (composer, répertoires, droits, migrations, groupe `adm`)
---
## [1.2.2] - 2026-05-14
### Corrigé
- URL introuvable : redirige vers la page de recherche (`/search?q=…`) au lieu du premier résultat (#61)
---
## [1.2.1] - 2026-05-14
### Corrigé
- Cache article invalidé si `index.md` est plus récent que `meta.json` (migration de contenu ne se reflétait pas)
- Migration 001 : `touch(meta.json)` après écriture de `index.md` pour invalider le cache
- `post_view.php` : le `# Titre` Markdown est retiré du rendu (déjà affiché par le template)
- Wizard étape 1 : en-tête affiche « Modifier » sans répéter le titre de l'article
- `wizard.js` : suppression de `scrollToCursor` (calcul erroné sur textarea auto-resize) ; Ctrl+Home / Ctrl+End scrollent correctement via `scrollIntoView`
---
## [1.2.0] - 2026-05-14
### Ajouté
- Wizard multi-étapes pour la création (5 écrans) et l'édition (6 écrans) d'articles (#58)
- Auto-sauvegarde en brouillon (debounce 3 s) avec indicateur visible
- Étape tags : champ plat avec détection automatique depuis le texte (abréviations, CamelCase, noms propres)
- Étape SEO : aperçu moteur de recherche en temps réel
- Étape 6 (édition) : diff ligne à ligne avant confirmation
- Plan Markdown dynamique (TOC) dans la colonne droite de l'éditeur
- Titre extrait du premier `# …` du contenu Markdown (plus de champ titre séparé)
- Système de migrations de contenu (`scripts/migrate_content.php`)
- Mode maintenance automatique (`data/.maintenance` → page HTTP 503)
- Migration `001` : ajout du titre Markdown dans les articles existants
- Bouton "Mettre à jour" dans l'administration (sans accès CLI)
- `UpdateChecker` : détection de mise à jour et migrations en attente
- Bandeau d'alerte pour les administrateurs sur toutes les pages
- Dashboard `/admin` : version déployée vs version disponible
### Modifié
- `ArticleManager` : +6 méthodes pour les brouillons overlay
- `lineDiff` : normalisation `\r\n``\n`, seuil relevé à 2 000 000, fallback ligne par ligne
- `push.sh` : génère `public/version.txt` (numéro de version semver) à chaque release
### Corrigé
- Diff étape 6 "violent" (tout supprimé/ajouté) dû aux fins de ligne `\r\n` du navigateur
---
## [1.1.0] - 2026-05-13
### Ajouté
- **Réactions visiteurs** : boutons 👍 / 🔥 / 🤔 sous chaque article, toggle async avec fallback formulaire natif
- **Commentaires avec vérification email** : code 6 chiffres, honeypot + CSRF, modération dans `/admin`
- **URLs propres** : `/edit/<u>`, `/new`, `/admin`, `/categorie/<cat>`, `/files/<u>/add`, `/import/<u>`, etc.
- **Moteur de recherche** : index trigramme+substring pré-construit, résultats scorés avec mise en évidence
### Amélioré
- **Cache multi-niveaux** : chargement réduit de ~5 s à ~0,4 s sur 1 000+ articles (mémoïsation, cache disque, slug index O(1))
- **Upload fichiers** : détection et message d'erreur explicite pour les fichiers > limite PHP
### Corrigé
- Métadonnées fichiers (`addFileMeta`) : guard `file_exists()` trop strict supprimé
- Sidebar droite article : classe Bootstrap `flex-nowrap-lg``flex-lg-nowrap`
- Flux RSS : exclusion catégories privées, redirection 301 `/rss``/feed`
---
## [1.0.0] - 2026-05-09
### Ajouté
- Moteur de blog PHP Folio — première release versionnée
- Articles en Markdown avec fichiers attachés, liens externes, images de couverture
- Authentification par lien magique envoyé par email (#29)
- SSO via Keycloak/OIDC avec PKCE
- Rôles, capacités et gestion des utilisateurs
- Catégories avec swatches couleur générées algorithmiquement
- Tags par type avec suggestions
- SEO : canonical, `sitemap.xml`, `robots.txt`, JSON-LD, `og:image`
- Avant-premières (articles futurs visibles aux utilisateurs autorisés)
- Pagination curseur (sans offset SQL)
- Import depuis URL (EXIF, OpenGraph, PDF)
- Historique des révisions avec diff
- Flux RSS (`/feed`) paginé avec autodiscovery
- Formulaire de contact (CSRF, honeypot, rate-limit)
- Pages légales (LCEN/RGPD), licences, à propos
- Migrations SQL versionnées (`database/migrate.php`)
- Système de déploiement par rsync