Une page mentionnant une offre promotionnelle limitée dans le temps reste techniquement publiable indéfiniment tant que personne ne la dépublie manuellement : WordPress ne connaît par défaut que la date de mise en ligne d’un contenu, jamais sa date d’expiration légale. Sur un site opérant des campagnes commerciales encadrées par des mentions obligatoires — durée, conditions, date de fin — ce point aveugle peut transformer un oubli de dépublication en non-conformité réelle, la page continuant d’afficher une offre qui n’existe plus.
Ce correctif introduit une date de fin de campagne associée à chaque contenu concerné, comparée en continu à la date courante, avec un avertissement affiché dans l’éditeur dès que la publication programmée dépasse cette échéance — avant que le contenu ne soit réellement en ligne au mauvais moment.
Ajouter une date de fin de campagne au contenu
Un champ meta dédié, ajouté via une métabox classique ou un champ de bloc personnalisé selon l’éditeur utilisé, stocke la date de fin légale associée au contenu.

Le plus simple est de déclarer la métadonnée côté PHP, avec une validation stricte du format, puis de l’exposer à l’éditeur de blocs via l’API REST :
add_action( 'init', function () {
foreach ( array( 'post', 'page' ) as $type ) {
register_post_meta( $type, 'campagne_date_fin', array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
'default' => '',
'sanitize_callback' => function ( $valeur ) {
return preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $valeur ) ? $valeur : '';
},
'auth_callback' => function () {
return current_user_can( 'edit_posts' );
},
) );
}
} );
Le format AAAA-MM-JJ a deux avantages : il se compare comme une simple chaîne et il n’a aucune ambiguïté de fuseau horaire, puisque la date légale d’une campagne est un jour calendaire, pas un instant précis. La fonction de nettoyage rejette tout ce qui ne respecte pas ce format : une saisie libre comme « fin juin » deviendrait une valeur vide, que l’avertissement saura signaler.
Avertir dans l’éditeur, avant la publication
L’avertissement doit apparaître au moment où l’auteur règle la date de publication, pas après. Un petit plugin JavaScript fournit aussi le champ de saisie de la date de fin, lit la date de publication et la date de fin dans le magasin de l’éditeur, affiche une notice dans le panneau du document, et verrouille la sauvegarde tant que l’incohérence subsiste :
import { registerPlugin } from '@wordpress/plugins';
import { PluginDocumentSettingPanel } from '@wordpress/edit-post';
import { useSelect, useDispatch } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
import { Notice, TextControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
function ControleCampagne() {
const { dateFin, datePublication } = useSelect( ( select ) => {
const editeur = select( 'core/editor' );
const meta = editeur.getEditedPostAttribute( 'meta' ) || {};
return {
dateFin: meta.campagne_date_fin,
datePublication: editeur.getEditedPostAttribute( 'date' ),
};
}, [] );
const { editPost, lockPostSaving, unlockPostSaving } = useDispatch( 'core/editor' );
const reference = datePublication ? new Date( datePublication ) : new Date();
const depasse =
Boolean( dateFin ) && reference > new Date( dateFin + 'T23:59:59' );
useEffect( () => {
if ( depasse ) {
lockPostSaving( 'campagne-fin' );
} else {
unlockPostSaving( 'campagne-fin' );
}
return () => unlockPostSaving( 'campagne-fin' );
}, [ depasse ] );
return (
<PluginDocumentSettingPanel
name="campagne-fin"
title={ __( 'Fin de campagne', 'campagne' ) }
>
<TextControl
type="date"
label={ __( 'Date de fin (incluse)', 'campagne' ) }
value={ dateFin || '' }
onChange={ ( valeur ) =>
editPost( { meta: { campagne_date_fin: valeur } } )
}
/>
{ depasse && (
<Notice status="warning" isDismissible={ false }>
{ __( 'La publication est programmée après la fin de la campagne.', 'campagne' ) }
</Notice>
) }
</PluginDocumentSettingPanel>
);
}
registerPlugin( 'campagne-fin', { render: ControleCampagne } );
Le champ de date utilise editPost(), qui inscrit la modification dans l’article en cours d’édition, sans appel réseau avant la sauvegarde. Le calcul a lieu dans le composant lui-même, et non dans le panneau : le contenu d’un panneau replié n’est pas monté, et le verrouillage ne fonctionnerait plus si l’auteur le refermait. La clé campagne-fin passée à lockPostSaving() identifie notre verrou : d’autres extensions peuvent verrouiller la sauvegarde pour leurs propres raisons, et chacune ne libère que le sien.
Le verrouillage est volontairement strict. Si votre équipe préfère laisser enregistrer un brouillon incohérent, supprimez l’appel au verrou et gardez seulement la notice : l’avertissement reste visible, mais n’interdit rien.
Bloquer la publication automatique côté serveur
L’éditeur n’est pas le seul chemin vers une publication : une importation, un appel REST ou une modification en masse contournent l’avertissement. Le dernier filet de sécurité se place au moment où WordPress publie un contenu programmé. Son déclencheur est une tâche planifiée portant le nom publish_future_post, dont la fonction native vérifie que l’article est toujours à l’état « programmé ». Une fonction exécutée juste avant elle peut repasser le contenu en brouillon si la campagne est terminée :
add_action( 'publish_future_post', function ( $post_id ) {
$fin = get_post_meta( $post_id, 'campagne_date_fin', true );
if ( ! $fin ) {
return;
}
$limite = date_create_immutable( $fin . ' 23:59:59', wp_timezone() );
if ( $limite && time() > $limite->getTimestamp() ) {
wp_update_post( array(
'ID' => $post_id,
'post_status' => 'draft',
) );
update_post_meta( $post_id, '_campagne_publication_bloquee', current_time( 'mysql' ) );
}
}, 5 );
La priorité 5 place cette fonction avant celle du cœur, qui s’exécute à la priorité 10 par défaut. Le fuseau horaire du site, obtenu avec wp_timezone(), évite le piège classique d’une limite calculée en UTC alors que la campagne se termine à minuit, heure de Paris. La trace enregistrée dans une métadonnée permet d’afficher ensuite une notice dans l’administration, pour que l’équipe comprenne pourquoi le contenu est redevenu un brouillon. Comme ce comportement dépend du fonctionnement interne de la planification, testez-le sur un site de recette avant de le déployer, et à chaque changement majeur de version de WordPress.
Un cas concret : une offre de déstockage encadrée
Un site de vente annonce une offre du 1er au 15 mars. La page est rédigée en février, programmée au 1er mars, avec une date de fin au 15 mars inscrite dans le champ dédié. Un collègue, quelques jours plus tard, décale la publication au 20 mars pour attendre une photo. L’éditeur affiche immédiatement la notice, et le bouton de sauvegarde est verrouillé jusqu’à ce que l’une des deux dates soit corrigée. Sans ce garde-fou, la page serait partie en ligne cinq jours après l’échéance, avec des conditions d’offre périmées.
Un délai légal qui n’est écrit que dans la tête de l’équipe finit toujours par être dépassé ; écrit dans le code, il devient une règle que tout le monde respecte sans y penser.
Dépublier les contenus échus
L’avertissement protège l’écriture, mais il laisse de côté les contenus déjà publiés dont l’échéance arrive. Une tâche quotidienne peut les repasser en brouillon, ce qui est plus prudent qu’une suppression :
function campagne_depublier_echues() {
$ids = get_posts( array(
'post_type' => array( 'post', 'page' ),
'post_status' => 'publish',
'fields' => 'ids',
'posts_per_page' => 50,
'meta_query' => array( array(
'key' => 'campagne_date_fin',
'value' => wp_date( 'Y-m-d' ),
'compare' => '<',
'type' => 'DATE',
) ),
) );
foreach ( $ids as $id ) {
wp_update_post( array( 'ID' => $id, 'post_status' => 'draft' ) );
}
}
add_action( 'campagne_controle_quotidien', 'campagne_depublier_echues' );
if ( ! wp_next_scheduled( 'campagne_controle_quotidien' ) ) {
wp_schedule_event( time(), 'daily', 'campagne_controle_quotidien' );
}
Ce choix est une décision éditoriale autant que technique. Certaines équipes préfèrent ne pas dépublier automatiquement, pour ne pas faire disparaître une page référencée sans prévenir : dans ce cas, remplacez la mise en brouillon par un courriel d’alerte adressé à l’auteur.
Les pièges à éviter
- Comparer des dates sans tenir compte du fuseau horaire du site : une campagne qui se termine à minuit expire trop tôt ou trop tard.
- Verrouiller la sauvegarde sans message explicatif : les auteurs ne comprennent pas pourquoi le bouton est grisé.
- Se reposer uniquement sur l’éditeur : les imports et les appels directs à l’API REST ne passent pas par ce code.
- Dépublier sans laisser de trace : l’équipe ne sait plus si le contenu a été retiré volontairement.
- Rendre la date de fin obligatoire pour tous les contenus : seuls les contenus de campagne sont concernés.
Conclusion
Un contenu de campagne a deux dates, et WordPress n’en connaît qu’une. Ajouter la seconde sous forme de métadonnée validée, la comparer à la publication dans l’éditeur, puis la faire respecter côté serveur et par une vérification quotidienne, transforme un oubli possible en une impossibilité pratique. Le coût est de quelques dizaines de lignes, pour un risque de non-conformité réel.