samedi 26 septembre 2026

À propos

Contact

Extensions

wp_insert_post et révisions : la course qui écrase votre contenu

Un import planifié et une sauvegarde admin qui se chevauchent, et voilà qu'une révision plus ancienne écrase la version que tout le monde voit à l'écran.

Par Clément Hadrot • 14 février 2020 • 5 min de lecture • Aucun commentaire
wp_insert_post et révisions : la course qui écrase votre contenu

Un client vend des places de concert sur son site. Toutes les nuits à 3 h, un import synchronise les stocks et les descriptions depuis un flux XML fourni par le partenaire billetterie. Un matin, la chargée de communication modifie à la main la description d’un événement à 3 h 02, pendant que l’import tourne encore. À 9 h, elle rouvre la page : sa modification a disparu, remplacée par une version datant de la veille.

Ce n’est pas un bug de cache, ni un souci de synchronisation entre serveurs. C’est une course entre deux appels à wp_insert_post() qui touchent le même article au même moment, et WordPress gère très mal ce scénario par défaut.

Le symptôme

Le contenu affiché correspond à une version antérieure à la dernière modification connue. Dans l’écran des révisions, on retrouve bien la bonne version, mais elle n’est plus celle qui est publiée. Aucune erreur PHP, aucun log d’échec : tout s’est exécuté « normalement », c’est justement le problème.

Le pattern se reproduit uniquement quand deux conditions sont réunies : un traitement automatisé (import, synchronisation, tâche cron) qui appelle wp_insert_post() ou wp_update_post() sur un article, et une intervention humaine sur ce même article dans la même fenêtre de quelques secondes.

Le diagnostic

L'essentiel à retenir : Une révision plus récente ne veut pas dire plus « valide » ; Le post_modified seul ne suffit jamais à trancher ; Verrouiller l'édition pendant un import évite la casse

La cause tient à l’ordre d’écriture en base. Chaque appel à wp_insert_post() lit d’abord la ligne existante dans wp_posts, construit un tableau de données, éventuellement crée une révision de l’état courant via wp_save_post_revision(), puis écrit la nouvelle version. Si le processus A (l’import) lit l’article avant que le processus B (la sauvegarde admin) n’ait écrit sa modification, l’import travaille sur une image périmée de l’article. Quand il écrit à son tour, il réécrit cette image périmée, révision comprise, par-dessus la modification de B.

Le point qui trompe la plupart des développeurs : on regarde l’historique des révisions et on suppose que la plus récente en date est la bonne. Ce n’est pas vrai ici, parce que la révision créée par l’import porte un timestamp de création postérieur à celui de la modification manuelle, alors que son contenu est antérieur. On peut le vérifier en comparant le contenu de chaque révision via l’écran revision.php, ou en interrogeant directement la table :

SELECT ID, post_modified, post_content
FROM wp_posts
WHERE post_parent = 4821
AND post_type = 'revision'
ORDER BY post_modified DESC
LIMIT 5;

On y voit clairement deux révisions très proches dans le temps mais avec un contenu différent, l’une portant la modification humaine, l’autre non.

Le correctif

La solution la plus robuste consiste à ne jamais laisser l’import réécrire un article sans vérifier qu’il travaille sur la dernière version connue. On ajoute un verrou logique avant le traitement, sur le modèle de ce que fait déjà WordPress pour l’édition concurrente dans l’admin (wp_check_post_lock()), mais appliqué cette fois au script d’import :

function import_verrouiller_post( int $post_id ): bool {
    $verrou = get_post_meta( $post_id, '_import_lock', true );
    if ( $verrou && ( time() - (int) $verrou ) < 60 ) {
        return false; // un autre traitement est en cours
    }
    update_post_meta( $post_id, '_import_lock', time() );
    return true;
}

Autre option, plus fine : comparer le post_modified_gmt lu au début du script avec celui présent en base juste avant l'écriture finale, et abandonner l'écriture si les deux ne correspondent plus. C'est le même principe que la gestion des conflits optimistes qu'on trouve dans beaucoup d'API REST, transposé à l'API Post.

Réduire la fenêtre de risque

  • Planifier les imports lourds en dehors des heures de travail de l'équipe éditoriale.
  • Découper l'import en lots plus courts pour réduire le temps pendant lequel un article reste « en cours de traitement ».
  • Désactiver la création de révision pendant un import purement technique, avec le filtre wp_revisions_to_keep ramené à 0 pour ce contexte précis, si l'historique n'a pas de valeur métier pour ces écritures.

La prévention

Sur ce projet, la correction retenue a combiné deux mesures : un verrou par transient avec une durée de vie de deux minutes autour de chaque écriture d'import, et une alerte Slack automatique quand le script détecte qu'un article a été modifié entre sa lecture et son écriture. En six mois d'exploitation, l'alerte s'est déclenchée quatre fois, et à chaque fois elle a évité une perte de contenu silencieuse.

Un import qui écrit sans lire l'état courant juste avant d'écrire n'est pas un import fiable, c'est un pari sur le silence des utilisateurs.

Il faut aussi former l'équipe éditoriale : si un import tourne à heure fixe, la documentation interne doit le mentionner explicitement, pour que personne ne modifie un article dans cette fenêtre sans le savoir.

Prévention à long terme

Pour les architectures plus exigeantes, on peut aller plus loin en confiant les écritures automatisées à une file d'attente traitée séquentiellement (Action Scheduler, par exemple) plutôt qu'à un cron qui frappe directement la base. Cela élimine une bonne partie des scénarios de concurrence, puisque les tâches s'exécutent une par une plutôt qu'en parallèle avec les actions humaines.

En résumé

Une révision plus récente en date n'est pas forcément plus récente en contenu : c'est le piège central de ce bug. Dès qu'un script automatisé et un humain peuvent toucher le même article, il faut un mécanisme de verrou ou de détection de conflit, sans quoi WordPress appliquera loyalement la dernière écriture reçue, même si elle efface un travail plus récent.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi