Si vous avez déjà collé un extrait d’article dans une meta description “vite fait” à minuit, vous savez le problème : c’est répétitif, ça finit souvent trop long, et ça part parfois hors-sujet. Le résumé automatique, lui, peut être fiable… à condition de le brancher proprement à WordPress 6.9.4, avec du cache, des garde-fous, et zéro clé API exposée.

Le besoin / Le cas d’usage

Un site WordPress vit de contenus. Mais le lecteur, lui, scanne. Un résumé court et stable (2–3 phrases) améliore :

  • Les pages de catégorie (aperçu homogène des articles).
  • Les newsletters (teasers rapides sans réécriture manuelle).
  • Les pages “À la une” (mise en avant plus lisible).
  • Les sites multi-auteurs : même style de résumé, même longueur.

À la fin, vous aurez un système qui :

  • génère un résumé IA lors de l’enregistrement d’un article,
  • le stocke en meta (dans la base WordPress),
  • le met en cache (Transients) pour éviter de payer deux fois,
  • l’affiche via un shortcode (compatible Divi 5, Elementor, Avada),
  • gère les erreurs (timeout, quota, JSON invalide) sans casser votre site.

Résumé rapide

  • Vous stockez la clé API dans wp-config.php (jamais en dur dans le code).
  • Un mu-plugin génère un résumé via wp_remote_post() vers l’API OpenAI.
  • Le résumé est enregistré dans post meta (_ai_summary) et réutilisé.
  • Un transient évite les appels répétés et limite les coûts.
  • Un shortcode [ai_summary] permet l’affichage dans Divi 5 / Elementor / Avada.
  • Vous avez un mode debug, des logs propres, et un tableau de diagnostic.

Quand utiliser l’IA pour ça

J’active ce type de résumé automatique quand le site a au moins une de ces contraintes :

  • Beaucoup d’articles (archives profondes, catégories très actives).
  • Plusieurs rédacteurs et besoin d’un format uniforme.
  • Contenu long (guides, dossiers, études) où un chapeau humain est souvent oublié.
  • Recyclage : newsletter, push notifications, pages “top articles”.

Ça marche aussi très bien pour des sites qui utilisent Divi 5, Elementor ou Avada : on affiche le résumé via shortcode, sans toucher au builder.

Quand ne PAS utiliser l’IA

Ne payez pas une API si une solution native suffit. Cas typiques :

  • Vous avez déjà un extrait WordPress (champ “Extrait”) et votre équipe le remplit correctement.
  • Vos articles sont courts (300–500 mots) : un extrait tronqué peut suffire.
  • Vous avez des contraintes légales fortes (contenu médical/juridique) : un résumé IA peut “interpréter” et introduire des erreurs.
  • Vous voulez un résumé 100% déterministe : l’IA varie, même avec des paramètres stricts.

Alternative simple (sans IA) : afficher l’extrait, ou générer un “résumé” en tronquant le contenu (ex. wp_trim_words()). C’est gratuit, instantané, et souvent suffisant.

Prérequis

Versions et environnement

  • WordPress : 6.9.4 (avril 2026) ou plus récent.
  • PHP : 8.1+ (recommandé). Si vous êtes en 7.x, vous allez croiser des erreurs fatales sur du code moderne.
  • HTTPS côté site (fortement recommandé) pour éviter de faire transiter des sessions admin en clair.

Une API : définition simple (utile avant le code)

Une API (Application Programming Interface) est un service accessible via HTTP. Concrètement, WordPress envoie une requête HTTP (souvent en JSON) à une URL, le service répond avec du JSON, et votre code transforme cette réponse en texte exploitable.

Dans WordPress, on fait ça proprement via l’API HTTP : wp_remote_post() et wp_remote_get(). Source officielle : HTTP API (developer.wordpress.org).

Clé API et stockage (wp-config.php)

Vous avez besoin d’une clé API d’un fournisseur IA. Ici je montre OpenAI parce que l’API est stable et bien documentée, mais la structure sera similaire pour Anthropic/Mistral/Google.

Ajoutez ensuite la clé dans wp-config.php (au-dessus de “That’s all, stop editing!”). Ne la mettez jamais dans un plugin, jamais dans un snippet partagé, jamais dans un dépôt Git.

define( 'BPCAB_OPENAI_API_KEY', 'sk-votre-cle-ici' );

Attention coûts : chaque résumé est un appel payant. Vous allez limiter ça avec le cache et en ne résumant que lors d’événements précis (publication / mise à jour).

Où coller le code

Pour ce type de fonctionnalité, j’évite functions.php (trop facile à casser lors d’une mise à jour de thème). Deux options robustes :

  • mu-plugin (recommandé) : fichier dans wp-content/mu-plugins/, chargé automatiquement.
  • plugin custom : dossier dans wp-content/plugins/, activable/désactivable.

Je pars sur un mu-plugin, parce que j’ai souvent vu des sites “perdre” leurs snippets après un changement de thème ou un plugin de snippets désactivé.


Architecture de la solution

Flux (schéma textuel)

Éditeur WordPress → enregistrement de l’article → hook save_post → récupération du contenu → vérifications (type, statut, autosave) → cache transient (éviter double appel) → wp_remote_post() → API IA → parsing JSON → sanitation → stockage en post meta _ai_summary → affichage via shortcode.

Pourquoi ce design

  • Stocker en meta : le résumé est persistant, indexable, et réutilisable sans appeler l’API à chaque affichage.
  • Transient : évite les appels multiples si vous cliquez “Mettre à jour” plusieurs fois, ou si un plugin déclenche plusieurs sauvegardes.
  • Shortcode : compatible avec Divi 5 / Elementor / Avada sans développement spécifique.

Le code complet — étape par étape

Étape 1 — Créer le mu-plugin

Créez le dossier wp-content/mu-plugins/ s’il n’existe pas. Ajoutez un fichier :

  • wp-content/mu-plugins/ai-auto-summary.php

Collez la base suivante :

<?php
/**
 * Plugin Name: AI Auto Summary (mu-plugin)
 * Description: Génère et stocke un résumé IA des articles WordPress.
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

Étape 2 — Définir les constantes et réglages

On centralise les réglages : longueur, modèle, timeout. Vous pourrez ajuster sans toucher à toute la logique.

const BPCAB_AI_SUMMARY_META_KEY = '_ai_summary';
const BPCAB_AI_SUMMARY_MODEL    = 'gpt-4.1-mini'; // Modèle “mini” : bon compromis coût/qualité (à ajuster)
const BPCAB_AI_TIMEOUT_SECONDS  = 20;
const BPCAB_AI_MAX_INPUT_CHARS  = 12000; // Limite simple pour éviter d'envoyer des romans entiers

Étape 3 — Ajouter le hook de génération au bon moment

Un hook est un point d’extension. Une action exécute du code à un moment donné (ex. sauvegarde d’un post). Un filtre modifie une valeur (ex. le contenu avant affichage). Ici, on utilise une action : save_post.

Piège classique : utiliser the_content pour générer le résumé. Ça déclenche l’IA à chaque affichage. Résultat : facture API + site lent. On génère au moment de la sauvegarde, pas au rendu.

add_action( 'save_post', 'bpcab_ai_maybe_generate_summary', 20, 3 );

/**
 * Déclenche la génération du résumé lors de l'enregistrement.
 *
 * @param int     $post_id ID du post.
 * @param WP_Post $post    Objet post.
 * @param bool    $update  True si mise à jour, false si création.
 */
function bpcab_ai_maybe_generate_summary( int $post_id, $post, bool $update ): void {
	// 1) Éviter les autosaves/révisions (sinon vous payez pour rien)
	if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
		return;
	}

	// 2) Vérifier le type de contenu : ici, uniquement les articles
	if ( $post->post_type !== 'post' ) {
		return;
	}

	// 3) Générer seulement quand l'article est publié (ou en passe de l'être)
	$status = get_post_status( $post_id );
	if ( $status !== 'publish' ) {
		return;
	}

	// 4) Si un résumé existe déjà, ne pas écraser (vous pourrez ajouter un bouton “Regénérer” plus tard)
	$existing = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
	if ( is_string( $existing ) && $existing !== '' ) {
		return;
	}

	// 5) Vérifier la présence de la clé API
	if ( ! defined( 'BPCAB_OPENAI_API_KEY' ) || ! is_string( BPCAB_OPENAI_API_KEY ) || BPCAB_OPENAI_API_KEY === '' ) {
		error_log( '[AI Summary] Clé API absente. Ajoutez BPCAB_OPENAI_API_KEY dans wp-config.php.' );
		return;
	}

	$content = bpcab_ai_get_post_text_for_summary( $post_id );
	if ( $content === '' ) {
		return;
	}

	// 6) Anti double-facturation : transient de verrouillage pendant 10 minutes
	$lock_key = 'bpcab_ai_summary_lock_' . $post_id;
	if ( get_transient( $lock_key ) ) {
		return;
	}
	set_transient( $lock_key, 1, 10 * MINUTE_IN_SECONDS );

	$summary = bpcab_ai_generate_summary_via_openai( $content, get_the_title( $post_id ) );

	// En cas d'échec, on libère le lock plus tôt (sinon il expire tout seul)
	if ( $summary === '' ) {
		delete_transient( $lock_key );
		return;
	}

	// 7) Sanitation : on stocke du texte simple
	$summary = sanitize_text_field( $summary );

	update_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, $summary );
}

Étape 4 — Extraire un texte “propre” depuis l’article

Le contenu WordPress peut contenir des blocs, du HTML, des shortcodes. Envoyer ça brut à l’IA donne souvent des résumés bizarres (elle “résume” des bouts de mise en page). On nettoie :

  • suppression des shortcodes,
  • suppression des balises HTML,
  • limite de taille.
/**
 * Récupère un texte propre à résumer (sans HTML/shortcodes).
 */
function bpcab_ai_get_post_text_for_summary( int $post_id ): string {
	$post = get_post( $post_id );
	if ( ! $post instanceof WP_Post ) {
		return '';
	}

	$text = $post->post_content;

	// Retirer les shortcodes (souvent présents avec Divi/Avada et certains builders)
	$text = strip_shortcodes( $text );

	// Retirer le HTML
	$text = wp_strip_all_tags( $text );

	// Normaliser les espaces
	$text = preg_replace( '/s+/', ' ', $text );
	$text = trim( (string) $text );

	if ( $text === '' ) {
		return '';
	}

	// Limiter la taille envoyée à l'API (simple et efficace)
	if ( mb_strlen( $text ) > BPCAB_AI_MAX_INPUT_CHARS ) {
		$text = mb_substr( $text, 0, BPCAB_AI_MAX_INPUT_CHARS );
	}

	return $text;
}

Étape 5 — Appeler l’API OpenAI avec wp_remote_post()

On fait un appel HTTP POST vers l’endpoint “Responses” (API moderne). On gère :

  • timeout (sinon votre sauvegarde peut sembler “bloquée”),
  • codes HTTP (401, 429, 500…),
  • JSON invalide,
  • cache (transient basé sur le hash du contenu).
/**
 * Génère un résumé via l'API OpenAI.
 *
 * @param string $content Texte à résumer.
 * @param string $title   Titre de l'article (aide au contexte).
 * @return string Résumé ou chaîne vide si échec.
 */
function bpcab_ai_generate_summary_via_openai( string $content, string $title = '' ): string {
	// Cache “fonctionnel” : si le contenu est identique, on réutilise le résumé
	$hash      = hash( 'sha256', $title . '|' . $content );
	$cache_key = 'bpcab_ai_summary_' . substr( $hash, 0, 32 );

	$cached = get_transient( $cache_key );
	if ( is_string( $cached ) && $cached !== '' ) {
		return $cached;
	}

	$endpoint = 'https://api.openai.com/v1/responses';

	// Prompt : court, contraint, stable
	$instruction = "Vous êtes un assistant de rédaction. Résumez l'article en français en 2 à 3 phrases maximum. " .
		"Ton neutre. Pas de listes. Pas de titre. Pas de conclusion. " .
		"Ne mentionnez pas que vous êtes une IA.";

	$input = "Titre : " . $title . "nnTexte :n" . $content;

	$body = array(
		'model' => BPCAB_AI_SUMMARY_MODEL,
		'input' => array(
			array(
				'role'    => 'system',
				'content' => $instruction,
			),
			array(
				'role'    => 'user',
				'content' => $input,
			),
		),
		// Paramètres de génération : limiter la longueur et la variabilité
		'max_output_tokens' => 160,
		'temperature'       => 0.2,
	);

	$args = array(
		'timeout' => BPCAB_AI_TIMEOUT_SECONDS,
		'headers' => array(
			'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
			'Content-Type'  => 'application/json',
		),
		'body'    => wp_json_encode( $body ),
	);

	$response = wp_remote_post( $endpoint, $args );

	if ( is_wp_error( $response ) ) {
		error_log( '[AI Summary] wp_remote_post error: ' . $response->get_error_message() );
		return '';
	}

	$code = (int) wp_remote_retrieve_response_code( $response );
	$raw  = (string) wp_remote_retrieve_body( $response );

	if ( $code < 200 || $code >= 300 ) {
		error_log( '[AI Summary] HTTP ' . $code . ' - Réponse: ' . $raw );
		return '';
	}

	$data = json_decode( $raw, true );
	if ( ! is_array( $data ) ) {
		error_log( '[AI Summary] JSON invalide. Body: ' . $raw );
		return '';
	}

	/*
	 * Parsing : l'API Responses peut renvoyer un tableau "output" avec des items.
	 * On récupère le texte de sortie de manière prudente, sans supposer un format unique.
	 */
	$summary = bpcab_ai_extract_text_from_openai_responses( $data );
	$summary = trim( (string) $summary );

	if ( $summary === '' ) {
		error_log( '[AI Summary] Résumé vide. Body: ' . $raw );
		return '';
	}

	// Sanitation côté affichage : on stocke du texte simple, mais on reste prudent
	$summary = wp_strip_all_tags( $summary );

	// Cache 30 jours : un résumé ne change pas souvent
	set_transient( $cache_key, $summary, 30 * DAY_IN_SECONDS );

	return $summary;
}

/**
 * Extrait le texte depuis la réponse OpenAI "Responses API".
 * Cette fonction est volontairement défensive.
 */
function bpcab_ai_extract_text_from_openai_responses( array $data ): string {
	// Cas fréquent : output[0].content[0].text
	if ( isset( $data['output'] ) && is_array( $data['output'] ) ) {
		foreach ( $data['output'] as $output_item ) {
			if ( ! is_array( $output_item ) ) {
				continue;
			}
			if ( ! isset( $output_item['content'] ) || ! is_array( $output_item['content'] ) ) {
				continue;
			}
			foreach ( $output_item['content'] as $content_item ) {
				if ( is_array( $content_item ) && isset( $content_item['text'] ) && is_string( $content_item['text'] ) ) {
					return $content_item['text'];
				}
			}
		}
	}

	// Fallback : certains formats renvoient "output_text"
	if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) ) {
		return $data['output_text'];
	}

	return '';
}

Étape 6 — Afficher le résumé (shortcode)

Un shortcode est un petit code entre crochets que WordPress remplace par du contenu. C’est la méthode la plus simple pour l’intégration dans :

  • Divi 5 : module “Code” ou “Texte” (en mode texte) avec [ai_summary].
  • Elementor : widget “Shortcode”.
  • Avada : élément “Shortcode” (Fusion Builder).
add_shortcode( 'ai_summary', 'bpcab_ai_summary_shortcode' );

/**
 * Shortcode: [ai_summary id="123"]
 * Sans id, affiche le résumé du post courant.
 */
function bpcab_ai_summary_shortcode( array $atts ): string {
	$atts = shortcode_atts(
		array(
			'id' => 0,
		),
		$atts,
		'ai_summary'
	);

	$post_id = (int) $atts['id'];
	if ( $post_id <= 0 ) {
		$post_id = get_the_ID();
	}

	if ( ! $post_id ) {
		return '';
	}

	$summary = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
	if ( ! is_string( $summary ) || $summary === '' ) {
		return '';
	}

	// Affichage : on autorise uniquement du texte (pas de HTML)
	$summary = esc_html( $summary );

	return '<div class="ai-summary">' . $summary . '</div>';
}

Si vous voulez du HTML minimal (ex. italique), ne stockez pas du HTML brut sans contrôle. Utilisez wp_kses_post() au moment de l’affichage et une whitelist stricte. Pour un débutant, je conseille : texte simple.


Le code assemblé complet

Copiez-collez tout ce fichier dans wp-content/mu-plugins/ai-auto-summary.php. Ensuite, ajoutez la constante de clé API dans wp-config.php.

<?php
/**
 * Plugin Name: AI Auto Summary (mu-plugin)
 * Description: Génère et stocke un résumé IA des articles WordPress.
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

const BPCAB_AI_SUMMARY_META_KEY = '_ai_summary';
const BPCAB_AI_SUMMARY_MODEL    = 'gpt-4.1-mini';
const BPCAB_AI_TIMEOUT_SECONDS  = 20;
const BPCAB_AI_MAX_INPUT_CHARS  = 12000;

add_action( 'save_post', 'bpcab_ai_maybe_generate_summary', 20, 3 );
add_shortcode( 'ai_summary', 'bpcab_ai_summary_shortcode' );

/**
 * Déclenche la génération du résumé lors de l'enregistrement.
 *
 * @param int     $post_id ID du post.
 * @param WP_Post $post    Objet post.
 * @param bool    $update  True si mise à jour, false si création.
 */
function bpcab_ai_maybe_generate_summary( int $post_id, $post, bool $update ): void {
	if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
		return;
	}

	if ( ! ( $post instanceof WP_Post ) ) {
		return;
	}

	if ( $post->post_type !== 'post' ) {
		return;
	}

	$status = get_post_status( $post_id );
	if ( $status !== 'publish' ) {
		return;
	}

	$existing = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
	if ( is_string( $existing ) && $existing !== '' ) {
		return;
	}

	if ( ! defined( 'BPCAB_OPENAI_API_KEY' ) || ! is_string( BPCAB_OPENAI_API_KEY ) || BPCAB_OPENAI_API_KEY === '' ) {
		error_log( '[AI Summary] Clé API absente. Ajoutez BPCAB_OPENAI_API_KEY dans wp-config.php.' );
		return;
	}

	$content = bpcab_ai_get_post_text_for_summary( $post_id );
	if ( $content === '' ) {
		return;
	}

	$lock_key = 'bpcab_ai_summary_lock_' . $post_id;
	if ( get_transient( $lock_key ) ) {
		return;
	}
	set_transient( $lock_key, 1, 10 * MINUTE_IN_SECONDS );

	$summary = bpcab_ai_generate_summary_via_openai( $content, get_the_title( $post_id ) );

	if ( $summary === '' ) {
		delete_transient( $lock_key );
		return;
	}

	$summary = sanitize_text_field( $summary );

	update_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, $summary );
}

/**
 * Récupère un texte propre à résumer (sans HTML/shortcodes).
 */
function bpcab_ai_get_post_text_for_summary( int $post_id ): string {
	$post = get_post( $post_id );
	if ( ! $post instanceof WP_Post ) {
		return '';
	}

	$text = (string) $post->post_content;

	$text = strip_shortcodes( $text );
	$text = wp_strip_all_tags( $text );
	$text = preg_replace( '/s+/', ' ', $text );
	$text = trim( (string) $text );

	if ( $text === '' ) {
		return '';
	}

	if ( mb_strlen( $text ) > BPCAB_AI_MAX_INPUT_CHARS ) {
		$text = mb_substr( $text, 0, BPCAB_AI_MAX_INPUT_CHARS );
	}

	return $text;
}

/**
 * Génère un résumé via l'API OpenAI.
 */
function bpcab_ai_generate_summary_via_openai( string $content, string $title = '' ): string {
	$hash      = hash( 'sha256', $title . '|' . $content );
	$cache_key = 'bpcab_ai_summary_' . substr( $hash, 0, 32 );

	$cached = get_transient( $cache_key );
	if ( is_string( $cached ) && $cached !== '' ) {
		return $cached;
	}

	$endpoint = 'https://api.openai.com/v1/responses';

	$instruction = "Vous êtes un assistant de rédaction. Résumez l'article en français en 2 à 3 phrases maximum. " .
		"Ton neutre. Pas de listes. Pas de titre. Pas de conclusion. " .
		"Ne mentionnez pas que vous êtes une IA.";

	$input = "Titre : " . $title . "nnTexte :n" . $content;

	$body = array(
		'model' => BPCAB_AI_SUMMARY_MODEL,
		'input' => array(
			array(
				'role'    => 'system',
				'content' => $instruction,
			),
			array(
				'role'    => 'user',
				'content' => $input,
			),
		),
		'max_output_tokens' => 160,
		'temperature'       => 0.2,
	);

	$args = array(
		'timeout' => BPCAB_AI_TIMEOUT_SECONDS,
		'headers' => array(
			'Authorization' => 'Bearer ' . BPCAB_OPENAI_API_KEY,
			'Content-Type'  => 'application/json',
		),
		'body'    => wp_json_encode( $body ),
	);

	$response = wp_remote_post( $endpoint, $args );

	if ( is_wp_error( $response ) ) {
		error_log( '[AI Summary] wp_remote_post error: ' . $response->get_error_message() );
		return '';
	}

	$code = (int) wp_remote_retrieve_response_code( $response );
	$raw  = (string) wp_remote_retrieve_body( $response );

	if ( $code < 200 || $code >= 300 ) {
		error_log( '[AI Summary] HTTP ' . $code . ' - Réponse: ' . $raw );
		return '';
	}

	$data = json_decode( $raw, true );
	if ( ! is_array( $data ) ) {
		error_log( '[AI Summary] JSON invalide. Body: ' . $raw );
		return '';
	}

	$summary = bpcab_ai_extract_text_from_openai_responses( $data );
	$summary = trim( (string) $summary );

	if ( $summary === '' ) {
		error_log( '[AI Summary] Résumé vide. Body: ' . $raw );
		return '';
	}

	$summary = wp_strip_all_tags( $summary );

	set_transient( $cache_key, $summary, 30 * DAY_IN_SECONDS );

	return $summary;
}

/**
 * Extrait le texte depuis la réponse OpenAI "Responses API".
 */
function bpcab_ai_extract_text_from_openai_responses( array $data ): string {
	if ( isset( $data['output'] ) && is_array( $data['output'] ) ) {
		foreach ( $data['output'] as $output_item ) {
			if ( ! is_array( $output_item ) ) {
				continue;
			}
			if ( ! isset( $output_item['content'] ) || ! is_array( $output_item['content'] ) ) {
				continue;
			}
			foreach ( $output_item['content'] as $content_item ) {
				if ( is_array( $content_item ) && isset( $content_item['text'] ) && is_string( $content_item['text'] ) ) {
					return $content_item['text'];
				}
			}
		}
	}

	if ( isset( $data['output_text'] ) && is_string( $data['output_text'] ) ) {
		return $data['output_text'];
	}

	return '';
}

/**
 * Shortcode: [ai_summary id="123"]
 */
function bpcab_ai_summary_shortcode( array $atts ): string {
	$atts = shortcode_atts(
		array(
			'id' => 0,
		),
		$atts,
		'ai_summary'
	);

	$post_id = (int) $atts['id'];
	if ( $post_id <= 0 ) {
		$post_id = get_the_ID();
	}

	if ( ! $post_id ) {
		return '';
	}

	$summary = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
	if ( ! is_string( $summary ) || $summary === '' ) {
		return '';
	}

	return '<div class="ai-summary">' . esc_html( $summary ) . '</div>';
}

Explication du code

Pourquoi save_post (et pas the_content)

save_post se déclenche quand WordPress enregistre un contenu. Ça vous donne un moment “naturel” pour appeler l’IA une fois, stocker le résultat, et ne plus y toucher.

J’ai souvent vu des tutos qui résument “à l’affichage” via the_content. Sur un site avec cache désactivé ou un bot qui crawl, vous payez l’API en boucle. Et le lecteur peut voir un résumé différent à chaque chargement.

Le verrou transient (anti double appel)

WordPress peut sauvegarder plusieurs fois (autosave, mises à jour, plugins SEO). Le transient bpcab_ai_summary_lock_{ID} évite de déclencher l’IA 3 fois en 30 secondes.

Le cache par hash de contenu

On calcule un hash SHA-256 du titre + contenu. Si le même texte revient (copier-coller, restauration), on réutilise le résumé depuis le transient. C’est un gain direct sur la facture.

Sanitization : ce qu’on protège

  • sanitize_text_field() : retire des caractères et normalise un texte simple.
  • wp_strip_all_tags() : supprime toute balise HTML qui pourrait avoir été générée.
  • esc_html() à l’affichage : empêche toute injection HTML/JS.

Risque réel : si vous affichez une réponse IA sans échappement, vous ouvrez une porte à l’injection (rare, mais je l’ai déjà vu via des contenus qui “forcent” l’IA à produire du HTML).


Coûts API et optimisation

Les prix changent régulièrement. Prenez l’habitude de vérifier la page officielle avant de déployer : OpenAI Pricing.

Estimation simple (ordre de grandeur)

Un résumé de 2–3 phrases, c’est souvent :

  • entrée : 1 000 à 3 000 tokens (selon la longueur de l’article),
  • sortie : 80 à 160 tokens.

Exemple de calcul mensuel :

  • 200 articles résumés / mois
  • 2 000 tokens d’entrée + 120 tokens de sortie en moyenne

Vous multipliez par le prix du modèle choisi. Le point clé : le coût est surtout dans l’entrée. D’où la limite BPCAB_AI_MAX_INPUT_CHARS.

Optimisations qui marchent vraiment

  • Limiter le texte envoyé : 8 000–12 000 caractères suffisent souvent.
  • Modèle “mini” pour des résumés : inutile de payer un modèle lourd pour 3 phrases.
  • Générer une seule fois et stocker en meta.
  • Regénération manuelle (option avancée) plutôt que regénérer à chaque update.

Variantes et cas d’usage avancés

1) Regénérer le résumé quand l’article est mis à jour

Par défaut, le code ne remplace pas un résumé existant. Si vous voulez le refaire à chaque mise à jour, supprimez ce bloc :

// 4) Si un résumé existe déjà, ne pas écraser
$existing = get_post_meta( $post_id, BPCAB_AI_SUMMARY_META_KEY, true );
if ( is_string( $existing ) && $existing !== '' ) {
	return;
}

Mais je ne le fais pas sur la plupart des sites : un auteur peut faire 10 petites corrections, et vous payez 10 fois.

2) Résumer aussi des pages (post_type = page)

Vous pouvez autoriser page :

if ( ! in_array( $post->post_type, array( 'post', 'page' ), true ) ) {
	return;
}

3) Intégration Divi 5 / Elementor / Avada (pratique)

  • Divi 5 : ajoutez un module “Code” et mettez [ai_summary]. Si vous utilisez un template Theme Builder, mettez le shortcode dans le layout d’article.
  • Elementor : widget “Shortcode” dans le template Single Post, puis [ai_summary].
  • Avada : élément “Shortcode” dans le builder, ou dans un template global.

Si votre builder “nettoie” le shortcode (ça arrive sur certains modules texte), utilisez le widget/élément dédié “Shortcode”.


Sécurité et bonnes pratiques

Ne jamais exposer la clé API côté client

Ne faites pas d’appel à OpenAI depuis JavaScript dans le navigateur. Même si vous “cachez” la clé, elle finira visible. L’appel doit rester côté serveur (PHP) via wp_remote_post().

Limiter qui peut déclencher la génération

Le hook save_post est déclenché par des actions d’édition. En admin, c’est déjà protégé par les capacités WordPress. Si vous ajoutez plus tard un bouton “Regénérer”, utilisez :

  • un nonce (jeton anti-CSRF),
  • current_user_can( 'edit_post', $post_id ).

Référence : Nonces (developer.wordpress.org).

Rate limiting (anti abus / anti facture surprise)

Si plusieurs auteurs publient en masse, vous pouvez limiter à X résumés/heure via un transient global. Exemple : stocker un compteur et refuser au-delà d’un seuil. C’est basique, mais ça évite le “j’ai importé 2 000 posts et j’ai tout résumé d’un coup”.

RGPD : données envoyées à un tiers

Vous envoyez du contenu à un prestataire externe. Selon votre contexte :

  • mettez à jour votre politique de confidentialité,
  • évitez d’envoyer des données personnelles inutiles,
  • ne résumez pas automatiquement des contenus contenant des informations sensibles.

Comment tester et déboguer

1) Activer les logs WordPress

Dans wp-config.php (sur un environnement de test), activez :

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

Les erreurs seront dans wp-content/debug.log.

2) Test minimal

  1. Créez un nouvel article.
  2. Ajoutez 2–3 paragraphes de texte (pas seulement un titre).
  3. Publiez.
  4. Vérifiez la meta _ai_summary (avec un plugin comme “Query Monitor” ou via l’éditeur si vous avez un outil de meta).
  5. Ajoutez [ai_summary] dans votre template ou dans le contenu, et vérifiez l’affichage.

3) Vérifier les appels HTTP

Si vous avez des timeouts, commencez par augmenter temporairement BPCAB_AI_TIMEOUT_SECONDS à 30. Puis regardez si votre hébergeur bloque les requêtes sortantes.

Doc officielle sur l’API HTTP WordPress : HTTP API.


Si ça ne marche pas

Tableau de diagnostic

Symptôme Cause probable Vérification Solution
Le résumé n’apparaît jamais Code collé au mauvais endroit / mu-plugin non chargé Vérifiez que le fichier est dans wp-content/mu-plugins/ Déplacez le fichier, vérifiez les droits, rechargez l’admin
Erreur 500 en admin lors de la publication Erreur PHP (point-virgule manquant, parenthèse) Consultez wp-content/debug.log Corrigez la syntaxe, ou restaurez une sauvegarde
HTTP 401 dans les logs Clé API invalide Voir debug.log (réponse API) Regénérez la clé, mettez-la dans wp-config.php
HTTP 429 dans les logs Quota dépassé / rate limit fournisseur Dashboard fournisseur Attendre, réduire les appels, ajouter du rate limiting + cache
Timeout / publication “bloquée” Timeout trop bas, hébergeur lent, firewall sortant Augmenter le timeout, tester depuis un autre serveur Timeout 30s, contacter l’hébergeur, autoriser l’endpoint
Résumé vide Contenu trop court ou trop “builder” (shortcodes) Vérifiez le texte après nettoyage Ajoutez du texte réel, ajustez le nettoyage, résumez un extrait
Vous modifiez l’article mais le résumé ne change pas Le code ne régénère pas si meta existante Vérifiez la présence de _ai_summary Supprimez la meta ou implémentez une regénération manuelle

Erreurs réalistes que je vois souvent (et comment les éviter)

  • Tester en production sans sauvegarde : faites un test sur staging. Une faute de syntaxe PHP peut bloquer tout l’admin.
  • Confusion action/filtre : save_post est une action. Ne cherchez pas à “return” quelque chose pour modifier une valeur.
  • Hook inadapté : éviter the_content pour une tâche coûteuse.
  • Plugin de cache : si vous affichez le résumé via shortcode et que vous avez un cache agressif, videz-le après la première génération.
  • PHP trop ancien : si votre hébergeur est en PHP 7.4, passez en 8.1+. Référence : PHP supported versions (php.net).

Ressources


FAQ

1) Est-ce que ça marche avec Elementor / Divi 5 / Avada ?

Oui. Le shortcode [ai_summary] est la méthode la plus simple. Utilisez le widget/élément “Shortcode” de votre builder.

2) Pourquoi un mu-plugin plutôt que functions.php ?

Parce que functions.php dépend du thème. Un changement de thème, et vous perdez la fonctionnalité. Le mu-plugin est chargé automatiquement, thème ou pas.

3) Est-ce que ça va ralentir la publication d’un article ?

Oui, un peu : vous attendez la réponse API pendant la sauvegarde (jusqu’au timeout). Sur des sites très actifs, je bascule souvent ce traitement en tâche asynchrone (WP-Cron ou Action Scheduler), mais c’est une variante plus avancée.

4) Puis-je résumer uniquement un extrait (les 2 premiers paragraphes) ?

Oui. Modifiez bpcab_ai_get_post_text_for_summary() pour ne prendre que les X premiers caractères, ou pour découper par paragraphes avant nettoyage.

5) Pourquoi stocker en post meta plutôt que recalculer à l’affichage ?

Pour éviter de payer l’API à chaque page vue et pour garantir un résumé stable. À l’affichage, vous ne faites qu’une lecture en base (rapide).

6) Puis-je afficher le résumé dans les archives (catégories) automatiquement ?

Oui. Vous pouvez ajouter le shortcode dans le template d’archive (ou via le builder si votre thème le permet). Autre option : accrocher un filtre sur the_excerpt pour remplacer l’extrait par la meta, mais je le fais seulement si le site a une stratégie claire (sinon ça surprend les auteurs).

7) Est-ce que WordPress 6.9.4 impose une manière particulière de faire les requêtes HTTP ?

Non : l’approche recommandée reste wp_remote_post() via l’HTTP API. C’est stable et compatible avec les environnements où cURL n’est pas directement accessible en PHP.

8) Comment “forcer” une regénération pour un article déjà résumé ?

Supprimez la meta _ai_summary (via un outil de meta, ou un petit script admin), puis mettez à jour l’article. Variante plus propre : ajouter un bouton “Regénérer” avec nonce et capacité.

9) Est-ce que l’IA peut inventer des informations dans un résumé ?

Oui, c’est possible. Réduire la température aide, mais ne garantit pas zéro erreur. Pour des contenus sensibles, gardez une validation humaine ou n’utilisez pas l’IA.

10) Pourquoi limiter BPCAB_AI_MAX_INPUT_CHARS plutôt que compter les tokens ?

Compter les tokens précisément demande une librairie dédiée. Pour un site débutant, une limite en caractères est simple, robuste, et réduit déjà fortement les coûts.