=== HKC Performance ===
Contributors: hkabla
Plugin URI: https://plugins.hervekabla.com/hkc-performance/
Tags: core web vitals, pagespeed insights, performance, monitoring, search console
Requires at least: 6.2
Tested up to: 6.9
Requires PHP: 7.4
Stable tag: 0.11.1
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Surveillance des Core Web Vitals et de PageSpeed Insights depuis l'administration, sans rien charger sur le site public.

== Description ==

L'extension ajoute un menu « Performance » (interface en anglais) qui suit quelques pages de
référence : un score PageSpeed Insights mesuré par Google, les données réelles des visiteurs
Chrome, un historique, et des pistes de lecture.

**Deux mesures, deux usages**

* **Le test de laboratoire** (Lighthouse, via PageSpeed Insights) : un chargement simulé sur un
  téléphone moyen en 4G lente. Il sert au *diagnostic* : il dit quoi corriger.
* **Les données terrain** (Chrome UX Report, 28 derniers jours) : ce que vivent réellement les
  visiteurs. Ce sont elles que Google prend en compte, et que reprend le rapport « Signaux Web
  essentiels » de Search Console. Elles servent au *pilotage*.

Les Core Web Vitals sont LCP, INP et CLS. INP n'existe qu'en données terrain ; en laboratoire,
TBT en est l'approximation.

**Un score qui ne varie plus pour rien**

Un score mobile varie de 5 à 15 points d'un test à l'autre sans que rien ne change. L'extension :

* lance plusieurs passages en parallèle (trois par défaut) et retient le passage médian ;
* chauffe le cache de la page juste avant le test, avec un profil mobile ou ordinateur, pour ne
  pas mesurer une page générée à froid ;
* relève les en-têtes de cache (QUIC.cloud, LiteSpeed, Cloudflare…) et signale une couche de
  cache disparue depuis le contrôle précédent ;
* note la version de Lighthouse de chaque contrôle et marque ses changements sur le graphique :
  une marche à cet endroit vient plus probablement de Google que du site ;
* signale un écart mobile / ordinateur important sans en faire une alerte.

**Tableau de bord**

Pour chaque page et chaque appareil (mobile en premier) : score et jauge aux couleurs de Google,
six métriques de laboratoire avec leurs seuils, évaluation Core Web Vitals des vrais visiteurs
(de la page, ou du site entier si la page manque de trafic), courbe du score sur 90 jours,
pistes d'amélioration classées par impact estimé, capture de la page vue par Google, et
conditions de la mesure.

Le tableau de bord n'appelle jamais Google à l'affichage : il lit la base et s'affiche aussitôt.
Si un contrôle échoue (quota, délai), le dernier résultat réussi reste affiché avec la cause.

**Alertes**

Évaluées sur mobile à la fin de chaque contrôle, selon des seuils réglables : score sous un
minimum (70 par défaut), LCP, TBT ou CLS au-dessus d'un maximum, chute du score d'au moins
20 points par rapport à la médiane de la semaine précédente (et non au seul contrôle d'avant),
et, en option, échec de l'évaluation Core Web Vitals des vrais visiteurs. Une chute qui coïncide
avec un changement de version de Lighthouse ou avec une mise à jour du site le précise.

Une alerte s'affiche en bandeau dans toute l'administration (masquable jusqu'à la prochaine
alerte nouvelle) et par un compteur sur le menu. Un email, facultatif, ne part que pour une
alerte nouvelle, au plus un par 24 heures.

**Changements du site**

Mises à jour et activations d'extensions, de thèmes et de WordPress, réglages de LiteSpeed Cache
modifiés, et notes saisies à la main (« images recompressées ») : tout est daté, rappelé à côté
des résultats (« depuis le contrôle précédent : LiteSpeed Cache mis à jour… ») et marqué d'un
losange sur les courbes. Les hooks de WordPress donnent l'heure exacte ; une photographie du site
prise à chaque contrôle rattrape ce qu'ils ne voient pas (fichiers remplacés par FTP, réglages).

**Pistes en langage clair**

Chaque piste de Lighthouse reçoit une explication rédigée à la main (pas d'IA), et, si LiteSpeed
Cache ou ShortPixel sont actifs, l'endroit où regarder dans ces extensions. Une piste apparue
depuis le contrôle précédent est marquée « New ». Les ressources en cause (images trop lourdes…)
sont listées.

Diagnostics propres à la pile LiteSpeed, en lecture seule :

* repères de LiteSpeed Cache lus dans le HTML servi : date de mise en cache, présence du Critical
  CSS et du CSS unique ;
* LCP au-dessus de 4 s sans Critical CSS alors que le chargement asynchrone du CSS est actif :
  Critical CSS probablement non régénéré ;
* CLS qui dépasse 0,25 avec CSS Combine actif : rappel de vérifier la mise en page ;
* QUIC.cloud absent alors qu'il répondait au contrôle précédent ;
* mise à jour de LiteSpeed Cache : rappel des vérifications habituelles.

**Search Console : qu'est-ce qu'on gagne ?**

Si une propriété Search Console est choisie, les clics, impressions et positions de chaque page
de référence et du site entier sont importés par jour et par appareil (16 mois au premier
passage). Chaque page affiche les clics hebdomadaires avec le score médian de la semaine.

Le sélecteur « Before / after » compare, autour d'un changement ou d'une note, jusqu'à 28 jours
de part et d'autre : score et LCP médians, clics, impressions et position de la page, et clics du
site entier, qui sert de témoin contre la saisonnalité et les mises à jour de Google. C'est une
corrélation, pas une preuve, et l'écran le dit.

**Export CSV**

Une ligne par contrôle : métriques, données des vrais visiteurs, et chiffres Search Console de la
page ce jour-là pour cet appareil.

**Contrôles**

* Automatiques, chaque jour ou chaque semaine à l'heure choisie (WP-Cron).
* Par page, depuis le bouton « Check this page » de sa carte.
* Manuels, depuis le bouton « Run a check now » : la progression s'affiche, l'administration
  reste utilisable, et si l'on quitte la page, WP-Cron termine le contrôle. Un contrôle manuel
  au plus toutes les 5 minutes.

Chaque étape (une page, un appareil) tient en une requête d'une minute au plus : pas de risque
de dépasser la limite d'exécution PHP de l'hébergeur.

**Rien sur le site public**

Hors administration et tâches planifiées, l'extension ne charge rien : ni classe, ni hook, ni
script, ni feuille de style. Lighthouse tourne sur les serveurs de Google ; le serveur du site
ne fait qu'une ou deux requêtes vers la page contrôlée, pour chauffer son cache.

L'extension ne modifie aucun réglage d'une autre extension (LiteSpeed Cache, QUIC.cloud,
ShortPixel…) : elle constate et oriente, elle ne corrige rien.

== Installation ==

1. Téléverser `hkc-performance.zip` depuis Extensions › Ajouter › Téléverser une extension.
2. Activer l'extension.
3. Ouvrir le menu « Performance », cliquer sur « Connect Google », puis « Sign in with Google ».

La page est réservée aux administrateurs (capacité `manage_options`).

== Connexion Google ==

Un bouton « Sign in with Google », une fenêtre popup, un compte Google : rien à configurer.

La connexion passe par **auth.hervekabla.com**, le service de connexion des extensions HKC, qui
détient l'application Google (comme MonsterInsights ou Site Kit). Le parcours :

1. la popup ouvre une page du service qui affiche le site demandeur, à confirmer ;
2. Google affiche son écran de consentement (lecture seule de Search Console) ;
3. le service renvoie vers le site un code à usage unique, valable 5 minutes ;
4. le site échange ce code, serveur à serveur, contre les jetons, avec un vérificateur (PKCE)
   qui n'a jamais quitté le site : un code intercepté ne sert à rien.

Ensuite, **les jetons restent chiffrés sur le site**, et les appels à PageSpeed Insights et à
Search Console partent du site directement vers Google. Le service n'intervient plus que pour
renouveler le jeton d'accès (environ une fois par heure d'utilisation) et pour l'historique
hebdomadaire Chrome UX Report, dont l'API n'accepte pas la connexion Google et passe donc par la
clé du service. Il ne conserve aucun jeton et ne voit passer aucun rapport.

Si le service est momentanément injoignable, le tableau de bord continue d'afficher les derniers
résultats ; les contrôles reprennent dès son retour.

Dans la modale, choisir ensuite la propriété Search Console (celle du site est présélectionnée).

Le parcours prévient la page d'origine par deux canaux (`window.opener` et `BroadcastChannel`),
parce que la politique COOP des pages Google peut couper le premier. Si le navigateur bloque la
popup, le même parcours a lieu dans la fenêtre courante.

**Sans intermédiaire (option avancée)** : un site peut utiliser son propre client OAuth, créé dans
son projet Google Cloud (environ 5 minutes, une fois). La modale guide la création : activer
« PageSpeed Insights API » et « Google Search Console API », audience « Internal » pour un compte
Google Workspace ou « External » publiée « In production » (en « Testing », Google coupe la
connexion tous les 7 jours), client « Web application » avec l'URI de redirection affichée. Une clé
API Chrome UX Report, facultative, ajoute alors l'historique hebdomadaire.

Pour tester un autre relais, définir `HKC_PERFORMANCE_RELAY` dans `wp-config.php`.

== Mises à jour ==

L'extension n'est pas distribuée par wordpress.org : ses mises à jour viennent de
plugins.hervekabla.com (en-tête `Update URI`, mécanisme natif de WordPress 5.8 et suivants).
Elles apparaissent dans l'écran des extensions comme les autres.

== Sécurité ==

* Le jeton de rafraîchissement, et le cas échéant le secret client et la clé CrUX, sont chiffrés
  en base (AES-256-GCM, clé dérivée des sels de `wp-config.php`) et jamais renvoyés au navigateur.
* Le parcours est protégé par un paramètre `state` lié à l'utilisateur et par PKCE ; via le
  service de connexion, par une page de confirmation qui affiche le site demandeur.
* Accès en lecture seule à Search Console.
* Seule l'adresse publique de la page contrôlée est envoyée à Google.

== Stockage ==

Trois tables dédiées — contrôles (`{préfixe}_hkc_performance_checks`), changements du site
(`_events`) et chiffres Search Console (`_search`) — purgées chaque jour au-delà de la durée de
conservation choisie (16 mois par défaut, comme l'historique de Search Console). Seule la capture
d'écran du dernier contrôle de chaque page est conservée.

La désinstallation révoque l'accès chez Google, supprime les tables, les réglages, les jetons,
les alertes masquées et les tâches planifiées.

== Frequently Asked Questions ==

= Les contrôles automatiques partent en retard =

WP-Cron ne s'exécute que lorsqu'une requête atteint PHP. Sur un site dont les pages sont servies
depuis un cache, cela peut tarder. Désactiver WP-Cron (`define( 'DISABLE_WP_CRON', true );`) et
programmer chez l'hébergeur une tâche cron qui appelle `wp-cron.php` toutes les 5 à 15 minutes.

= Combien de requêtes Google ? =

Pages × 2 appareils × passages : avec 5 pages et 3 passages, 30 requêtes par contrôle. Le quota
gratuit de PageSpeed Insights est de 25 000 requêtes par jour.

== Changelog ==

= 0.11.1 =
* Les mises à jour apparaissent dans l'heure qui suit leur mise en ligne, au lieu d'attendre jusqu'à une demi-journée : le manifeste du site éditeur n'est plus gardé que soixante minutes, la cadence à laquelle WordPress redemande de lui-même.

= 0.11.0 =
* Plus de boutons grisés : quand aucune extension ne peut appliquer une redirection, le bouton retient quand même la destination choisie. Les décisions s'accumulent sous « À appliquer », avec les règles Apache correspondantes, prêtes à coller dans un .htaccess.
* Les règles sont ancrées sur l'adresse exacte (RedirectMatch avec ^ et $) : Redirect fonctionne par préfixe et emporterait tout ce qui commence pareil.
* Une décision prise n'est jamais présentée comme une redirection en place : elle attend que vous la déclariez appliquée.

= 0.10.1 =
* Les journaux de 404 des extensions que le plugin ne connaît pas par leur nom sont désormais reconnus à leur forme : une table dont le nom contient « 404 », une colonne d'adresse, une colonne de date, parfois un compteur. Les extensions de redirection se comptent par dizaines ; coder pour chacune n'a pas de fin.
* Un journal reconnu ainsi est lu, jamais écrit : le plugin ne sait pas par quelle extension il est tenu, donc aucune redirection n'est proposée depuis cet onglet, et il le dit.

= 0.10.0 =
* Correction : les adresses importées depuis Search Console ne s'affichaient pas. L'onglet rendait sa marche à suivre avant même de compter ce qu'il avait en base, dès qu'aucune source vivante n'était branchée — un import tout juste fait restait invisible.
* Nouveau, avec la connexion Search Console déjà en place : « Contrôler les pages auxquelles Google envoie encore du monde ». L'API searchAnalytics donne les adresses qui reçoivent encore des clics ; le plugin demande à chacune ce qu'elle répond aujourd'hui, et retient celles qui ne répondent plus. Ce sont les 404 qui coûtent vraiment des visites. Aucun export à télécharger, aucune extension à installer.
* L'onglet ne propose plus d'installer une extension de redirection. Elle reste nécessaire pour appliquer une redirection depuis le plugin, et c'est dit — mais ce n'est plus présenté comme la marche à suivre.

= 0.9.3 =
* L'extension de redirection est détectée sur le fait qu'elle est en service, et non sur la présence de sa table : désinstaller Redirection ne supprime pas toujours wp_redirection_404, et l'onglet continuait d'annoncer un journal que plus personne n'alimentait.
* Un filtre vide ne mène plus à une impasse : quand il n'y a plus rien à traiter, la marche à suivre et l'import Search Console restent à portée, au lieu d'une page qui se contente de dire qu'elle est vide.

= 0.9.2 =
* Correction importante : l'autorisation Analytics était demandée à toute connexion, y compris sur les sites qui n'en veulent pas. Si le projet Google ne la déclare pas, Google refuse la demande entière — et c'est la connexion Search Console qui tombe avec. Elle est désormais demandée à part, par un bouton, seulement pour le relevé des 404.
* Le mode d'emploi du client OAuth propre au site mentionne les deux API Analytics à activer, qui manquaient.

= 0.9.1 =
* Correction du relevé Analytics, qui ne pouvait rien ramener en Analytics 4 : il cherchait les pages d'erreur sous le chemin /404.html, hérité d'Universal Analytics. Analytics 4 ignore ce champ et rapporte l'adresse réellement demandée. Le repère est désormais le titre de la page d'erreur, que le plugin demande au site lui-même — aucun réglage à deviner, quels que soient le thème et la langue.
* L'adresse en erreur est donc lue directement, et la page qui portait le mauvais lien vient de la dimension pageReferrer. Les vues d'une même adresse sont additionnées, et son référent le plus fréquent retenu.
* L'ancienne convention reste interrogée en parallèle, pour les sites restés dessus.

= 0.9.0 =
* Liens cassés : Google Analytics devient une source, et la seule qui ne demande rien au site. La mesure d'audience est déjà en place, elle a déjà de l'historique, et aucune table ne grossit sur le site. Le module fonctionne désormais sans aucune extension de redirection.
* Analytics apporte en prime la page qui portait le mauvais lien — ce qu'aucun journal de 404 ne donne —, affichée sous l'adresse en erreur.
* Nouvelle autorisation Google demandée : analytics.readonly, en lecture seule. Les comptes déjà connectés doivent se reconnecter pour en bénéficier — un jeton ne gagne pas une autorisation après coup. Le plugin le détecte et l'explique au lieu d'échouer.
* Le chemin des pages d'erreur est réglable : rien n'identifie une 404 dans Analytics, les extensions de mesure la déclarent sous un chemin convenu (/404.html pour MonsterInsights).

= 0.8.0 =
* Liens cassés : import d'un export Search Console. Le rapport « Pages » de Google n'a pas d'API — l'API n'expose que searchAnalytics, sitemaps, sites et l'inspection d'une adresse à la fois —, le fichier exporté est donc la seule voie. Aucun en-tête n'est lu : dans chaque ligne, la première cellule qui ressemble à une adresse de ce site est retenue, quels que soient la langue des colonnes, le séparateur et l'ordre.
* Les adresses importées n'ont pas de compteur de visites — Google n'en donne pas — et sont listées quel que soit le seuil, marquées comme venues de Search Console. Une adresse déjà vue par un vrai visiteur garde son origine et son compteur.

= 0.7.4 =
* Liens cassés : la marche à suivre s'affiche aussi quand une extension de redirection est bien installée mais que rien n'a été trouvé — c'est le cas le plus fréquent, et presque toujours le même motif : le journal des 404 est resté coupé. Un bouton ouvre directement les options de l'extension détectée.
* Troisième cas distingué : des adresses sont enregistrées mais toutes sous le seuil de visites. L'onglet dit combien, et renvoie au réglage plutôt que de laisser croire qu'il n'y a rien.

= 0.7.3 =
* GEO : une page de liste — accueil, catégorie, archive — n'est plus pénalisée parce qu'elle n'expose pas de date de mise à jour. Elle n'en a pas à elle : ce qui la date, c'est son article le plus récent, lu sur les éléments <time> de la page. Le critère d'origine reste entier pour les articles.
* Liens cassés : quand aucune extension de redirection n'est installée, l'onglet donne la marche à suivre dans l'ordre, avec un bouton d'installation, et prévient que le journal des 404 de Redirection est coupé par défaut. Dire « il n'y a rien à montrer » sans dire quoi faire n'aide personne.

= 0.7.2 =
* Les modules ont leur propre onglet, avec leurs réglages : période lue et seuil de visites pour les liens cassés, test des robots côté serveur pour le GEO. Une case perdue au milieu des réglages généraux ne disait pas qu'un onglet entier en dépendait.
* Tableau des pages de référence : l'adresse occupe enfin la place qu'il lui faut, le rôle celle qui lui suffit.
* Autocomplétion à la saisie d'une adresse : à partir de deux caractères, les pages du site correspondantes sont proposées, titre compris. Sur un site de plusieurs milliers d'articles, personne ne retrouve une adresse de mémoire.

= 0.7.1 =
* Le bouton « Suggest pages from Search Console » n'est plus désactivé quand Google n'est pas connecté ou qu'aucune propriété n'est choisie : il reste cliquable et dit ce qui manque. Un bouton grisé avale le clic sans rien expliquer — de l'extérieur, il « ne fait rien ».
* La propriété Search Console interrogée est rappelée à côté du bouton.

= 0.7.0 =
* Deux modules facultatifs, éteints tant qu'on ne les allume pas, chacun avec son onglet (Réglages › Modules supplémentaires).
* **Liens cassés** : les adresses que vos visiteurs demandent en vain, les plus fréquentes d'abord, avec une à trois destinations possibles pour chacune — même adresse ailleurs, adresse presque identique, ou même sujet. Rien n'est redirigé sans votre clic. Le module lit le journal de l'extension de redirection déjà installée (Redirection ou Rank Math) : rien de cette extension ne s'exécute sur le site public, et cela ne change pas.
* **Préparation aux réponses d'IA** : ce qu'une IA générative trouve sur vos pages de référence — données structurées, titre, résumé, titres de section, date de mise à jour — et surtout si les robots d'IA peuvent entrer. Deux vérifications : ce qu'autorise robots.txt, et ce que fait vraiment le serveur quand un de ces robots se présente, ce qui révèle un blocage par pare-feu ou extension de sécurité. Tout est lu sur votre site, aucun service extérieur n'est appelé.
* La note de préparation est plafonnée à 50 dès qu'un signal est au rouge : une page sans données structurées n'est pas « à moitié prête ». Un encadré rappelle que cette note mesure la préparation technique, pas l'autorité du domaine — l'écart avec un outil comme Zenboost est normal. Un champ permet de consigner les notes relevées ailleurs, qui rejoignent le journal du site.

= 0.6.0 =
* Échantillon de pages : jusqu'à douze pages de référence au lieu de cinq, chacune avec un libellé et un rôle — « à chaque contrôle » pour les deux ou trois pages que l'on surveille tous les jours, « avec l'échantillon » pour les autres, mesurées un jour par semaine. Les pages intérieures bougent lentement : les mesurer toutes les jours dépenserait des appels pour rien.
* Nouveau panneau en tête du tableau de bord : médiane de l'échantillon, pages qui décrochent de plus de dix points, et causes communes à plusieurs pages (« CSS inutilisé — 3 pages sur 5 »). La médiane passe avant la moyenne : une seule page catastrophique déplace la moyenne de dix points et fait conclure à un problème général qui n'existe pas.
* Les pages dont les passages sont trop dispersés sont écartées de la comparaison : une page qui oscille entre 59 et 89 n'est pas lente, elle est instable, et c'est un autre diagnostic.
* L'export CSV porte le libellé et le rôle de chaque page.

= 0.5.3 =
* Quand les passages d'un contrôle s'écartent de 15 points ou plus, le tableau de bord le dit en clair : la page est instable, un contrôle isolé — ici comme sur PageSpeed Insights — donne un chiffre au hasard dans la fourchette, et deux scores isolés ne se comparent pas. La fourchette était déjà affichée, mais personne ne la lisait.

= 0.5.2 =
* Email d'alerte habillé : en-tête « HKC Performance » et nom du site d'où part l'alerte, alertes encadrées avec une pastille « New », bouton vers le tableau de bord, et lien vers les réglages. Le nom d'expéditeur indique l'extension et le site ; une version texte accompagne le message.

= 0.5.1 =
* Consultation sur téléphone : le tableau avant / après défile horizontalement en gardant ses en-têtes, et les commandes de la page (gérer la connexion, titre de page, boutons mobile / bureau) se visent au doigt.

= 0.5.0 =
* La médiane porte toujours sur un nombre impair de passages : si Google n'en renvoie que deux distincts, un troisième est demandé. Avec deux passages, l'ancien code retenait le plus mauvais.
* L'âge de la copie testée est relevé : quand un CDN ou un proxy a servi une page déjà ancienne, le tableau de bord le dit et rappelle de purger ce cache avant de mesurer un changement.

= 0.4.0 =
* Les étapes mobile de toutes les pages passent avant les étapes ordinateur : le résultat attendu arrive en moins d'une minute.
* Chaque page a son bouton « Check this page ».
* Le panneau d'une page se rafraîchit dès que son étape se termine, sans attendre la fin du contrôle.
* Plafond réglable d'appels quotidiens à l'API PageSpeed Insights, avec le compteur du jour affiché dans les réglages.

= 0.3.1 =
* Message clair quand la permission Search Console n'a pas été cochée sur l'écran de Google.
* Historique CrUX : les semaines sans données ne sont plus lues comme des zéros.
* « Vérifier à nouveau » dans les mises à jour relit immédiatement le manifeste.

= 0.3.0 =
* Connexion Google en un clic via le service de connexion des extensions HKC (auth.hervekabla.com) : plus rien à créer dans Google Cloud. Les jetons restent sur le site.
* Historique hebdomadaire Chrome UX Report sans clé API.
* Le client OAuth propre au site devient une option avancée.
* Mises à jour automatiques depuis plugins.hervekabla.com.

= 0.2.0 =
* Alertes : seuils réglables sur mobile, chute mesurée contre la semaine précédente, bandeau dans l'administration, compteur sur le menu, email limité à un par 24 heures.
* Journal des changements du site (mises à jour, activations, réglages LiteSpeed Cache) et notes manuelles, rappelés à côté des résultats et marqués sur les courbes.
* Pistes reformulées en langage clair, avec des indications propres à LiteSpeed Cache et ShortPixel ; pistes nouvelles signalées ; ressources en cause listées.
* Diagnostics LiteSpeed : repères de cache et de Critical CSS lus dans la page, Critical CSS manquant, CSS Combine, QUIC.cloud disparu.
* Search Console : import des clics, impressions et positions par jour et par appareil, graphique hebdomadaire, comparaison avant / après contre le site entier.
* Élément LCP affiché.
* Export CSV.

= 0.1.0 =
* Phase 1 : connexion Google en popup (OAuth, client propre au site), contrôles PageSpeed Insights
  mobile et ordinateur avec médiane de plusieurs passages, préchauffage du cache et relevé des
  en-têtes, données terrain Chrome UX Report, historique du score avec marquage des versions de
  Lighthouse, pistes d'amélioration, suggestions de pages depuis Search Console, historique CrUX
  hebdomadaire facultatif.
