WordOps 3.22 : j’ai testé le gestionnaire LEMP WordPress automatisé
J'avais toujours repoussé WordOps. Quand on gère déjà nginx à la main, l'idée d'un outil…
Le système de hooks (crochets) de WordPress est l’épine dorsale de son architecture extensible. C’est ce qui permet à des milliers de plugins et thèmes de coexister et d’étendre WordPress sans modifier son code source. Comprendre les hooks est essentiel pour tout développeur WordPress intermédiaire.
Dans ce guide approfondi, nous explorerons la différence entre actions et filtres, leur fonctionnement interne, et comment les utiliser efficacement dans vos projets professionnels.
WordPress implémente le pattern de conception Observer (aussi appelé Publish-Subscribe) :
┌─────────────┐ Fire Event ┌──────────────┐
│ WordPress │ ──────────────────────> │ Hook System │
│ Core │ │ (wp-hooks) │
└─────────────┘ └──────────────┘
│
│ Notify
▼
┌────────────────────────────────┐
│ Registered Callbacks │
│ (Plugins, Themes, Functions) │
└────────────────────────────────┘
Simplifié : structure interne du système de hooks
/
// Tableau global stockant tous les hooks
global $wpfilter;
/
Structure de $wpfilter:
$wpfilter = array(
'hookname' => WPHook Object {
callbacks = array(
10 => array( // Priorité
'uniqueid1' => array(
'function' => 'callbackfunction',
'acceptedargs' => 1
),
'uniqueid2' => array(
'function' => array($object, 'method'),
'acceptedargs' => 2
)
),
20 => array( ... )
)
}
)
/
1. addaction() ou addfilter()
↓
Stockage dans $wpfilter global
↓
doaction() ou applyfilters()
↓
Tri par priorité
↓
Exécution séquentielle des callbacks
↓
Retour du résultat (filtres) ou fin (actions)
Les actions permettent d’exécuter du code à des moments spécifiques sans modifier de données.
Exemple conceptuel d'une action
/
// WordPress fait ceci internalement:
function doactionexample() {
// Code WordPress...
doaction('monactionpersonnalisee', $arg1, $arg2);
// Suite du code WordPress...
}
// Vous vous "accrochez" avec:
addaction('monactionpersonnalisee', 'mafonctioncallback', 10, 2);
function mafonctioncallback($arg1, $arg2) {
// Votre code s'exécute ici
// Pas besoin de retourner quoi que ce soit
echo "Action déclenchée !";
}
Les filtres permettent de modifier des données avant qu’elles soient utilisées ou affichées.
Exemple conceptuel d'un filtre
/
// WordPress fait ceci internalement:
function applyfiltersexample() {
$valeur = "Texte original";
// Permet aux plugins de modifier $valeur
$valeur = applyfilters('monfiltrepersonnalise', $valeur, $arg2);
// Utilise $valeur (potentiellement modifiée)
echo $valeur;
}
// Vous vous "accrochez" avec:
addfilter('monfiltrepersonnalise', 'moncallbackfiltre', 10, 2);
function moncallbackfiltre($valeur, $arg2) {
// Modifier la valeur
$valeur = strtoupper($valeur);
// TOUJOURS retourner la valeur
return $valeur;
}
| Caractéristique | Actions | Filtres |
|---|---|---|
| But | Exécuter du code | Modifier des données |
| Valeur de retour | Aucune (void) | Obligatoire |
| Fonction d’ajout | addaction() |
addfilter() |
| Fonction de déclenchement | doaction() |
applyfilters() |
| Exemple d’usage | Envoyer un email, enregistrer un log | Modifier un titre, ajouter du CSS |
Hook au démarrage de WordPress
/
addaction('init', 'yawcinitialization');
function yawcinitialization() {
// Enregistrer des post types personnalisés
registerposttype('livre', array(
'labels' => array(
'name' => 'Livres',
'singularname' => 'Livre'
),
'public' => true,
'hasarchive' => true,
'supports' => array('title', 'editor', 'thumbnail'),
'rewrite' => array('slug' => 'livres'),
));
// Enregistrer des taxonomies
registertaxonomy('genre', 'livre', array(
'labels' => array(
'name' => 'Genres',
'singularname' => 'Genre'
),
'hierarchical' => true,
'rewrite' => array('slug' => 'genre'),
));
}
/
Hook lors de l'activation d'un thème
/
addaction('aftersetuptheme', 'yawcthemesetup');
function yawcthemesetup() {
// Support des fonctionnalités du thème
addthemesupport('post-thumbnails');
addthemesupport('custom-logo');
addthemesupport('html5', array(
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
));
// Enregistrer les menus
registernavmenus(array(
'primary' => ('Menu Principal', 'yawc'),
'footer' => ('Menu Pied de page', 'yawc'),
));
// Tailles d'images personnalisées
addimagesize('portrait', 400, 600, true);
addimagesize('paysage', 800, 400, true);
}
/
Hook lors du chargement des scripts
/
addaction('wpenqueuescripts', 'yawcenqueueassets');
function yawcenqueueassets() {
// Charger les styles
wpenqueuestyle(
'yawc-main-style',
gettemplatedirectoryuri() . '/assets/css/main.css',
array(),
'1.0.0'
);
// Charger les scripts
wpenqueuescript(
'yawc-main-script',
gettemplatedirectoryuri() . '/assets/js/main.js',
array('jquery'),
'1.0.0',
true
);
// Passer des données PHP au JavaScript
wplocalizescript('yawc-main-script', 'yawcData', array(
'ajaxUrl' => adminurl('admin-ajax.php'),
'nonce' => wpcreatenonce('yawc-nonce'),
'siteUrl' => homeurl(),
));
}
Ajouter du contenu avant et après les articles
/
addaction('thecontent', 'yawcaddcontentbefore', 1);
addaction('thecontent', 'yawcaddcontentafter', 999);
function yawcaddcontentbefore($content) {
if (issingle() && !isadmin()) {
$before = '';
return $before . $content;
}
return $content;
}
function yawcaddcontentafter($content) {
if (issingle() && !isadmin()) {
$after = '';
$after .= '#### Partager cet article';
$after .= yawcgetsharebuttons();
$after .= '';
return $content . $after;
}
return $content;
}
function yawcgetreadingtime() {
$content = getpostfield('postcontent', gettheID());
$wordcount = strwordcount(striptags($content));
$readingtime = ceil($wordcount / 200); // 200 mots par minute
return $readingtime;
}
/
Modifier le footer
/
addaction('wpfooter', 'yawccustomfootercontent');
function yawccustomfootercontent() {
?>
Ajouter une page d'administration personnalisée
/
addaction('adminmenu', 'yawcaddadminmenu');
function yawcaddadminmenu() {
addmenupage(
'Configuration YAWC', // Titre de la page
'YAWC Settings', // Titre du menu
'manageoptions', // Capacité requise
'yawc-settings', // Slug
'yawcsettingspagecallback', // Callback
'dashicons-admin-generic', // Icône
30 // Position
);
addsubmenupage(
'yawc-settings',
'Statistiques',
'Stats',
'manageoptions',
'yawc-stats',
'yawcstatspagecallback'
);
}
function yawcsettingspagecallback() {
?>
# html(getadminpagetitle()); ?>
Enregistrer les paramètres
/
addaction('admininit', 'yawcregistersettings');
function yawcregistersettings() {
registersetting('yawcsettingsgroup', 'yawcapikey', array(
'type' => 'string',
'sanitizecallback' => 'sanitizetextfield',
'default' => '',
));
registersetting('yawcsettingsgroup', 'yawcenablecache', array(
'type' => 'boolean',
'sanitizecallback' => 'restsanitizeboolean',
'default' => false,
));
addsettingssection(
'yawcmainsection',
'Paramètres principaux',
'yawcsectioncallback',
'yawc-settings'
);
addsettingsfield(
'yawcapikey',
'Clé API',
'yawcapikeycallback',
'yawc-settings',
'yawcmainsection'
);
addsettingsfield(
'yawcenablecache',
'Activer le cache',
'yawcenablecachecallback',
'yawc-settings',
'yawcmainsection'
);
}
function yawcsectioncallback() {
echo 'Configurez les paramètres de YAWC.
';
}
function yawcapikeycallback() {
$value = getoption('yawcapikey', '');
printf(
'',
escattr($value)
);
}
function yawcenablecachecallback() {
$value = getoption('yawcenablecache', false);
printf(
'',
checked(1, $value, false)
);
}
Actions lors de la sauvegarde d'un post
/
addaction('savepost', 'yawcsavepostmeta', 10, 3);
function yawcsavepostmeta($postid, $post, $update) {
// Vérifications de sécurité
if (defined('DOINGAUTOSAVE') && DOINGAUTOSAVE) {
return;
}
if (!currentusercan('editpost', $postid)) {
return;
}
if (wpispostrevision($postid)) {
return;
}
// Sauvegarder des métadonnées personnalisées
if (isset($POST['yawccustomfield'])) {
updatepostmeta(
$postid,
'yawccustomfield',
sanitizetextfield($POST['yawccustomfield'])
);
}
// Générer automatiquement un slug SEO-friendly
if ($post->posttype === 'livre') {
$seotitle = sanitizetitle($post->posttitle);
updatepostmeta($postid, 'yawcseoslug', $seotitle);
}
// Logger l'activité
yawclogpostupdate($postid, $post, $update);
}
/
Action lors de la suppression d'un post
/
addaction('beforedeletepost', 'yawccleanuppostdata');
function yawccleanuppostdata($postid) {
// Nettoyer les données associées
global $wpdb;
// Supprimer les métadonnées personnalisées
$wpdb->delete(
$wpdb->postmeta,
array('postid' => $postid, 'metakey' => 'yawccustomdata'),
array('%d', '%s')
);
// Supprimer les fichiers uploadés associés
$attachments = getposts(array(
'posttype' => 'attachment',
'postparent' => $postid,
'numberposts' => -1,
));
foreach ($attachments as $attachment) {
wpdeleteattachment($attachment->ID, true);
}
// Logger la suppression
errorlog(sprintf('Post %d deleted at %s', $postid, currenttime('mysql')));
}
Modifier le titre des articles
/
addfilter('thetitle', 'yawcmodifytitle', 10, 2);
function yawcmodifytitle($title, $postid) {
// Ajouter un indicateur "Nouveau" aux articles récents
if (ismainquery() && !isadmin()) {
$post = getpost($postid);
if ($post && $post->posttype === 'post') {
$daysold = (time() - strtotime($post->postdate)) / DAYINSECONDS;
if ($daysold <= 7) {
$title .= ' Nouveau';
}
}
}
return $title;
}
/
Modifier l'extrait
/
addfilter('theexcerpt', 'yawccustomexcerpt');
function yawccustomexcerpt($excerpt) {
// Ajouter un lien "Lire la suite" personnalisé
if (hasexcerpt()) {
$excerpt = rtrim($excerpt);
$excerpt .= sprintf(
' Continuer la lecture →',
getpermalink()
);
}
return $excerpt;
}
/
Modifier la longueur de l'extrait
/
addfilter('excerptlength', 'yawcexcerptlength');
function yawcexcerptlength($length) {
return isfrontpage() ? 20 : 55;
}
/
Personnaliser le texte "Lire la suite"
/
addfilter('excerptmore', 'yawcexcerptmore');
function yawcexcerptmore($more) {
return '...';
}
Modifier la requête principale
/
addfilter('pregetposts', 'yawcmodifymainquery');
function yawcmodifymainquery($query) {
// Ne modifier que la requête principale sur le front-end
if (!isadmin() && $query->ismainquery()) {
// Exclure une catégorie de la page d'accueil
if ($query->ishome()) {
$query->set('cat', '-5'); // Exclure la catégorie ID 5
}
// Changer le nombre d'articles par page pour les archives
if ($query->isarchive()) {
$query->set('postsperpage', 12);
}
// Trier par titre sur les pages de catégorie
if ($query->iscategory()) {
$query->set('orderby', 'title');
$query->set('order', 'ASC');
}
// Recherche personnalisée
if ($query->issearch()) {
// Rechercher dans plusieurs post types
$query->set('posttype', array('post', 'page', 'livre'));
// Exclure les posts privés des résultats de recherche
$query->set('poststatus', 'publish');
}
}
return $query;
}
/
Modifier les arguments de WPQuery
/
addfilter('postswhere', 'yawcfilterpostswhere', 10, 2);
function yawcfilterpostswhere($where, $query) {
global $wpdb;
// Ajouter un filtre de date personnalisé
if ($customdate = $query->get('yawccustomdate')) {
$where .= $wpdb->prepare(
" AND $wpdb->posts.postdate >= %s",
date('Y-m-d', strtotime($customdate))
);
}
return $where;
}
// Utilisation:
// $query = new WPQuery(array('yawccustomdate' => '-30 days'));
Modifier les capacités utilisateur
/
addfilter('userhascap', 'yawcmodifyusercapabilities', 10, 4);
function yawcmodifyusercapabilities($allcaps, $caps, $args, $user) {
// Permettre aux éditeurs de gérer les options
if (isset($allcaps['editor']) && $allcaps['editor']) {
$allcaps['manageoptions'] = true;
}
// Restriction basée sur l'heure
$currenthour = (int) currenttime('H');
if ($currenthour >= 18 || $currenthour < 8) {
// Retirer certaines capacités hors des heures de bureau
unset($allcaps['deletepublishedposts']);
}
return $allcaps;
}
/
Personnaliser le message de connexion
/
addfilter('loginmessage', 'yawccustomloginmessage');
function yawccustomloginmessage($message) {
if (empty($message)) {
return '';
}
return $message;
}
/
Modifier l'URL de redirection après connexion
/
addfilter('loginredirect', 'yawcloginredirect', 10, 3);
function yawcloginredirect($redirectto, $request, $user) {
if (!isset($user->roles) || !isarray($user->roles)) {
return $redirectto;
}
// Rediriger les admins vers le dashboard
if (inarray('administrator', $user->roles)) {
return adminurl();
}
// Rediriger les autres utilisateurs vers leur profil
return adminurl('profile.php');
}
Personnaliser les colonnes de la liste des posts
/
addfilter('managepostscolumns', 'yawcaddcustomcolumns');
addfilter('managepostscustomcolumn', 'yawccustomcolumncontent', 10, 2);
function yawcaddcustomcolumns($columns) {
// Réorganiser les colonnes
$newcolumns = array();
foreach ($columns as $key => $value) {
$newcolumns[$key] = $value;
// Ajouter après le titre
if ($key === 'title') {
$newcolumns['wordcount'] = 'Nombre de mots';
$newcolumns['readingtime'] = 'Temps de lecture';
}
}
return $newcolumns;
}
function yawccustomcolumncontent($column, $postid) {
switch ($column) {
case 'wordcount':
$content = getpostfield('postcontent', $postid);
$wordcount = strwordcount(striptags($content));
echo numberformati18n($wordcount);
break;
case 'readingtime':
$content = getpostfield('postcontent', $postid);
$wordcount = strwordcount(striptags($content));
$minutes = ceil($wordcount / 200);
printf('%d min', $minutes);
break;
}
}
/
Rendre les colonnes personnalisées triables
/
addfilter('manageedit-postsortablecolumns', 'yawcsortablecolumns');
function yawcsortablecolumns($columns) {
$columns['wordcount'] = 'wordcount';
return $columns;
}
/
Personnaliser le footer de l'admin
/
addfilter('adminfootertext', 'yawccustomadminfooter');
function yawccustomadminfooter($text) {
return sprintf(
'Développé avec ❤️ par Votre Entreprise | WordPress %s',
'https://votresite.com',
getbloginfo('version')
);
}
Plugin principal avec hooks personnalisés
/
class YAWCNewsletter {
public function construct() {
addaction('adminpostnewslettersubscribe', array($this, 'handlesubscription'));
}
public function handlesubscription() {
// Vérifications de sécurité
if (!isset($POST['newsletternonce']) ||
!wpverifynonce($POST['newsletternonce'], 'newslettersubscribe')) {
wpdie('Erreur de sécurité');
}
$email = sanitizeemail($POST['email']);
if (!isemail($email)) {
wpdie('Email invalide');
}
// Hook AVANT l'inscription
doaction('yawcbeforenewslettersubscribe', $email);
// Logique d'inscription
$result = $this->addsubscriber($email);
if ($result) {
// Hook APRÈS l'inscription réussie
doaction('yawcafternewslettersubscribe', $email, $result);
wpredirect(addqueryarg('subscribed', '1', wpgetreferer()));
} else {
// Hook en cas d'erreur
doaction('yawcnewslettersubscribefailed', $email);
wpredirect(addqueryarg('subscribeerror', '1', wpgetreferer()));
}
exit;
}
private function addsubscriber($email) {
global $wpdb;
$tablename = $wpdb->prefix . 'newslettersubscribers';
$result = $wpdb->insert(
$tablename,
array(
'email' => $email,
'subscribedate' => currenttime('mysql'),
'status' => 'active',
),
array('%s', '%s', '%s')
);
return $result !== false;
}
}
new YAWCNewsletter();
/
Utilisation des hooks personnalisés par d'autres plugins/thèmes
/
// Envoyer un email de bienvenue
addaction('yawcafternewslettersubscribe', 'yawcsendwelcomeemail');
function yawcsendwelcomeemail($email, $subscriberid) {
$subject = 'Bienvenue dans notre newsletter !';
$message = sprintf(
"Bonjour,nnMerci de vous être inscrit à notre newsletter.nnVous recevrez désormais nos dernières actualités.nnCordialement,nL'équipe"
);
wpmail($email, $subject, $message);
}
// Logger les tentatives échouées
addaction('yawcnewslettersubscribefailed', 'yawclogfailedsubscription');
function yawclogfailedsubscription($email) {
errorlog(sprintf(
'[Newsletter] Failed subscription attempt for email: %s at %s',
$email,
currenttime('mysql')
));
}
// Ajouter à une liste MailChimp
addaction('yawcafternewslettersubscribe', 'yawcsynctomailchimp', 20);
function yawcsynctomailchimp($email) {
// Intégration avec l'API MailChimp
$apikey = getoption('yawcmailchimpapikey');
$listid = getoption('yawcmailchimplistid');
if (empty($apikey) || empty($listid)) {
return;
}
// Appel API MailChimp...
}
Système de templating avec filtres personnalisés
/
class YAWCTemplateEngine {
public static function render($templatename, $data = array()) {
// Permettre la modification des données avant le rendu
$data = applyfilters('yawctemplatedata', $data, $templatename);
$data = applyfilters("yawctemplatedata{$templatename}", $data);
// Obtenir le chemin du template
$templatepath = self::gettemplatepath($templatename);
// Permettre la modification du chemin
$templatepath = applyfilters('yawctemplatepath', $templatepath, $templatename);
if (!fileexists($templatepath)) {
return '';
}
// Extraire les variables
extract($data);
// Commencer le buffer
obstart();
// Hook avant le rendu
doaction('yawcbeforetemplaterender', $templatename, $data);
// Inclure le template
include $templatepath;
// Hook après le rendu
doaction('yawcaftertemplaterender', $templatename, $data);
// Récupérer le contenu
$output = obgetclean();
// Permettre la modification du output final
$output = applyfilters('yawctemplateoutput', $output, $templatename, $data);
return $output;
}
private static function gettemplatepath($templatename) {
return gettemplatedirectory() . '/templates/' . $templatename . '.php';
}
}
/
Utilisation des filtres personnalisés
/
// Ajouter des données globales à tous les templates
addfilter('yawctemplatedata', 'yawcaddglobaltemplatedata');
function yawcaddglobaltemplatedata($data) {
$data['sitename'] = getbloginfo('name');
$data['currentuser'] = wpgetcurrentuser();
$data['isloggedin'] = isuserloggedin();
return $data;
}
// Modifier les données pour un template spécifique
addfilter('yawctemplatedataproduct-card', 'yawcenrichproductdata');
function yawcenrichproductdata($data) {
if (isset($data['productid'])) {
$data['productreviews'] = yawcgetproductreviews($data['productid']);
$data['relatedproducts'] = yawcgetrelatedproducts($data['productid']);
}
return $data;
}
// Minifier le HTML en sortie
addfilter('yawctemplateoutput', 'yawcminifyhtml', 999);
function yawcminifyhtml($output) {
if (!isadmin() && !currentusercan('editposts')) {
$output = pregreplace('/s+/', ' ', $output);
$output = pregreplace('/>s+', '><', $output);
}
return $output;
}
Plugin: YAWC Events Manager
Démonstration d'une architecture complète avec hooks
/
class YAWCEventsManager {
private static $instance = null;
public static function getinstance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function construct() {
$this->inithooks();
}
private function inithooks() {
addaction('init', array($this, 'registerposttype'));
addaction('savepostevent', array($this, 'saveeventmeta'), 10, 2);
}
public function registerposttype() {
$labels = applyfilters('yawceventposttypelabels', array(
'name' => 'Événements',
'singularname' => 'Événement',
'addnew' => 'Ajouter un événement',
'addnewitem' => 'Ajouter un nouvel événement',
'edititem' => 'Modifier l'événement',
));
$args = array(
'labels' => $labels,
'public' => true,
'hasarchive' => true,
'supports' => array('title', 'editor', 'thumbnail'),
'rewrite' => array('slug' => 'evenements'),
);
$args = applyfilters('yawceventposttypeargs', $args);
registerposttype('event', $args);
}
public function saveeventmeta($postid, $post) {
// Hook avant la sauvegarde
doaction('yawcbeforesaveeventmeta', $postid, $post);
$metafields = array('eventdate', 'eventlocation', 'eventprice');
foreach ($metafields as $field) {
if (isset($POST[$field])) {
$value = $POST[$field];
// Permettre le filtrage de chaque valeur
$value = applyfilters("yawceventmeta{$field}", $value, $postid);
$value = applyfilters('yawceventmetavalue', $value, $field, $postid);
updatepostmeta($postid, '' . $field, $value);
}
}
// Hook après la sauvegarde
doaction('yawcaftersaveeventmeta', $postid, $post);
}
/
Obtenir les événements à venir
/
public function getupcomingevents($limit = 10) {
$args = array(
'posttype' => 'event',
'postsperpage' => $limit,
'metakey' => 'eventdate',
'orderby' => 'metavalue',
'order' => 'ASC',
'metaquery' => array(
array(
'key' => 'eventdate',
'value' => date('Y-m-d'),
'compare' => '>=',
'type' => 'DATE',
),
),
);
// Permettre la modification de la requête
$args = applyfilters('yawcupcomingeventsqueryargs', $args);
$query = new WPQuery($args);
// Permettre la modification des résultats
$events = applyfilters('yawcupcomingevents', $query->posts, $query);
return $events;
}
}
// Initialiser le plugin
YAWCEventsManager::getinstance();
/
Exemples d'utilisation des hooks par des extensions
/
// Extension 1: Ajouter un champ personnalisé
addfilter('yawceventposttypeargs', 'yawcaddeventcustomfields');
function yawcaddeventcustomfields($args) {
$args['supports'][] = 'custom-fields';
return $args;
}
// Extension 2: Valider le prix
addfilter('yawceventmetaeventprice', 'yawcvalidateeventprice', 10, 2);
function yawcvalidateeventprice($value, $postid) {
$value = floatval($value);
return max(0, $value); // S'assurer que le prix n'est pas négatif
}
// Extension 3: Envoyer une notification
addaction('yawcaftersaveeventmeta', 'yawcnotifynewevent', 10, 2);
function yawcnotifynewevent($postid, $post) {
if ($post->poststatus === 'publish' && !getpostmeta($postid, 'notificationsent', true)) {
// Envoyer des notifications aux abonnés
yawcsendeventnotifications($postid);
// Marquer comme envoyé
updatepostmeta($postid, 'notificationsent', true);
}
}
La priorité détermine l'ordre d'exécution
Priorité par défaut: 10
Plus le nombre est bas, plus tôt la fonction s'exécute
/
// Ces fonctions s'exécuteront dans cet ordre:
addaction('init', 'fonctionpriorite5', 5); // 1er
addaction('init', 'fonctionpriorite10', 10); // 2ème (défaut)
addaction('init', 'fonctionpriorite10bis', 10); // 3ème (même priorité, ordre d'ajout)
addaction('init', 'fonctionpriorite15', 15); // 4ème
addaction('init', 'fonctionpriorite100', 100); // 5ème (dernier)
function fonctionpriorite5() {
echo "1. Priorité 5n";
}
function fonctionpriorite10() {
echo "2. Priorité 10 (première)n";
}
function fonctionpriorite10bis() {
echo "3. Priorité 10 (deuxième)n";
}
function fonctionpriorite15() {
echo "4. Priorité 15n";
}
function fonctionpriorite100() {
echo "5. Priorité 100n";
}
Exemple: Chaîne de filtres avec priorités
/
// Filtre de base
addfilter('thetitle', 'yawcuppercasetitle', 10);
function yawcuppercasetitle($title) {
return strtoupper($title);
}
// Ce filtre s'exécute AVANT (priorité plus basse)
addfilter('thetitle', 'yawcaddprefix', 5);
function yawcaddprefix($title) {
return '[ARTICLE] ' . $title;
}
// Ce filtre s'exécute APRÈS (priorité plus haute)
addfilter('thetitle', 'yawcaddsuffix', 20);
function yawcaddsuffix($title) {
return $title . ' ✓';
}
// Résultat final: "[ARTICLE] MON TITRE ✓"
// Ordre d'exécution:
// 1. yawcaddprefix (5): "[ARTICLE] Mon titre"
// 2. yawcuppercasetitle (10): "[ARTICLE] MON TITRE"
// 3. yawcaddsuffix (20): "[ARTICLE] MON TITRE ✓"
/
Surcharger un hook existant
/
// Hook d'un plugin tiers (priorité 10)
addfilter('contentfilter', 'otherpluginfunction', 10);
// Pour modifier APRÈS le plugin tiers
addfilter('contentfilter', 'yawcmodifyafter', 20);
// Pour s'exécuter AVANT le plugin tiers
addfilter('contentfilter', 'yawcmodifybefore', 5);
// Pour désactiver complètement le hook du plugin tiers
removefilter('contentfilter', 'otherpluginfunction', 10);
Le 4ème paramètre définit le nombre d'arguments acceptés
/
// Hook WordPress avec plusieurs arguments
doaction('exampleaction', $arg1, $arg2, $arg3);
// Recevoir 1 seul argument (défaut)
addaction('exampleaction', 'fonction1arg', 10, 1);
function fonction1arg($arg1) {
// Accès uniquement à $arg1
}
// Recevoir 2 arguments
addaction('exampleaction', 'fonction2args', 10, 2);
function fonction2args($arg1, $arg2) {
// Accès à $arg1 et $arg2
}
// Recevoir tous les arguments
addaction('exampleaction', 'fonctionallargs', 10, 3);
function fonctionallargs($arg1, $arg2, $arg3) {
// Accès à tous les arguments
}
/
Exemple concret: Modification de requête
/
addaction('pregetposts', 'yawcmodifysearch', 10, 1);
function yawcmodifysearch($query) {
// $query est le seul argument nécessaire
if ($query->issearch() && !isadmin()) {
$query->set('postsperpage', 20);
}
}
/
Exemple: Callback de sauvegarde avec arguments multiples
/
addaction('savepost', 'yawcsavehandler', 10, 3);
function yawcsavehandler($postid, $post, $update) {
// 3 arguments sont disponibles
if ($update) {
// C'est une mise à jour, pas une nouvelle publication
yawclogupdate($postid, $post);
}
}
N'exécuter des hooks que dans certaines conditions
/
// Seulement sur la page d'accueil
addaction('wpfooter', 'yawchomepagefooter');
function yawchomepagefooter() {
if (!isfrontpage()) {
return;
}
echo 'Découvrez nos services !';
}
// Seulement pour les articles d'une certaine catégorie
addfilter('thecontent', 'yawcaddcategorydisclaimer');
function yawcaddcategorydisclaimer($content) {
if (!issingle() || !incategory('tutoriels')) {
return $content;
}
$disclaimer = '';
$disclaimer .= 'Note: Ce tutoriel nécessite des connaissances de base en PHP.';
$disclaimer .= '';
return $disclaimer . $content;
}
// Seulement pour certains rôles utilisateur
addaction('adminnotices', 'yawcadminnoticeforeditors');
function yawcadminnoticeforeditors() {
$user = wpgetcurrentuser();
if (!inarray('editor', $user->roles)) {
return;
}
?>
Rappel: N'oubliez pas de vérifier l'orthographe avant de publier.
action('wpfooter', 'yawcmobiledownloadapp');
function yawcmobiledownloadapp() {
if (!wpismobile()) {
return;
}
?>
Créer des hooks dynamiques basés sur le contexte
/
class YAWCDynamicHooks {
public static function init() {
// Hook générique
addaction('yawcprocessform', array(CLASS, 'process'), 10, 2);
}
public static function process($formid, $data) {
// Hook spécifique au formulaire
doaction("yawcprocessform{$formid}", $data);
// Hook basé sur le type de données
if (isset($data['type'])) {
doaction("yawcprocessformtype{$data['type']}", $formid, $data);
}
// Hook basé sur l'utilisateur
if (isuserloggedin()) {
$user = wpgetcurrentuser();
doaction("yawcprocessformuser{$user->ID}", $formid, $data);
foreach ($user->roles as $role) {
doaction("yawcprocessformrole{$role}", $formid, $data);
}
}
}
}
YAWCDynamicHooks::init();
/
Utilisation des hooks dynamiques
/
// Hook pour un formulaire spécifique
addaction('yawcprocessformcontact', 'yawchandlecontactform');
function yawchandlecontactform($data) {
// Logique spécifique au formulaire de contact
wpmail(getoption('adminemail'), 'Nouveau contact', printr($data, true));
}
// Hook pour un type de données
addaction('yawcprocessformtypenewsletter', 'yawchandlenewslettersignup');
function yawchandlenewslettersignup($formid, $data) {
// Ajouter à la liste de newsletter
yawcaddtonewsletter($data['email']);
}
// Hook pour un rôle spécifique
addaction('yawcprocessformrolesubscriber', 'yawcsubscriberformbonus');
function yawcsubscriberformbonus($formid, $data) {
// Bonus pour les abonnés
yawcaddloyaltypoints(getcurrentuserid(), 10);
}
Hooks spécifiques aux post types personnalisés
/
// WordPress fournit des hooks dynamiques pour chaque post type
addaction('savepostlivre', 'yawcsavebook', 10, 3);
addaction('savepostfilm', 'yawcsavemovie', 10, 3);
function yawcsavebook($postid, $post, $update) {
// Logique spécifique aux livres
if (!$update) {
// Nouveau livre
yawcnotifybookeditors($postid);
}
}
function yawcsavemovie($postid, $post, $update) {
// Logique spécifique aux films
yawcupdateimdbrating($postid);
}
/
Filtres sur les requêtes de taxonomie
/
addaction('creategenre', 'yawcnewgenrecreated', 10, 2);
function yawcnewgenrecreated($termid, $ttid) {
// Un nouveau genre a été créé
yawcrebuildgenrecache();
}
/
Hooks sur les métadonnées
/
addaction('addedpostmeta', 'yawcmetaadded', 10, 4);
function yawcmetaadded($metaid, $postid, $metakey, $metavalue) {
// Réagir à l'ajout de métadonnées spécifiques
if ($metakey === 'featuredbook') {
yawcupdatefeaturedbookslist();
}
}
MAUVAIS: Hook exécuté à chaque fois
/
addaction('thepost', 'yawcbadexpensiveoperation');
function yawcbadexpensiveoperation($post) {
// Cette requête s'exécute pour CHAQUE post dans une boucle !
$relatedposts = getposts(array(
'categoryin' => wpgetpostcategories($post->ID),
'postnotin' => array($post->ID),
'postsperpage' => 5,
));
// Traitement coûteux...
}
/
BON: Hook avec mise en cache
/
addaction('thepost', 'yawcgoodexpensiveoperation');
function yawcgoodexpensiveoperation($post) {
// Vérifier le cache d'abord
$cachekey = 'relatedposts' . $post->ID;
$relatedposts = wpcacheget($cachekey);
if ($relatedposts === false) {
// Seulement si pas en cache
$relatedposts = getposts(array(
'categoryin' => wpgetpostcategories($post->ID),
'postnotin' => array($post->ID),
'postsperpage' => 5,
));
// Mettre en cache pour 1 heure
wpcacheset($cachekey, $relatedposts, '', 3600);
}
// Utiliser $relatedposts...
}
/
MEILLEUR: Hook conditionnel
/
addaction('thepost', 'yawcbestexpensiveoperation');
function yawcbestexpensiveoperation($post) {
// N'exécuter que si nécessaire
if (!issingle() || isadmin()) {
return;
}
// Utiliser un transient pour un cache persistant
$cachekey = 'relatedposts' . $post->ID;
$relatedposts = gettransient($cachekey);
if ($relatedposts === false) {
$relatedposts = getposts(array(
'categoryin' => wpgetpostcategories($post->ID),
'postnotin' => array($post->ID),
'postsperpage' => 5,
'fields' => 'ids', // Récupérer seulement les IDs
'nofoundrows' => true, // Pas besoin de pagination
));
settransient($cachekey, $relatedposts, HOURINSECONDS);
}
}
/
Nettoyer le cache quand nécessaire
/
addaction('savepost', 'yawcclearrelatedpostscache');
function yawcclearrelatedpostscache($postid) {
$cachekey = 'relatedposts' . $postid;
deletetransient($cachekey);
// Nettoyer aussi les caches des posts de la même catégorie
$categories = wpgetpostcategories($postid);
foreach ($categories as $catid) {
deletetransient('categoryposts' . $catid);
}
}
Retirer des hooks quand ils ne sont plus nécessaires
/
class YAWCConditionalHook {
private static $hookadded = false;
public static function maybeaddhook() {
// Ajouter le hook seulement une fois et seulement si nécessaire
if (!self::$hookadded && self::shouldrun()) {
addfilter('thecontent', array(CLASS, 'modifycontent'));
self::$hookadded = true;
}
}
private static function shouldrun() {
return issingle() && incategory('premium');
}
public static function modifycontent($content) {
// Après la première exécution, retirer le hook
removefilter('thecontent', array(CLASS, 'modifycontent'));
return self::addpremiumcontent($content);
}
private static function addpremiumcontent($content) {
return 'Contenu Premium' . $content;
}
}
// Initialiser conditionnellement
if (issingle()) {
YAWCConditionalHook::maybeaddhook();
}
Limiter la fréquence d'exécution d'un hook coûteux
/
class YAWCHookThrottle {
private static $lastrun = array();
/
N'exécuter qu'une fois par période définie
/
public static function throttle($hookname, $callback, $waitseconds = 60) {
$currenttime = time();
if (!isset(self::$lastrun[$hookname]) ||
($currenttime - self::$lastrun[$hookname]) >= $waitseconds) {
calluserfunc($callback);
self::$lastrun[$hookname] = $currenttime;
return true;
}
return false;
}
}
/
Utilisation
/
addaction('savepost', 'yawcrebuildcachethrottled');
function yawcrebuildcachethrottled($postid) {
// Ne rebuild le cache que toutes les 5 minutes maximum
YAWCHookThrottle::throttle('rebuildcache', function() {
yawcrebuildentirecache();
}, 300);
}
/
Debouncing: Attendre que l'activité se calme
/
class YAWCHookDebounce {
private static $timers = array();
public static function debounce($hookname, $callback, $delayseconds = 5) {
// Annuler le timer précédent s'il existe
if (isset(self::$timers[$hookname])) {
wpclearscheduledhook('yawcdebounced' . $hookname);
}
// Programmer une nouvelle exécution
wpschedulesingleevent(
time() + $delayseconds,
'yawcdebounced' . $hookname
);
// Enregistrer le callback
if (!hasaction('yawcdebounced' . $hookname)) {
addaction('yawcdebounced' . $hookname, $callback);
}
self::$timers[$hookname] = time();
}
}
/
Utilisation: N'exécuter qu'après 10 secondes d'inactivité
/
addaction('wpajaxautosave', 'yawcautosavedebounced');
function yawcautosavedebounced() {
YAWCHookDebounce::debounce('autosave', function() {
// Cette fonction ne s'exécutera que 10 secondes après
// la dernière tentative de sauvegarde automatique
yawcperformheavyautosaveoperations();
}, 10);
}
Afficher tous les hooks enregistrés pour un tag donné
/
function yawclisthooks($tag = '') {
global $wpfilter;
if (empty($tag)) {
echo '## Tous les hooks WordPress';
foreach ($wpfilter as $hookname => $hook) {
yawcdisplayhookdetails($hookname, $hook);
}
} else {
if (isset($wpfilter[$tag])) {
yawcdisplayhookdetails($tag, $wpfilter[$tag]);
} else {
echo "Aucun hook trouvé pour: $tag
";
}
}
}
function yawcdisplayhookdetails($hookname, $hook) {
echo "### $hookname";
echo '';
echo 'Priorité Fonction Arguments acceptés ';
foreach ($hook->callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
$functionname = yawcgetcallbackname($callback['function']);
echo '';
echo "$priority ";
echo "$functionname ";
echo "{$callback['acceptedargs']} ";
echo ' ';
}
}
echo '
';
}
function yawcgetcallbackname($callback) {
if (isstring($callback)) {
return $callback;
} elseif (isarray($callback)) {
if (isobject($callback[0])) {
return getclass($callback[0]) . '->' . $callback[1];
} else {
return $callback[0] . '::' . $callback[1];
}
} elseif ($callback instanceof Closure) {
return 'Closure';
} else {
return 'Callback inconnu';
}
}
// Utilisation en mode debug
if (WPDEBUG && currentusercan('manageoptions')) {
addaction('wpfooter', function() {
echo '';
yawclisthooks('wpenqueuescripts');
echo '';
});
}
Logger toutes les exécutions de hooks
/
class YAWCHookLogger {
private static $logs = array();
private static $enabled = false;
public static function enable() {
if (self::$enabled) {
return;
}
self::$enabled = true;
// Intercepter tous les hooks
addfilter('all', array(CLASS, 'loghook'), 1);
}
public static function loghook($tag) {
if (!self::$enabled) {
return $tag;
}
$backtrace = debugbacktrace(DEBUGBACKTRACEIGNOREARGS, 4);
// Déterminer si c'est une action ou un filtre
$type = 'unknown';
foreach ($backtrace as $trace) {
if (isset($trace['function'])) {
if (inarray($trace['function'], array('doaction', 'doactionrefarray'))) {
$type = 'action';
break;
} elseif (inarray($trace['function'], array('applyfilters', 'applyfiltersrefarray'))) {
$type = 'filter';
break;
}
}
}
self::$logs[] = array(
'tag' => $tag,
'type' => $type,
'time' => microtime(true),
'memory' => memorygetusage(),
);
return $tag;
}
public static function getlogs() {
return self::$logs;
}
public static function displaylogs() {
if (empty(self::$logs)) {
echo 'Aucun log disponible.
';
return;
}
$starttime = self::$logs[0]['time'];
$startmemory = self::$logs[0]['memory'];
echo '';
echo '# Tag Type Temps (ms) Mémoire (KB) ';
foreach (self::$logs as $index => $log) {
$timediff = ($log['time'] - $starttime) 1000;
$memorydiff = ($log['memory'] - $startmemory) / 1024;
printf(
'%d %s %s %.2f %.2f ',
$index + 1,
eschtml($log['tag']),
eschtml($log['type']),
$timediff,
$memorydiff
);
}
echo '
';
}
public static function savetofile($filename = 'hook-logs.json') {
$uploaddir = wpuploaddir();
$filepath = $uploaddir['basedir'] . '/' . $filename;
fileputcontents($filepath, jsonencode(self::$logs, JSONPRETTYPRINT));
return $filepath;
}
}
// Activer en mode debug
if (WPDEBUG && isset($GET['loghooks'])) {
YAWCHookLogger::enable();
addaction('shutdown', function() {
$file = YAWCHookLogger::savetofile();
errorlog('Hooks logged to: ' . $file);
});
}
Tracer l'exécution d'un hook particulier / function yawctracehook($tag) { addaction($tag, function() use ($tag) { $args = funcgetargs(); echo ''; echo "#### Hook déclenché: $tag"; if (!empty($args)) { echo ''; printr($args); echo '';
}// Backtrace pour voir d'où vient l'appel
echo 'Backtrace
'; debugprintbacktrace(DEBUGBACKTRACEIGNOREARGS); echo '';
echo '
';
}, 1, 99); // Priorité 1 pour s'exécuter en premier, 99 args max
}// Utilisation
usercan('manageoptions') && isset($GET['trace'])) {
if (current
yawctracehook('savepost');
yawctracehook('wpenqueuescripts');
yawctracehook('thecontent');
}
## 10. Cas d'usage avancés et patterns
### Pattern: Hook ProxyCréer un système de proxy pour intercepter et modifier les hooks / class YAWCHookProxy { private $originaltag; private $proxytag; private $middleware = array(); public function construct($originaltag, $proxytag = null) { $this->originaltag = $originaltag; $this->proxytag = $proxytag ?: $originaltag . 'proxied'; $this->setup(); } private function setup() { // Intercepter le hook original addfilter($this->originaltag, array($this, 'intercept'), 1, 99); } public function intercept() { $args = funcgetargs(); // Exécuter les middlewares foreach ($this->middleware as $middleware) { $args = calluserfuncarray($middleware, $args); if ($args === false) { // Un middleware a arrêté l'exécution return $args[0]; } } // Exécuter le hook proxifié arrayunshift($args, $this->proxytag); return calluserfuncarray('applyfilters', $args); } public function addMiddleware($callback) { $this->middleware[] = $callback; return $this; } } / Utilisation du proxy / $contentproxy = new YAWCHookProxy('thecontent'); // Ajouter un middleware de logging $contentproxy->addMiddleware(function($content) { errorlog('Content length before filters: ' . strlen($content)); return funcgetargs(); }); // Ajouter un middleware de sécurité $contentproxy->addMiddleware(function($content) { // Bloquer si le contenu contient certains mots if (strpos($content, 'BLOCKEDWORD') !== false) { errorlog('Content blocked'); return false; // Arrête l'exécution } return funcgetargs(); }); // Utiliser le hook proxifié addfilter('thecontentproxied', function($content) { return $content . 'Contenu ajouté via proxy
'; });Pattern: Event Dispatcher
Système d'événements avancé basé sur les hooks / class YAWCEventDispatcher { private static $instance = null; private $listeners = array(); public static function instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } / Enregistrer un écouteur d'événement / public function listen($event, $callback, $priority = 10) { if (!isset($this->listeners[$event])) { $this->listeners[$event] = array(); } $this->listeners[$event][] = array( 'callback' => $callback, 'priority' => $priority, ); // Trier par priorité usort($this->listeners[$event], function($a, $b) { return $a['priority'] - $b['priority']; }); return $this; } / Déclencher un événement / public function dispatch($event, $payload = array()) { // Hook WordPress standard doaction("yawcevent{$event}", $payload); // Système d'événements personnalisé if (!isset($this->listeners[$event])) { return null; } $result = null; foreach ($this->listeners[$event] as $listener) { $result = calluserfunc($listener['callback'], $payload, $result); // Permettre l'arrêt de la propagation if ($result === false) { break; } } return $result; } / Écouter un événement une seule fois / public function once($event, $callback, $priority = 10) { $wrapper = function($payload) use ($event, $callback, &$wrapper) { // Retirer l'écouteur après exécution $this->forget($event, $wrapper); return calluserfunc($callback, $payload); }; return $this->listen($event, $wrapper, $priority); } / Retirer un écouteur / public function forget($event, $callback = null) { if ($callback === null) { unset($this->listeners[$event]); return $this; } if (isset($this->listeners[$event])) { $this->listeners[$event] = arrayfilter( $this->listeners[$event], function($listener) use ($callback) { return $listener['callback'] !== $callback; } ); } return $this; } } / Utilisation du Event Dispatcher / $events = YAWCEventDispatcher::instance(); // Écouter un événement personnalisé $events->listen('user.registered', function($payload) { $user = $payload['user']; // Envoyer un email de bienvenue wpmail($user->useremail, 'Bienvenue !', 'Merci de vous être inscrit.'); }); $events->listen('user.registered', function($payload) { // Ajouter des points de fidélité yawcaddloyaltypoints($payload['user']->ID, 100); }, 20); // Écouter une seule fois $events->once('first.purchase', function($payload) { // Bonus uniquement pour le premier achat yawcsendfirstpurchasebonus($payload['userid']); }); // Déclencher l'événement function yawcregisteruser($userdata) { $user = wpinsertuser($userdata); if (!iswperror($user)) { YAWCEventDispatcher::instance()->dispatch('user.registered', array( 'user' => getuserdata($user), 'timestamp' => currenttime('timestamp'), )); } return $user; }Pattern: Hook Chain
Chaîner plusieurs transformations avec validation / class YAWCFilterChain { private $value; private $filters = array(); public function construct($initialvalue) { $this->value = $initialvalue; } / Ajouter un filtre à la chaîne / public function pipe($callback) { $this->filters[] = $callback; return $this; } / Ajouter un filtre conditionnel / public function when($condition, $callback) { if (iscallable($condition)) { $shouldapply = calluserfunc($condition, $this->value); } else { $shouldapply = (bool) $condition; } if ($shouldapply) { $this->filters[] = $callback; } return $this; } / Valider la valeur / public function validate($callback, $errormessage = 'Validation failed') { $this->filters[] = function($value) use ($callback, $errormessage) { if (!calluserfunc($callback, $value)) { throw new Exception($errormessage); } return $value; }; return $this; } / Exécuter la chaîne et retourner le résultat / public function get() { $value = $this->value; try { foreach ($this->filters as $filter) { $value = calluserfunc($filter, $value); } } catch (Exception $e) { errorlog('Filter chain error: ' . $e->getMessage()); return $this->value; // Retourner la valeur originale en cas d'erreur } return $value; } } / Utilisation de la chaîne de filtres / $processedcontent = (new YAWCFilterChain($rawcontent)) ->pipe('trim') ->pipe('striptags') ->validate(function($value) { return strlen($value) > 10; }, 'Content too short') ->pipe(function($content) { return wpautop($content); }) ->when(isuserloggedin(), function($content) { return $content . 'Contenu premium pour membres
'; }) ->pipe(function($content) { return applyfilters('mycustomcontentfilter', $content); }) ->get();Conclusion
Le système de hooks WordPress est l'un des patterns architecturaux les plus puissants et élégants du CMS. Maîtriser actions et filtres vous permet de:
Le système de hooks est au cœur de l'écosystème WordPress. En les maîtrisant, vous rejoignez la communauté de développeurs qui construit collaborativement l'un des CMS les plus extensibles au monde.
Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.