WP Manifestindependent plugin directory
manifest / integrations / pivot-offres

PIVOT Offres V2 self-updates

Publie les offres touristiques de PIVOT/Web (Tourisme Wallonie) : pages de listing paramétrables, recherche et pagination 100 % côté client, cartographie, pages détail optimisées SEO, multilingue fr/nl/en/de à partir des traductions renvoyées par PIVOT. Aucune offre n'est stockée en base de données.

by cgt-it · github.com/cgt-it/pivot-offres

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/cgt-it/pivot-offres/archive/refs/heads/master.zip

Ships its own WordPress updater (built-in updater), so new versions show up under Dashboard → Updates.

Extension WordPress qui publie les offres touristiques de PIVOT/Web 3.1 (Commissariat général au Tourisme, Wallonie) : pages de listing paramétrables, recherche et pagination entièrement côté navigateur, cartographie, fiches détail optimisées pour le référencement.

Aucune offre n'est écrite en base de données. Tout passe par un cache fichier, renouvelé automatiquement et réinitialisable à la main.

Multilingue fr / nl / en / de. Les traductions des contenus viennent de PIVOT ; l'interface d'administration et les textes visibles sont livrés traduits. Les langues publiées sont celles de l'extension de traduction du site — WPML, Polylang, TranslatePress et Weglot sont reconnus d'office ; sans extension, le site reste monolingue.


Installation

  1. Copiez le dossier pivot-offres dans wp-content/plugins/.
  2. Activez l'extension.
  3. Ouvrez PIVOT → Réglages, choisissez l'environnement (stage ou production), collez la clé ws_key correspondante, puis cliquez sur Tester la connexion.
  4. Créez votre première page dans PIVOT → Ajouter une page.

L'activation crée la table de journal et les deux dossiers de cache — wp-content/uploads/pivot-cache/ pour les index servis au navigateur, wp-content/pivot-cache-private/ pour tout le reste —, planifie les tâches de reconstruction, reprend les pages de l'ancien plugin PIVOT s'il en trouve (voir ci-dessous) et rafraîchit les permaliens.

Prérequis : WordPress 6.0, PHP 7.4, permaliens autres que « simple », et les dossiers wp-content et uploads accessibles en écriture.


Reprise depuis l'ancien plugin PIVOT

Un site qui utilisait l'ancien plugin Pivot (versions 2.x, tables {prefix}pivot_pages et {prefix}pivot_filter) retrouve à l'activation ses pages de listing et leurs filtres, aux mêmes adresses.

  1. Désactivez l'ancien plugin, sans le supprimer : sa désinstallation efface ses tables, et avec elles ce qu'il y a à reprendre. Les deux plugins ne peuvent pas être actifs ensemble, car ils déclarent tous deux pivot_settings().
  2. Activez PIVOT Offres. Un encadré dresse le bilan : pages et filtres repris, points à revoir.
  3. Les index se construisent en arrière-plan, une page par minute. Une page visitée avant son tour affiche un message d'attente le temps de sa construction.

La reprise ne tourne qu'une fois, et seulement si aucune page de listing n'existe encore : des pages recréées à la main ne se retrouvent pas en double. Activé pendant que l'ancien plugin l'était encore, PIVOT Offres ne démarre pas (collision de noms) : la reprise a lieu au premier écran d'administration qui suit la désactivation de l'ancien. Pour la relancer — une page supprimée par erreur, un registre qui n'était pas vide —, utilisez PIVOT → Cache et outils → Ancien plugin PIVOT : les pages déjà reprises et les adresses déjà prises sont écartées. Les tables de l'ancien plugin ne sont jamais modifiées.

Ancien plugin PIVOT Offres
Titre, chemin, requête, carte, tri repris tels quels ; titres traduits depuis WPML (contexte pivot)
Nombre de colonnes Colonnes de la page ; offres par page : 12, 15 pour 5 colonnes, 18 pour 6
Image de bandeau Image d'en-tête de la page
Description texte d'introduction
Tri aléatoire non repris : les offres suivent l'ordre de PIVOT
Type de page, shortcode non repris : l'affichage suit le type de chaque offre
Filtre « Nom » (urn:fld:nomofr) aucun critère : la recherche libre, active par défaut, cherche déjà dans le nom
Commune, Localité critères Commune et Localité, en liste déroulante
Cases « Type » (urn:typ:…) un critère Type d'offre à cases à cocher
Cases « Valeur » (urn:val:class:3star…) un critère à cases à cocher sur le champ (urn:fld:class), au nom du groupe
Cases oui/non une bascule par case
Groupe d'un filtre Groupe du critère, traductions WPML comprises. Un critère qui réunit plusieurs lignes, comme « Classement », prend plutôt le nom du groupe pour libellé
Date de début et date de fin un critère Dates « du … au … » sur la période de l'événement
Nombres critère numérique ; un minimum et un maximum sur le même champ deviennent « entre deux valeurs »

Trois différences de comportement à connaître :

  • Un critère à cases à cocher propose toutes les valeurs présentes dans les offres de la page, et non plus seulement celles que l'ancien plugin avait retenues.
  • Plusieurs cases cochées d'un même critère se combinent en « ou ».
  • Les pages d'agenda de l'ancien plugin excluaient elles-mêmes les événements terminés. Ici, c'est la requête PIVOT qui décide des offres affichées.

La clé ws_key de l'ancien plugin est reprise si aucune n'est encore saisie. L'environnement est alors réglé sur la production, ou sur stage si l'ancienne adresse du service pointait vers stage.


Comment ça marche

Le cycle des données

PIVOT/Web ──► index JSON (fichier)  ──► navigateur : recherche, filtres, pagination, carte
   │            reconstruit par cron, tenu à jour chaque nuit par différentiel
   │                  │ si « complet avec offres liées »
   │                  ▼
   └────────► fiche détail (fichier) ──► page /details/CODE&type=TYPE

Une page de listing exécute sa requête pré-programmée en entier une seule fois par cycle de cache, en mode paginé, et en tire un index compact : pour chaque offre, uniquement ce que la page affiche et filtre. Cet index est servi au navigateur comme fichier statique. Entre deux reconstructions, il est tenu à jour chaque nuit par le différentiel de PIVOT : seules les offres ajoutées, modifiées ou retirées sont redemandées (voir Mise à jour par différentiel).

Le visiteur qui tape dans le champ de recherche, coche un filtre ou change de page ne déclenche donc aucun appel à PIVOT : tout se joue dans son navigateur, sur les données déjà chargées.

Ce qui est rendu côté serveur

La première page de résultats est écrite en HTML par PHP, à partir du même index. Les moteurs de recherche et les visiteurs sans JavaScript voient donc des offres, des liens et une pagination fonctionnelle. Le script prend ensuite la main sans recharger la page.

Renouvellement du cache

Contenu Réglage Renouvellement
Index des listes Listes d'offres chaque nuit par différentiel ; reconstruction complète de sécurité la nuit, une fois la durée écoulée ; en journée, seulement si l'index manque ou a été invalidé. Sans différentiel : tâche planifiée toutes les 15 minutes, un index par passage
Fiches détail Fiches détail à la première visite après expiration ; ou à chaque reconstruction d'un index en mode Complet avec offres liées, puis chaque nuit pour les offres modifiées
Thesaurus Thesaurus à la demande, durée longue conseillée
Erreurs Erreurs évite de marteler le service sur une offre absente

Une fiche détail absente du cache coûte au visiteur un appel à PIVOT, d'une demi-seconde environ. Une page de listing réglée sur Complet avec offres liées reçoit, en construisant son index, chaque offre au niveau de détail de la fiche : elle la range au passage dans le cache des fiches, sans appel supplémentaire. Les fiches de ses offres s'ouvrent alors dès la première visite, que l'on vienne d'une vignette, d'un moteur de recherche ou d'un lien direct. En contrepartie, la construction de l'index est environ trois fois plus longue, et le cache privé grossit de 30 à 60 Ko par offre. Avec la mise à jour par différentiel, ces fiches sont gardées jusqu'à la reconstruction complète suivante, et celles des offres modifiées sont réécrites chaque nuit. Sans différentiel, gardez la durée Fiches détail au moins égale à celle des Listes d'offres : sinon les fiches expirent avant que la reconstruction suivante ne les renouvelle.

Reconstruction manuelle : PIVOT → Cache et outils, ou le bouton Reconstruire maintenant sur la page de listing (barre de progression, traitement par lots).

Les index volumineux sont construits par tranches : si le budget de temps est dépassé, la construction reprend en arrière-plan. Réduisez Offres par appel si votre hébergeur coupe les requêtes longues.

Mise à jour par différentiel

PIVOT sait dire ce qui a changé dans une requête depuis la dernière réception validée (query/CODE/diff, puis /ack). Sur un site de 1 500 offres, c'est quelques offres par jour. Chaque nuit, à l'Heure de la mise à jour (04:00 par défaut, heure du site), le plugin demande donc pour chaque page de listing :

  1. Ce qui a changé, en version légère : codes et opérations. Quand rien n'a bougé, la réponse fait 126 octets, et c'est tout pour la nuit.
  2. S'il y a des changements, les offres concernées en un second appel, au niveau de détail de la page. L'index est réécrit localement, les fiches détail suivent, puis la réception est validée chez PIVOT.

Pour 5 pages, une nuit sans changement représente 5 appels, environ 630 octets et 6 secondes, là où une reconstruction complète télécharge 18 à 70 Mo.

Ce qui garantit que rien ne se perd :

  • La référence du différentiel est posée au début de chaque reconstruction complète, avant le téléchargement : une offre modifiée pendant la construction ressortira la nuit suivante.
  • La réception n'est validée qu'après l'écriture de l'index. Si la validation se perd, les mêmes changements reviennent la nuit suivante et sont réappliqués sans dommage.
  • Tout écart ramène à une reconstruction complète, qui repose la référence : une offre modifiée ou retirée que le plugin ne connaît pas, plus de 50 changements (ou 20 % de la page), une référence perdue chez PIVOT (toutes les offres reviennent « ajoutées »), une configuration de page modifiée, des fiches locales disparues.
  • Après trois échecs d'affilée — un quart d'heure d'écart, six tentatives par nuit au plus — le différentiel est réinitialisé chez PIVOT (/clear) et la page est reconstruite.
  • La reconstruction complète reste programmée à intervalle régulier (Listes d'offres), la nuit : elle rattrape ce que le différentiel ne voit pas, comme la photo d'une offre changée sans que l'offre elle-même soit modifiée. 7 jours suffisent.

Limites :

  • PIVOT tient un seul différentiel par clé et par requête. Deux pages de listing sur la même requête se voleraient les changements : elles restent en reconstruction complète, et l'écran d'édition le signale. De même, décochez Mise à jour par différentiel sur une copie du site (préproduction, poste local) qui utilise la même clé et la même requête.
  • WordPress ne lance ses tâches planifiées qu'à la première visite qui suit l'heure prévue. Pour une heure exacte, désactivez le déclenchement par les visites (define( 'DISABLE_WP_CRON', true ); dans wp-config.php) et faites appeler wp-cron.php par une tâche cron du serveur, toutes les 5 ou 15 minutes.

L'écran d'édition d'une page de listing indique la dernière vérification et le nombre de changements appliqués. Le Journal consigne les échecs et les reprises ; au niveau tout, il garde aussi une ligne par mise à jour appliquée.


Pages de listing

Chaque page associe une URL de votre site à un code de requête PIVOT. Aucune page WordPress n'est à publier : l'adresse est créée par l'extension.

Réglage Effet
URL un ou plusieurs segments, ex. sejourner/hotels
Code de requête QRY-00-0000-0000
Paramètres pour une requête paramétrable : radius=10, une par ligne
Richesse des données Résumé (rapide), Complet — indispensable dès qu'un filtre porte sur un champ PIVOT —, ou Complet avec offres liées, qui met aussi en cache les fiches détail des offres de la page (voir Renouvellement du cache)
Offres par page pagination navigateur
Colonnes vignettes par ligne à partir de 1200 px de large ; en dessous, trois au plus dès 992 px, deux sur tablette (dès 576 px), une sur mobile — les paliers de la grille Bootstrap
Carte pointe les offres géolocalisées de la page
Image d'en-tête bandeau au-dessus du titre, repris comme image de partage (og:image)
Critères de recherche voir ci-dessous

Ajouter un filtre : les critères suggérés

Le plus simple est de ne rien saisir. Sous le titre Critères de recherche, le plugin lit un échantillon de soixante offres de votre requête et propose les critères qui ont un sens, chacun avec son nombre de valeurs, sa couverture et deux ou trois exemples :

Province — 5 valeurs · 100 % des offres — Namur · Liège · Luxembourg   [Ajouter]

Un clic pose le critère entièrement réglé : libellé, source, urn, contrôle et clé d'URL. Si le critère porte sur un champ PIVOT et que la page est encore en mode résumé, la richesse des données bascule sur Complet au passage — sinon le champ ne serait pas renvoyé et le filtre resterait vide.

Sont écartés d'office : les champs présents sur moins de 10 % des offres, ceux qui n'ont qu'une seule valeur, ceux qui en ont presque autant que d'offres (une référence interne n'est pas un critère), les descriptifs et les coordonnées de contact.

Un champ numérique échappe à la règle des valeurs trop nombreuses : cent prix différents ne font pas une liste, mais une très bonne jauge. Il est proposé comme nombre à comparer, avec son étendue :

Nombre de personnes — de 12 à 80 · 100 % des offres   [Ajouter]

Les dates d'un événement sont proposées de la même façon, en tête de liste, comme date ou période :

Dates — du 01/02/2025 au 21/01/2027 · 100 % des offres   [Ajouter]

L'analyse est mise en cache pour la durée des listes d'offres ; le lien Réanalyser les offres la refait immédiatement.

Régler un critère à la main

Ajouter un critère sur mesure ouvre un critère vierge :

  • Libellé : ce que verra le visiteur.
  • Groupe : facultatif, voir ci-dessous.
  • Source : type d'offre, localité, commune, code postal, province, ou champ PIVOT.
  • Contrôle : liste déroulante, cases à cocher, saisie libre, interrupteur, nombre à comparer, date ou période.

Les valeurs proposées au visiteur sont toujours déduites des offres de la page, avec leur nombre d'occurrences : elles suivent vos données sans que vous ayez à les tenir à jour.

Grouper des critères

Un groupe réunit plusieurs critères sous un même intertitre : typiquement une série d'interrupteurs, « Équipements » au-dessus de Terrasse, Parking et Wifi. Donnez le même nom de groupe à chacun ; le champ propose ceux déjà utilisés dans la page.

  • Le groupe s'affiche à la place du premier de ses critères, et ses critères y restent dans leur ordre.
  • Le nom du groupe se traduit une seule fois pour tous ses critères, dans le tableau Groupes de critères sous la liste. Un groupe ajouté y apparaît après enregistrement ; sans traduction, son nom s'affiche tel quel.
  • Le groupe ne sert qu'à l'affichage : le changer ne reconstruit pas l'index.

Critères numériques

Capacité, nombre de chambres, prix, distance, dénivelé : un nombre ne se choisit pas dans une liste, il se compare. Le contrôle Nombre à comparer ouvre trois réglages :

Réglage Valeurs
Comparaison Au moins (≥), Au plus (≤), Entre deux valeurs, Égal à (=)
Affichage Champ de saisie, ou Jauge (curseur) bornée par la plus petite et la plus grande valeur des offres de la page
Unité facultative, affichée à côté du nombre : €, km, m…

C'est vous qui fixez la comparaison ; le visiteur ne saisit qu'un nombre, et lit à côté ce qu'il signifie : « au moins [ 3 ] », « entre [ 10 ] € et [ 50 ] € ». Pour un budget, pensez au champ du prix minimum avec Au plus : « au plus 50 € » retient les offres dont le prix le plus bas tient dans ce budget.

  • Saisie contrôlée : « 9,50 » et « 9.50 » sont acceptés, les espaces de groupement ignorés (« 1 250 »). Une saisie qui n'est pas un nombre est signalée sous le champ et le critère ne s'applique pas ; rien n'est corrigé à la place du visiteur. Deux bornes inversées sont remises dans l'ordre.
  • Jauge : laissée en butée, elle ne restreint rien. Les deux curseurs d'un intervalle ne se croisent pas. Une jauge ne sert pas l'égalité : avec Égal à, l'affichage repasse sur le champ de saisie.
  • Offres sans valeur : une offre qui n'a pas ce champ est écartée dès que le critère est utilisé, comme pour tout autre critère.
  • Adresse : une borne par paramètre, ?chambres_min=3, ?prix_max=50, ?distance_min=5&distance_max=10 ; l'égalité garde la clé nue, ?etoiles=4.
  • Recherche plein texte : les valeurs numériques n'y entrent pas — taper « 4 » ne ramène pas tous les hôtels de quatre chambres.

Le type du champ décide des nombres reconnus : UInt, UFloat, SFloat et Currency. Durées et heures (« 3:30 ») n'en font pas partie.

Critères de date

Pour un agenda : « du 1er au 31 octobre », « jusqu'au 31 décembre ». Le contrôle Date ou période affiche le calendrier du navigateur, dans la langue du visiteur, borné par la première et la dernière date des offres de la page.

Réglage Valeurs
Comparaison Entre deux dates (du … au …), À partir d'une date, Jusqu'à une date, À une date précise

Ce qui est comparé dépend de l'urn choisie :

Urn Le critère retient une offre dont…
urn:obj:date l'une des périodes touche les dates demandées : l'événement a lieu pendant, même s'il a commencé avant ou finit après
urn:fld:date:datedeb l'une des périodes commence dans les dates demandées
urn:fld:date:datefin l'une des périodes se termine dans les dates demandées
tout autre champ Date la date elle-même tombe dans les dates demandées

urn:obj:date est le bon choix dans la plupart des cas. Le catalogue des champs le propose sous le nom Période (date de début et date de fin), juste à côté de la date de début.

  • Plusieurs périodes : un événement peut en avoir plusieurs (un objet urn:obj:date par période). Il suffit que l'une d'elles réponde.
  • Sans première date, une période terminée ne compte pas : « jusqu'au 31 octobre » se lit « d'aujourd'hui au 31 octobre ». Sinon, un spectacle joué en mars et en décembre sortirait pour octobre au titre de mars. Des dates passées demandées explicitement (« du 1er au 31 mars ») restent respectées. Cette règle ne vaut pas pour une date isolée (« tout autre champ Date » ci-dessus).
  • Une période est continue : « Du 19/12/2025 au 31/12/2026, tous les dimanches » répond à n'importe quel jour entre ces deux dates. Le détail de l'ouverture est un texte libre, que le filtre ne lit pas.
  • Adresse : même règle que pour un nombre, au format ISO : ?date_min=2026-10-01&date_max=2026-10-31, ?date_max=2026-12-31, ?date=2026-10-10 pour une date précise. 10/10/2026 est aussi accepté.
  • Dates inversées : remises dans l'ordre.
  • Offres sans date : écartées dès que le critère est utilisé.
  • Recherche plein texte : les dates n'y entrent pas.

Trouver le bon champ PIVOT

Aucune urn à retenir : le lien Parcourir les champs disponibles, sous la case Urn, ouvre le catalogue des champs lu dans le thesaurus.

  • Les champs sont groupés par catégorie, avec leur libellé traduit et leur type PIVOT.
  • Un champ de recherche filtre sur le libellé, l'urn ou la catégorie.
  • Le sélecteur de type d'offre place en tête les types réellement présents dans cette page, repérés lors de la dernière construction de l'index. Ce sont les seuls dont les champs produiront des valeurs ; les autres types du thesaurus restent accessibles en dessous.
  • Cliquer sur un champ remplit l'urn, reprend son libellé s'il est encore vide, et sélectionne le contrôle adapté :
Type PIVOT Contrôle proposé
Boolean interrupteur
Choice, HChoice liste déroulante
MultiChoice, HMultiChoice cases à cocher
UInt nombre à comparer, au moins
Currency nombre à comparer, au plus
UFloat, SFloat nombre à comparer, entre deux valeurs
Date, et la Période urn:obj:date date ou période, entre deux dates
String, StringML, TextML, URL, EMail… saisie libre
autres liste déroulante

La case Urn accepte toujours la saisie directe, avec l'autocomplétion du navigateur sur les champs déjà chargés.

Le catalogue vient du cache thesaurus : il ne coûte qu'un appel à PIVOT la première fois. Si la liste paraît incomplète après une évolution du modèle de données, réinitialisez ce cache depuis Cache et outils.

Un filtre sur un champ PIVOT exige la richesse Complet : le mode résumé ne renvoie pas les spec.


Visites guidées

À la première ouverture de Pages de listing, Ajouter une page, Champs affichés et Réglages, une visite guidée se lance : un projecteur éclaire l'élément concerné et une bulle explique à quoi il sert.

  • L'avancement est enregistré par utilisateur : chaque personne de l'équipe voit la visite une fois, et elle ne se rouvre pas ensuite. La visite est retenue dès son ouverture, pas à sa dernière étape : quitter la page en cours de route ne la fait pas revenir.
  • Le bouton à côté du titre de chaque écran la rejoue à la demande.
  • PIVOT → Cache et outils → Aide remet toutes les visites à zéro pour votre compte.
  • Les touches ← et → parcourent les étapes, Échap ferme.

La visite détaillée d'un critère

Le bloc Critères de recherche porte son propre lien, Comment régler un critère ?. Il ouvre une visite de dix étapes qui passe les cinq réglages un par un — libellé, source, urn, contrôle, clé d'URL — en expliquant ce que chacun change pour le visiteur, puis aborde les traductions, le retrait d'un critère et le rappel sur la richesse « Complet ».

Cette visite ne se lance jamais toute seule : elle répond à un clic. Et si aucun critère n'est encore présent à l'écran, elle en ajoute un d'elle-même pour avoir quelque chose à montrer.

Une étape dont la cible est absente de la page est silencieusement sautée, et le compteur s'ajuste : sur un site monolingue, par exemple, l'étape consacrée aux traductions d'un critère ne s'affiche pas. Les visites restent donc justes quel que soit l'état de l'écran.

Pour adapter les textes, ajouter une visite ou en retirer une, passez par le filtre pivot_onboarding_tours.


Mises à jour

Les sites sont prévenus des nouvelles versions publiées sur GitHub (mdegembe/pivot-offres), comme pour une extension de wordpress.org : Extensions et Tableau de bord → Mises à jour proposent la mise à jour, et Voir les détails affiche les notes de version. WordPress vérifie toutes les 12 heures. Le lien Vérifier les mises à jour, sous l'extension dans la liste, force la vérification.

Un site doit recevoir une première fois à la main la version 2.10.0 ou une version ultérieure : c'est elle qui apporte ce mécanisme. Le dossier doit s'appeler pivot-offres.

Rien n'est proposé sur une copie de développement, reconnue à son dossier .git : la mise à jour y remplacerait le dépôt et les changements non commités. Pour tester malgré tout, ajoutez define( 'PIVOT_UPDATER_FORCE', true ); dans wp-config.php.

Publier une version

  1. Dans pivot-offres.php, montez le numéro à deux endroits : l'en-tête Version: et la constante PIVOT_VERSION.
  2. Commitez, posez le tag et poussez :
    git commit -am "Version 2.10.1"
    git tag v2.10.1
    git push origin master --tags
  3. L'action GitHub .github/workflows/release.yml vérifie que le tag correspond aux deux numéros, construit pivot-offres.zip et crée la Release. Ses notes sont générées à partir des commits ; corrigez-les dans GitHub si besoin, ce sont elles que les sites affichent.

Une Release sans pivot-offres.zip n'est jamais proposée aux sites. Si l'action échoue (numéros incohérents), corrigez, supprimez le tag (git tag -d v2.10.1 puis git push origin :refs/tags/v2.10.1) et recommencez.


Vérifier la version installée

Deux endroits l'affichent : la liste des extensions de WordPress, et la première ligne du tableau PIVOT → Cache et outils → Diagnostic.

Si vous ne voyez pas une nouveauté annoncée, c'est presque toujours que l'ancienne version est encore en place. Sur une installation locale, remplacez le contenu du dossier wp-content/plugins/pivot-offres/ par celui de l'archive, plutôt que de passer par l'envoi de zip : WordPress refuse d'écraser un dossier existant. Supprimez d'abord les fichiers présents, sans quoi des restes de l'ancienne version cohabitent avec la nouvelle.

Attention : passer par Extensions → Supprimer exécute la désinstallation, qui efface les réglages et les pages de listing. Préférez le remplacement de fichiers.

Videz ensuite le cache de votre navigateur (Ctrl+F5) pour le JavaScript et les styles.


En cas de problème à l'activation

Depuis la version 1.1.1, le plugin refuse de se charger plutôt que de provoquer une erreur fatale. Il affiche alors un encadré rouge dans l'administration qui nomme précisément le problème. Les causes possibles :

Message Ce qu'il faut faire
Ces noms sont déjà déclarés sur le site Un autre plugin, votre thème, ou votre code PIVOT existant utilise déjà un des noms du plugin. Désactivez-le ou renommez ses fonctions.
PIVOT Offres demande PHP 7.4 ou plus récent Demandez la mise à jour de PHP à votre hébergeur.
L'extension PHP « SimpleXML » (ou « libxml », « json ») est absente Faites installer php-xml et php-json par votre hébergeur : sans elles, les réponses de PIVOT sont illisibles et le cache ne peut pas être écrit.
Le dossier … n'est pas accessible en écriture Corrigez les droits du dossier nommé : wp-content/uploads, où sont rangés les index, ou wp-content, qui accueille le cache privé (pivot-cache-private/).

Le plugin ne traduit aucune chaîne avant l'action init : il ne déclenche donc pas l'avertissement « Translation loading for the … domain was triggered too early » de WordPress 6.7, ni la cascade de « Cannot modify header information » qu'il entraîne quand l'affichage des erreurs est actif (Local, MAMP, serveur de développement).

Si l'activation aboutit mais qu'une étape d'installation a échoué (table de journal, dossier de cache, tâches planifiées), un avertissement jaune le signale et détaille l'étape en cause.

Obtenir le message exact

Si l'écran reste blanc, ajoutez ceci dans wp-config.php, juste avant la ligne /* That's all, stop editing! */ :

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );

Rechargez la page d'activation : l'erreur complète, avec le fichier et le numéro de ligne, apparaît dans wp-content/debug.log. WordPress envoie aussi cette information par courriel à l'adresse d'administration du site.


Multilingue

Le plugin ne gère pas les langues lui-même. Il suit l'extension de traduction installée sur le site, quelle qu'elle soit. Sans extension de traduction, le site est monolingue : une seule langue, une seule URL par page, aucun préfixe inventé.

Ce qui reste toujours vrai, dans les deux cas : les libellés des offres viennent de PIVOT, qui les fournit en français, néerlandais, anglais et allemand. Une offre en cache contient les quatre ; la langue n'est choisie qu'à l'affichage.

Extensions reconnues

WPML, Polylang, TranslatePress et Weglot sont détectés sans configuration. Le plugin leur demande la liste des langues, la langue par défaut, la langue courante et l'URL de chaque page.

Pour les URL, il procède par ordre : il demande d'abord la conversion à l'extension, puis, si elle refuse — WPML ne convertit que les adresses correspondant à un contenu WordPress, et les nôtres n'en sont pas —, il lit sa configuration d'URL et applique lui-même la règle. Les quatre formats de WPML sont couverts : répertoires, répertoire pour la langue par défaut, domaine par langue, langue en paramètre. Polylang de même.

Pour toute autre extension, quatre filtres suffisent :

add_filter( 'pivot_site_languages',   fn() => array( 'fr', 'nl' ) );
add_filter( 'pivot_default_language', fn() => 'fr' );
add_filter( 'pivot_current_language', fn( $code ) => ma_langue_courante() );
add_filter( 'pivot_language_url',     fn( $url, $path, $lang ) => mon_url( $path, $lang ), 10, 3 );

Le premier suffit à activer le mode multilingue ; le quatrième n'est nécessaire que si votre extension ne réécrit pas déjà les liens dans la page.

Langues hors PIVOT

Un site publié en espagnol garde ses pages espagnoles : elles existent, elles sont indexées, mais leurs contenus reprennent la langue par défaut, puisque PIVOT ne fournit pas cette langue. L'écran des réglages signale ces langues d'un repère orange.

Vous installez une extension de traduction après coup

C'est prévu. Le plugin garde une empreinte de la configuration linguistique. Dès qu'elle change — extension installée, langue ajoutée ou retirée, langue par défaut modifiée — il :

  • marque tous les index comme périmés, pour qu'ils soient reconstruits par langue ;
  • demande un rafraîchissement des permaliens ;
  • affiche un message dans l'administration qui nomme l'extension détectée, liste les langues, et rappelle ce qu'il vous reste à faire.

Ce qu'il vous reste à faire, précisément : traduire le titre et l'URL de chaque page de listing (section Traductions de l'écran d'édition), et le libellé de vos critères. Rien n'est perdu : ce qui n'est pas traduit reprend la langue par défaut, le site reste cohérent en attendant.

Surcharger la traduction d'un critère

Oui, et c'est là que ça se passe. Chaque critère a un repli Traductions de ce critère qui apparaît dès que le site est multilingue :

  • un libellé par langue, pour remplacer le titre du filtre ;
  • des traductions de valeurs, une ligne valeur|texte par correction.

La colonne de gauche attend la clé stable de la valeur, listée sous Valeurs disponibles pour ce critère après la première construction de l'index. Une clé stable ne change pas d'une langue à l'autre : un lien filtré (?province=namur) reste valable dans toutes les versions du site.

Ces surcharges priment sur ce que renvoie PIVOT. Elles ne servent qu'à corriger une traduction absente ou inadaptée — dans le cas courant, laissez PIVOT faire.

Sans libellé saisi dans une langue, un critère sur un champ PIVOT prend le nom que PIVOT donne à ce champ dans cette langue, comme le faisait l'ancien plugin : « Balade et randonnée » s'affiche « Walk and hike » en anglais. Le libellé de la langue par défaut reste toujours celui que vous avez saisi. Ce libellé est écrit dans l'index : il s'applique à sa prochaine construction.

Vérifier

PIVOT → Cache et outils → Diagnostic indique l'extension détectée, les langues publiées, et l'URL d'exemple générée pour chacune. Deux adresses identiques y sont signalées : cela veut dire que votre extension ne distingue pas les URL fabriquées par le plugin, et le message vous indique quoi faire. Dans ce cas une seule balise hreflang est publiée, plutôt que plusieurs identiques.


URL des fiches détail

Une fiche est publiée à l'adresse https://exemple.be/details/CODEPIVOT&type=IDTYPE, par exemple /details/CHB-01-000RV1&type=3. Sur un site multilingue, le préfixe de langue vient de l'extension de traduction : /nl/details/CHB-01-000RV1&type=3.

Le plugin ne fait aucune redirection :

Adresse demandée Résultat
/details/CHB-01-000RV1&type=3 fiche servie
/details/CHB-01-000RV1 (sans le type) fiche servie
/details/CODE-INCONNU&type=3 404

Seul le code sert à retrouver l'offre, en majuscules ou en minuscules. Les deux formes de code de PIVOT sont reconnues : avec tirets (CHB-01-000RV1) et avec soulignés (CGT_0001_00000087). Quand l'adresse demandée n'a pas de type, ou pas le bon, la fiche s'affiche quand même, et sa balise canonique indique l'adresse avec le type réel de l'offre.

WordPress ajoute d'habitude une barre oblique finale aux adresses par une redirection 301. Le plugin la désactive sur les fiches : /details/CODE&type=3 reste tel quel.

En venant d'une version antérieure à la 2.6.0, les fiches étaient publiées sous /offre/nom-de-loffre-CODE/. Ces adresses renvoient désormais une 404. La mise à jour supprime la table de redirections, les réglages d'URL et le registre des adresses, puis reconstruit les index pour que les vignettes pointent vers /details/.


Référencement

  • Titre, méta description et canonique sur les listes et les fiches ; rel="prev"/rel="next" sur les pages paginées. Une page de listing sans description SEO saisie prend le début de son introduction ; toute méta description est coupée à 160 caractères, sur un espace.
  • Open Graph et Twitter Card. L'image de partage d'une page de listing est son image d'en-tête, à défaut celle de la première offre affichée ; le filtre pivot_listing_image peut la remplacer.
  • JSON-LD, seule source de données structurées (les gabarits ne portent plus de micro-données itemprop, qui décrivaient une seconde entité pour la même offre) :
    • sur les listes, un CollectionPage et l'ItemList des offres affichées ;
    • sur les fiches, un WebPage (langue, date de modification de l'offre), l'offre elle-même et un BreadcrumbList.
  • Le type schema.org de l'offre suit le type PIVOT (Hotel, BedAndBreakfast, Campground, Event, Restaurant, TouristTrip…), à défaut sa famille. Les propriétés suivent le type :
    • un événement porte ses dates (la prochaine période, heures comprises) et son lieu. Sans date lisible, il est décrit comme TouristAttraction, Google rejetant un Event sans date de début ;
    • un itinéraire porte son point de départ (itinerary) ;
    • un lieu ou un établissement porte adresse, coordonnées GPS, téléphone, équipements (amenityFeature) ; un établissement, en plus, son courriel, et un hébergement son classement (starRating) et son nombre de chambres ;
    • sameAs rassemble le site officiel et les réseaux sociaux, sans les sites de réservation.
  • Plan du site : les pages de listing et les fiches de toutes les langues sont ajoutées au plan du site XML de WordPress (wp-sitemap-pivot-listings-1.xml, wp-sitemap-pivot-offers-1.xml). Les adresses viennent des index déjà construits : aucun appel à PIVOT.
  • /llms.txt : le sommaire du site à l'usage des agents conversationnels (format llmstxt.org), avec les pages de listing de chaque langue et leur description, puis le plan du site et les index JSON. Un fichier llms.txt déposé à la racine du site l'emporte.
  • Plan du site et llms.txt se désactivent dans PIVOT → Réglages, section Affichage.
  • Une offre introuvable renvoie un vrai 404, jamais une page vide indexable.
  • Si Yoast SEO est actif, la canonique est alignée automatiquement. Sur les pages du plugin, ses balises Open Graph et Twitter sont retirées : celles du plugin, avec l'image de la page ou de l'offre, restent seules, au lieu de passer après le logo du site. Avec une autre extension SEO, vérifiez qu'Open Graph et la méta description ne sortent pas en double sur les pages du plugin.

Insérer des offres dans une page ou un article

Le shortcode [pivot_offres] pose une liste de vignettes dans un contenu WordPress ordinaire : ni carte, ni critères, rien à manipuler pour le visiteur. C'est l'usage éditorial — trois hébergements dans un article, les nouveautés en page d'accueil — par opposition aux pages de listing, qui sont des outils de recherche.

Trois sources

[pivot_offres listing="hebergements" nombre="3"]
[pivot_offres query="QRY-00-0000-0000" nombre="4" tri="nom"]
[pivot_offres codes="ALD-01-00096Z,CHB-01-000RV1"]
  • listing — reprend l'index d'une page de listing existante. C'est la forme à préférer : rien n'est redemandé à PIVOT, l'index est déjà là.
  • query — interroge directement une requête pré-programmée. Seules les offres nécessaires sont demandées, et le résultat est mis en cache : un shortcode ne déclenche pas un appel par affichage de page.
  • codes — une sélection nommée, dans l'ordre que vous écrivez. Pour un article qui met trois adresses en avant.

Attributs

Attribut Défaut Effet
nombre 6 nombre de vignettes
colonnes 3 de 1 à 6 ; repasse à 2 puis 1 sur petit écran
tri defaut defaut, nom, aleatoire
filtre — province:namur\|type:hotel — restreint sur les critères de la page ; un critère numérique prend une étendue : chambres:3.. (au moins 3), prix:..50 (au plus 50), distance:5..10 ; un critère de date aussi : date:2026-10-01..2026-10-31, date:..31/12/2026, date:2026-10-10, et en relatif date:aujourdhui..+30 (les 30 prochains jours)
titre — titre affiché au-dessus
lien non oui ajoute un lien vers la page de listing
lien_texte Voir toutes les offres libellé de ce lien
classe — classe CSS supplémentaire

Un formulaire pour le construire

PIVOT → Shortcode évite d'avoir à retenir la syntaxe : vous choisissez la source, le nombre, les colonnes, l'ordre, une restriction éventuelle, et le shortcode s'écrit au fur et à mesure. Un bouton le copie, un autre affiche l'aperçu réel juste en dessous, sans rien perdre de ce que vous étiez en train d'essayer.

Le formulaire rappelle aussi les clés de critères disponibles pour chaque page, avec quelques-unes de leurs valeurs : c'est ce qu'il faut pour écrire filtre="province:namur" sans se tromper. Pour un critère numérique, il rappelle l'étendue des valeurs, chambres : 1..165.

Les étendues s'écrivent avec .. et non avec < ou > : WordPress vide un attribut de shortcode qui contient un < sans > correspondant.

L'écran d'édition d'une page de listing affiche par ailleurs le shortcode correspondant, prêt à copier.

Détails qui comptent

Les vignettes passent par le même gabarit que les pages de listing : si votre thème a surchargé pivot-offres/parts/card.php, sa version est reprise ici aussi.

Une erreur de configuration — page inexistante, source manquante — n'affiche un message qu'aux personnes qui peuvent administrer l'extension. Un visiteur ne voit rien.


Robustesse des données

Les réponses de PIVOT varient d'une offre à l'autre : un champ présent ici peut manquer là. Toute lecture passe par pivot_get(), qui renvoie une valeur de repli plutôt qu'un avertissement PHP.

$locality = pivot_get( $offer, 'address.locality' );      // null si absent
pivot_echo( $offer, 'address.zip', '<span>', '</span>' );  // n'affiche rien si absent

Une offre incomplète produit une fiche plus courte, jamais une erreur.


Personnalisation

Gabarits

Copiez-les dans votre thème, dans un dossier pivot-offres/, pour les surcharger : listing.php, detail.php, parts/card.php, parts/filter-range.php, parts/filter-date.php, parts/closures-alert.php, parts/closures.php.

Un thème qui réécrit listing.php affiche lui-même l'image d'en-tête : $pivot_listing['image'] en donne l'adresse, et $pivot_listing['image_id'] son identifiant dans la médiathèque (0 si elle n'en vient pas), pour wp_get_attachment_image().

Il applique aussi le nombre de colonnes, $pivot_listing['columns'] (de 1 à 6). Le gabarit de l'extension pose la classe pivot-cols-N sur la grille, et pivot.css en tire les paliers. Un thème bâti sur Bootstrap pose plutôt chaque vignette dans une colonne. Comme le script réécrit la grille dès que l'index arrive, il lui donne les classes de cette colonne dans data-column-class, et il enveloppe chaqu

This README is longer than the copy stored here. Read the rest on GitHub →