Vous gérez un site WordPress avec des centaines ou des milliers de contenus créés par de multiples éditeurs ? Vous utilisez des types de posts personnalisés avec des champs ACF mais vos contributeurs négligent souvent les aspects SEO ? Voici une solution technique pour automatiser la création des méta-descriptions Yoast à partir de vos champs personnalisés.
Le problème concret
Sur le site fied.fr, chaque établissement d’enseignement supérieur renseigne ses formations à distance. Avec plusieurs milliers de formations et de nombreux contributeurs, il est impossible de s’assurer que chaque fiche formation dispose d’une méta-description SEO optimisée.
Les éditeurs se concentrent naturellement sur le contenu pédagogique, mais négligent souvent ces éléments techniques pourtant cruciaux pour le référencement.
La solution : un script PHP automatisé
J’ai développé un script qui :
- Identifie automatiquement les posts sans meta-description
- Génère des meta-descriptions cohérentes à partir des champs ACF, répondant aux objectifs globaux de référencement du site
- Traite les contenus par lots pour éviter la surcharge serveur
- S’exécute automatiquement via CRON
- Journalise tous les traitements pour un suivi précis
Architecture technique
Structure des données
Dans notre exemple, chaque formation dispose de :
- Un titre (
post_title
) - Un type de diplôme (
type_de_diplome
– champ ACF) - Une discipline (
discipline
– champ ACF) - Un établissement (
etablissement_simple
– relation ACF vers un autre post)
Le script génère une méta-description selon le pattern : [Type de diplôme] [Discipline] à distance par [Établissement]: [Titre de la formation]
.
Exemple concret : Master Informatique à distance par Université de Nantes: Développement Web et Applications Mobiles
Fonctionnalités clés du script
Traitement par lots limité
function get_formations_without_meta($limit = 50) {
// Récupère seulement 50 formations à la fois
// Évite les timeouts et la surcharge serveur
}
Récupération robuste des champs ACF
function get_acf_field_value($post_id, $field_name) {
// 1. Chercher dans les meta normales
// 2. Chercher avec le préfixe field_ (référence ACF)
// 3. Chercher dans les révisions récentes si nécessaire
// Gère les cas où ACF stocke les données de façon complexe
}
Vérification intelligente des champs requis
if (empty($acf_fields['type_de_diplome']) || empty($acf_fields['discipline']) || empty($acf_fields['etablissement'])) {
log_message("Champs ACF manquants");
continue; // Passe au suivant si données incomplètes
}
Limitation automatique à 155 caractères
if (strlen($meta_description) > 155) {
// Coupe intelligemment sans tronquer les mots
$last_space = strrpos($meta_description, ' ');
$meta_description = substr($meta_description, 0, $last_space) . '…';
}
Journalisation complète
- Nombre total de posts à traiter
- Progression en temps réel
- Statistiques de succès/échecs
- Estimation des prochains traitements
Le script complet
<?php
/**
* Script cron pour auto-générer les méta descriptions manquantes des formations
* Fichier : generate_formation_meta.php
* À placer dans le dossier racine de votre WordPress
*
* Cron à configurer : 0 2 * * 0 (tous les dimanches à 2h du matin)
* Commande : php /path/to/your/site/generate_formation_meta.php
*/
// Inclure WordPress
require_once(dirname(__FILE__) . '/wp-config.php');
require_once(dirname(__FILE__) . '/wp-load.php');
// Configuration
define('LOG_FILE', dirname(__FILE__) . '/formation_meta_log.txt');
define('MAX_EXECUTION_TIME', 300); // 5 minutes max
set_time_limit(MAX_EXECUTION_TIME);
/**
* Fonction de logging
*/
function log_message($message) {
$timestamp = date('[Y-m-d H:i:s]');
file_put_contents(LOG_FILE, $timestamp . ' ' . $message . PHP_EOL, FILE_APPEND | LOCK_EX);
echo $timestamp . ' ' . $message . PHP_EOL;
}
/**
* Récupère les formations sans méta description Yoast (limité à 50)
*/
function get_formations_without_meta($limit = 50) {
global $wpdb;
$query = "
SELECT p.ID, p.post_title
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON (p.ID = pm.post_id AND pm.meta_key = '_yoast_wpseo_metadesc')
WHERE p.post_type = 'formation'
AND p.post_status = 'publish'
AND (pm.meta_value IS NULL OR pm.meta_value = '')
ORDER BY p.post_date DESC
LIMIT %d
";
return $wpdb->get_results($wpdb->prepare($query, $limit));
}
/**
* Compte le nombre total de formations sans méta description
*/
function count_formations_without_meta() {
global $wpdb;
$query = "
SELECT COUNT(*)
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm ON (p.ID = pm.post_id AND pm.meta_key = '_yoast_wpseo_metadesc')
WHERE p.post_type = 'formation'
AND p.post_status = 'publish'
AND (pm.meta_value IS NULL OR pm.meta_value = '')
";
return $wpdb->get_var($query);
}
/**
* Fonction pour chercher un champ ACF dans différents endroits
*/
function get_acf_field_value($post_id, $field_name) {
global $wpdb;
// 1. Chercher dans les meta normales
$value = $wpdb->get_var($wpdb->prepare(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s",
$post_id, $field_name
));
if (!empty($value)) {
return $value;
}
// 2. Chercher avec le préfixe field_ (référence ACF)
$field_key = $wpdb->get_var($wpdb->prepare(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s",
$post_id, '_' . $field_name
));
if (!empty($field_key)) {
// Utiliser la clé pour récupérer la vraie valeur
$value = $wpdb->get_var($wpdb->prepare(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s",
$post_id, $field_key
));
if (!empty($value)) {
return $value;
}
}
// 3. Chercher dans les révisions récentes
$revision_ids = $wpdb->get_col($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'revision' ORDER BY post_date DESC LIMIT 3",
$post_id
));
foreach ($revision_ids as $revision_id) {
$value = $wpdb->get_var($wpdb->prepare(
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s",
$revision_id, $field_name
));
if (!empty($value) && $value !== 'field_' && !preg_match('/^a:\d+:/', $value)) {
return $value;
}
}
return null;
}
/**
* Récupère les champs ACF pour une formation (avec gestion des révisions ACF)
*/
function get_formation_acf_fields($post_id) {
// Récupérer type_de_diplome
$type_diplome = get_acf_field_value($post_id, 'type_de_diplome');
// Récupérer discipline
$discipline = get_acf_field_value($post_id, 'discipline');
// Récupérer l'ID de l'établissement via etablissement_simple
$etablissement_id = get_acf_field_value($post_id, 'etablissement_simple');
// Si pas trouvé, essayer aussi 'etablissement' au cas où
if (empty($etablissement_id)) {
$etablissement_raw = get_acf_field_value($post_id, 'etablissement');
// Si c'est une donnée sérialisée, essayer de l'extraire
if (!empty($etablissement_raw) && (is_string($etablissement_raw) && preg_match('/s:\d+:"(\d+)"/', $etablissement_raw, $matches))) {
$etablissement_id = $matches[1];
} elseif (is_numeric($etablissement_raw)) {
$etablissement_id = $etablissement_raw;
}
}
// Récupérer le nom de l'établissement à partir de son ID
$etablissement_nom = '';
if (!empty($etablissement_id) && is_numeric($etablissement_id)) {
global $wpdb;
$etablissement_nom = $wpdb->get_var($wpdb->prepare(
"SELECT post_title FROM {$wpdb->posts} WHERE ID = %d AND post_status = 'publish'",
$etablissement_id
));
}
return [
'type_de_diplome' => $type_diplome,
'discipline' => $discipline,
'etablissement' => $etablissement_nom,
'etablissement_id' => $etablissement_id,
'etablissement_raw' => isset($etablissement_raw) ? $etablissement_raw : ''
];
}
/**
* Génère la méta description pour une formation
*/
function generate_meta_description($post_id, $post_title, $acf_fields) {
$type_diplome = $acf_fields['type_de_diplome'];
$discipline = $acf_fields['discipline'];
$etablissement = $acf_fields['etablissement'];
// Vérifier que les champs nécessaires sont présents
if (empty($type_diplome) || empty($discipline) || empty($etablissement)) {
return false;
}
// Nettoyer le titre (enlever les caractères spéciaux, html, etc.)
$titre_clean = wp_strip_all_tags($post_title);
$titre_clean = html_entity_decode($titre_clean, ENT_QUOTES, 'UTF-8');
// Générer la méta description avec le nouveau template
$meta_description = sprintf(
'%s %s à distance par %s: %s',
trim($type_diplome),
trim($discipline),
trim($etablissement),
trim($titre_clean)
);
// Limiter à 155 caractères
if (strlen($meta_description) > 155) {
// Couper intelligemment (éviter de couper au milieu d'un mot)
$meta_description = substr($meta_description, 0, 152);
$last_space = strrpos($meta_description, ' ');
if ($last_space > 130) { // Si on a un espace pas trop loin
$meta_description = substr($meta_description, 0, $last_space);
}
$meta_description .= '...';
}
return $meta_description;
}
/**
* Sauvegarde la méta description dans Yoast
*/
function save_meta_description($post_id, $meta_description) {
global $wpdb;
// Insérer ou mettre à jour la méta description Yoast
$result = $wpdb->query($wpdb->prepare(
"INSERT INTO {$wpdb->postmeta} (post_id, meta_key, meta_value)
VALUES (%d, '_yoast_wpseo_metadesc', %s)
ON DUPLICATE KEY UPDATE meta_value = %s",
$post_id,
$meta_description,
$meta_description
));
return $result !== false;
}
/**
* Fonction principale
*/
function main() {
log_message("=== DÉBUT DU TRAITEMENT DES FORMATIONS ===");
// Compter le nombre total de formations sans méta description
$total_remaining = count_formations_without_meta();
log_message("Nombre total de formations sans méta description: {$total_remaining}");
// Récupérer les 50 premières formations sans méta description
$formations = get_formations_without_meta(50);
if (empty($formations)) {
log_message("Aucune formation sans méta description trouvée.");
log_message("✅ Toutes les formations ont maintenant une méta description !");
return;
}
$batch_size = count($formations);
log_message("Traitement d'un lot de {$batch_size} formations (reste {$total_remaining} au total)");
$processed = 0;
$success = 0;
$errors = 0;
foreach ($formations as $formation) {
$processed++;
log_message("[{$processed}/{$batch_size}] Traitement formation ID: {$formation->ID} - '{$formation->post_title}'");
// Récupérer les champs ACF
$acf_fields = get_formation_acf_fields($formation->ID);
// Vérifier si les champs requis sont présents
if (empty($acf_fields['type_de_diplome']) || empty($acf_fields['discipline']) || empty($acf_fields['etablissement'])) {
log_message(" ⚠️ Champs ACF manquants (type_de_diplome: '{$acf_fields['type_de_diplome']}', discipline: '{$acf_fields['discipline']}', etablissement: '{$acf_fields['etablissement']}', etablissement_id: '{$acf_fields['etablissement_id']}', raw: '{$acf_fields['etablissement_raw']}')");
$errors++;
continue;
}
// Générer la méta description
$meta_description = generate_meta_description(
$formation->ID,
$formation->post_title,
$acf_fields
);
if ($meta_description === false) {
log_message(" ❌ Impossible de générer la méta description");
$errors++;
continue;
}
// Sauvegarder
if (save_meta_description($formation->ID, $meta_description)) {
log_message(" ✅ Méta description générée: '{$meta_description}'");
$success++;
} else {
log_message(" ❌ Erreur lors de la sauvegarde");
$errors++;
}
// Pause pour éviter de surcharger le serveur
usleep(200000); // 0.2 seconde
}
$remaining_after = $total_remaining - $success;
log_message("=== RÉSUMÉ DU LOT ===");
log_message("Formations traitées dans ce lot: {$processed}");
log_message("Succès: {$success}");
log_message("Erreurs: {$errors}");
log_message("Formations restantes à traiter: {$remaining_after}");
if ($remaining_after > 0) {
log_message("📅 Le prochain cron traitera {$remaining_after} formations supplémentaires");
} else {
log_message("🎉 Toutes les formations ont maintenant une méta description !");
}
log_message("=== FIN DU TRAITEMENT ===");
}
// Vérifier que le script est exécuté en ligne de commande ou via cron
if (php_sapi_name() === 'cli' || !isset($_SERVER['HTTP_HOST'])) {
main();
} else {
// Sécurité : empêcher l'exécution via navigateur
http_response_code(403);
die('Accès interdit. Ce script doit être exécuté via cron ou ligne de commande.');
}
?>
Installation et configuration
1. Placement du fichier
- Placez le script
generate_formation_meta.php
à la racine de votre WordPress - Assurez-vous que le fichier a les bonnes permissions (644)
2. Adaptation du script
Modifiez les éléments suivants selon vos besoins :
Type de post personnalisé
WHERE p.post_type = 'formation' // Remplacez 'formation' par votre CPT
Champs ACF
// Adaptez selon vos noms de champs
'type_de_diplome' => $type_diplome,
'discipline' => $discipline,
'etablissement_simple' => $etablissement_id,
Pattern de meta-description
$meta_description = sprintf(
'%s %s à distance par %s: %s', // Modifiez ce pattern
trim($type_diplome),
trim($discipline),
trim($etablissement),
trim($titre_clean)
);
Configuration du CRON
Ajout dans la crontab serveur
# Tous les dimanches à 2h du matin
0 2 * * 0 php /path/to/your/site/generate_formation_meta.php
# Ou tous les jours à 3h du matin pour un traitement plus fréquent
0 3 * * * php /path/to/your/site/generate_formation_meta.php
Avantages de cette approche
✅ Scalabilité
- Traitement par lots configurable
- Pas de risque de timeout
- Adapté aux gros volumes de contenu
✅ Gestion avancée des champs ACF
- Fonction robuste
get_acf_field_value()
qui cherche dans plusieurs emplacements - Gestion des références ACF et des données sérialisées
- Recherche dans les révisions si nécessaire
- Compatible avec les complexités de stockage ACF
✅ Robustesse
- Vérification des données avant traitement
- Gestion des erreurs et cas complexes
- Journalisation complète des opérations
- Debugging avancé avec logs détaillés
✅ SEO optimisé
- Méta-descriptions cohérentes et informatives
- Pattern enrichi avec type + discipline + établissement
- Respect de la limite des 155 caractères
- Troncature intelligente des textes
✅ Maintenance facile
- Logs détaillés pour le suivi et debug
- Statistiques de progression complètes
- Sécurité (exécution CLI uniquement)
- Informations de diagnostic pour chaque échec
Personnalisation du nombre d’éléments par lot
// Pour traiter plus ou moins d'éléments selon votre serveur
$formations = get_formations_without_meta(100); // 100 au lieu de 50
Adaptation à d’autres types de contenu
Le script peut facilement être adapté pour :
- Des produits e-commerce avec caractéristiques techniques
- Des événements avec date et lieu
- Des articles avec catégorie et auteur
- Tout contenu structuré avec des champs personnalisés
Surveillance des performances
Surveillez le fichier de log pour :
- Identifier les contenus avec des données manquantes
- Optimiser la fréquence d’exécution
- Détecter les éventuels problèmes de performance
Conclusion
Cette solution automatise complètement la génération des méta-descriptions pour les sites WordPress utilisant ACF, particulièrement utile pour :
- Les sites collaboratifs avec de nombreux contributeurs
- Les catalogues de produits ou services
- Les annuaires et bases de données
- Tout site nécessitant une cohérence SEO à grande échelle
L’approche par CRON garantit un traitement régulier sans intervention manuelle, tout en préservant les performances du site et en offrant une traçabilité complète des opérations.
Besoin d’aide pour implémenter cette solution sur votre site WordPress ? N’hésitez pas à me contacter pour un accompagnement technique personnalisé.