
🛠 Comment créer un plugin de widget pour WordPress : un guide étape par étape
💡 Comment créer un plugin de widget WordPress
- Créez un dossier
my-widget-plugindanswp-content/plugins/et un fichiermy-widget-plugin.phpavec l'en-tête du plugin, puis activez-le dans le panneau d'administration - Déclarez une classe qui étend
WP_Widgetet surchargez les méthodes__construct(),form(),update()etwidget() - Enregistrez le widget avec la fonction
register_widget()sur le hookwidgets_initafin que WordPress puisse le voir dans la liste - Dans la méthode
form(), affichez les champs de paramètres; dansupdate(), nettoyez et sauvegardez les données viasanitize_text_field() - Dans la méthode
widget(), produisez le contenu en front-end, en échappant les valeurs avecesc_html()etwp_kses_post()
Étape 1: créer le squelette du plugin
Un widget dans WordPress n'est pas juste une ligne dans functions.php. Si vous voulez que les paramètres persistent, que le formulaire d'administration fonctionne et que le widget lui-même survive à la prochaine mise à jour du thème, vous devez le packager en tant que plugin. Cela isole le code et rend le widget indépendant de tout changement de thème.
Commencez par un dossier vide. Allez dans wp-content/plugins/ et créez un répertoire appelé my-widget-plugin. À l'intérieur, créez un fichier nommé my-widget-plugin.php. C'est le fichier que WordPress lira en premier lors de l'activation. Ouvrez le fichier et ajoutez l'en-tête standard de plugin:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */
Sauvegardez le fichier. Allez maintenant dans le panneau d'administration WordPress → Extensions. Si le plugin apparaît dans la liste, le squelette est prêt. Cliquez sur «Activer». Il ne fait encore rien d'utile; nous avons seulement annoncé son existence. Mais WordPress sait déjà que ce plugin existe et est prêt à exécuter son code. C'est un principe important: l'enregistrement d'abord, la logique ensuite.
Le fonctionnement interne du plugin, les hooks qui se déclenchent à l'activation et la manière dont WordPress localise votre fichier sont tous abordés dans la vidéo ci-dessus. Passons maintenant à la partie la plus intéressante: la classe du widget.
Étape 2: enregistrer le widget via WP_Widget
WordPress fournit une classe intégrée appelée WP_Widget; elle fait partie du cœur depuis la version 2.8 et reste la base de tout widget personnalisé. Vous n'avez pas besoin d'écrire la logique de sauvegarde, la génération des champs ou l'enregistrement à partir de zéro: il suffit d'étendre la classe et de surcharger quatre méthodes.
Ajoutez ce code à votre my-widget-plugin.php juste après l'en-tête du plugin, avant le ?> de fermeture:
1 // Widget class 2 class My_Custom_Widget extends WP_Widget { 3 4 public function __construct() { 5 parent::__construct( 6 'my_custom_widget', 7 __( 'My Custom Widget', 'text_domain' ), 8 array( 9 'customize_selective_refresh' => true, 10 ) 11 ); 12 } 13 14 public function form( $instance ) { 15 /* ... admin form ... */ 16 } 17 18 public function update( $new_instance, $old_instance ) { 19 /* ... saving settings ... */ 20 } 21 22 public function widget( $args, $instance ) { 23 /* ... frontend output ... */ 24 } 25 } 26 27 // Widget registration 28 function my_register_custom_widget() { 29 register_widget( 'My_Custom_Widget' ); 30 } 31 add_action( 'widgets_init', 'my_register_custom_widget' );
Décortiquons ce qui se passe ici. La classe My_Custom_Widget étend WP_Widget, ce qui vous donne des méthodes prêtes à l'emploi comme get_field_id() et get_field_name() pour générer les attributs des champs de formulaire. Dans le constructeur, nous passons trois choses à la classe parente: un identifiant unique de widget (caractères latins minuscules, sans espaces, my_custom_widget), son nom lisible par l'humain (la fonction __() le rend traduisible) et un tableau d'options. Le paramètre customize_selective_refresh => true permet au widget de se rafraîchir dans le personnalisateur sans recharger toute la page, un petit détail qui évite bien des frustrations lors de la configuration.
La fonction my_register_custom_widget() appelle register_widget() sur le hook widgets_init. C'est ainsi que WordPress prend connaissance de votre widget. Sans cette ligne, rien n'apparaîtra dans le panneau d'administration.
Remplissons maintenant les méthodes form(), update() et widget() avec une vraie logique.
Étape 3: créer le formulaire du widget dans le panneau d'administration
Le formulaire est ce que l'administrateur voit lorsqu'il fait glisser le widget dans une barre latérale. Il se compose de champs: des champs de texte, des listes déroulantes, des cases à cocher. Chaque champ doit pouvoir sauvegarder sa valeur et afficher la valeur actuelle lorsqu'il est rouvert.
3.1. La fonction form() et les champs de saisie
Ajoutez ce code à la méthode form() de votre classe. Il crée cinq champs: un titre, un champ de texte, une zone de texte, une case à cocher et une liste déroulante.
1 public function form( $instance ) { 2 3 $defaults = array( 4 'title' => '', 5 'text' => '', 6 'textarea' => '', 7 'checkbox' => '', 8 'select' => '', 9 ); 10 11 $args = wp_parse_args( (array) $instance, $defaults ); 12 $title = $args['title']; 13 $text = $args['text']; 14 $textarea = $args['textarea']; 15 $checkbox = $args['checkbox']; 16 $select = $args['select']; 17 ?> 18 19 <p> 20 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 21 <?php _e( 'Widget Title', 'text_domain' ); ?> 22 </label> 23 <input class="widefat" 24 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 25 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 26 type="text" 27 value="<?php echo esc_attr( $title ); ?>" /> 28 </p> 29 30 <p> 31 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 32 <?php _e( 'Text:', 'text_domain' ); ?> 33 </label> 34 <input class="widefat" 35 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 36 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 37 type="text" 38 value="<?php echo esc_attr( $text ); ?>" /> 39 </p> 40 41 <p> 42 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 43 <?php _e( 'Textarea:', 'text_domain' ); ?> 44 </label> 45 <textarea class="widefat" 46 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 47 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 48 echo wp_kses_post( $textarea ); 49 ?></textarea> 50 </p> 51 52 <p> 53 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 54 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 55 type="checkbox" 56 value="1" 57 <?php checked( '1', $checkbox ); ?> /> 58 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 59 <?php _e( 'Show additional block', 'text_domain' ); ?> 60 </label> 61 </p> 62 63 <p> 64 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 65 <?php _e( 'Display variant', 'text_domain' ); ?> 66 </label> 67 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 68 id="<?php echo $this->get_field_id( 'select' ); ?>" 69 class="widefat"> 70 <?php 71 $options = array( 72 '' => __( '— Select —', 'text_domain' ), 73 'option_1' => __( 'Option 1', 'text_domain' ), 74 'option_2' => __( 'Option 2', 'text_domain' ), 75 'option_3' => __( 'Option 3', 'text_domain' ), 76 ); 77 foreach ( $options as $key => $name ) { 78 printf( 79 '<option value="%s" %s>%s</option>', 80 esc_attr( $key ), 81 selected( $select, $key, false ), 82 esc_html( $name ) 83 ); 84 } 85 ?> 86 </select> 87 </p> 88 89 <?php }
Prêtez attention à deux choses. Premièrement, nous avons remplacé la fonction obsolète extract() par un accès direct au tableau. extract() a été exclu depuis longtemps des standards de codage de WordPress; elle crée des variables nommées d'après les clés du tableau, ce qui est dangereux et rend le débogage plus difficile. Deuxièmement, chaque valeur de sortie est passée par esc_attr(), wp_kses_post() ou esc_html(). Ce n'est pas de la paranoïa: les données de la base de données peuvent provenir de n'importe où, et l'assainissement est obligatoire.
3.2. La fonction update() pour la sauvegarde
La méthode update() est appelée lorsque le bouton «Enregistrer» est cliqué dans le formulaire du widget. Son rôle est de valider chaque champ et de retourner un tableau assaini pour l'écriture dans la base de données.
1 public function update( $new_instance, $old_instance ) { 2 $instance = $old_instance; 3 4 $instance['title'] = isset( $new_instance['title'] ) 5 ? sanitize_text_field( $new_instance['title'] ) : ''; 6 $instance['text'] = isset( $new_instance['text'] ) 7 ? sanitize_text_field( $new_instance['text'] ) : ''; 8 $instance['textarea'] = isset( $new_instance['textarea'] ) 9 ? wp_kses_post( $new_instance['textarea'] ) : ''; 10 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 11 $instance['select'] = isset( $new_instance['select'] ) 12 ? sanitize_text_field( $new_instance['select'] ) : ''; 13 14 return $instance; 15 }
Ici, nous utilisons sanitize_text_field() au lieu de wp_strip_all_tags() car elle normalise également les espaces blancs, supprime les caractères de contrôle invisibles et convertit la chaîne en UTF-8 sûr. C'est un nettoyage plus approfondi. wp_strip_all_tags() laisse un texte «brut» avec tous les artefacts d'espaces blancs intacts. Pour les champs de formulaire textuels, choisissez toujours sanitize_text_field().
La zone de texte conserve wp_kses_post(): elle autorise le HTML de base (liens, texte en gras, listes) mais supprime les scripts. La case à cocher retourne 1 ou false, ce qui est lisible et sans ambiguïté dans la base de données.
Étape 4: afficher le widget en front-end
La fonction widget() est ce que le visiteur voit. Elle reçoit deux paramètres: $args (l'enveloppe du widget, les balises avant et après le titre et la barre latérale) et $instance (les paramètres sauvegardés pour cette instance particulière).
1 public function widget( $args, $instance ) { 2 3 $title = isset( $instance['title'] ) 4 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 5 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 6 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 7 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 8 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 9 10 echo $args['before_widget']; 11 12 echo '<div class="widget-text wp_widget_plugin_box">'; 13 14 if ( $title ) { 15 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 16 } 17 18 if ( $text ) { 19 echo '<p>' . esc_html( $text ) . '</p>'; 20 } 21 22 if ( $textarea ) { 23 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 24 } 25 26 if ( $select ) { 27 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 28 } 29 30 if ( $checkbox ) { 31 echo '<p class="widget-checkbox-result">' . esc_html__( 'Additional block activated', 'text_domain' ) . '</p>'; 32 } 33 34 echo '</div>'; 35 36 echo $args['after_widget']; 37 }
Le point clé ici: nous avons remplacé extract( $args ) par un accès direct via $args['before_widget']. La raison est la même: extract() est obsolète et dangereuse. Nous avons également enveloppé la sortie dans esc_html() partout où du texte brut est attendu (le titre, la chaîne de texte, la valeur de la liste déroulante). La zone de texte est rendue via wp_kses_post(), donc si l'administrateur a inséré un lien ou du texte en gras, ils seront préservés.
La classe CSS wp_widget_plugin_box vous permet de styliser le bloc depuis la feuille de style du thème. N'hésitez pas à la renommer, assurez-vous simplement que la classe est unique et n'entre pas en conflit avec les classes du thème.
Code complet du plugin
Rassemblons le tout. Voici le fichier complet my-widget-plugin.php, prêt à être copié et activé:
1 <?php 2 /* 3 Plugin Name: My Widget Plugin 4 Plugin URI: https://www.wpexplorer.com/create-widget-plugin-wordpress/ 5 Description: Adds a customizable widget with text, textarea, checkbox, and dropdown. 6 Version: 1.0 7 Author: AJ Clarke 8 Author URI: https://www.wpexplorer.com/ 9 License: GPL2 10 */ 11 12 class My_Custom_Widget extends WP_Widget { 13 14 public function __construct() { 15 parent::__construct( 16 'my_custom_widget', 17 __( 'My Custom Widget', 'text_domain' ), 18 array( 'customize_selective_refresh' => true ) 19 ); 20 } 21 22 public function form( $instance ) { 23 $defaults = array( 24 'title' => '', 'text' => '', 'textarea' => '', 25 'checkbox' => '', 'select' => '' 26 ); 27 $args = wp_parse_args( (array) $instance, $defaults ); 28 $title = $args['title']; 29 $text = $args['text']; 30 $textarea = $args['textarea']; 31 $checkbox = $args['checkbox']; 32 $select = $args['select']; 33 ?> 34 <p> 35 <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"> 36 <?php _e( 'Widget Title', 'text_domain' ); ?> 37 </label> 38 <input class="widefat" 39 id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" 40 name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" 41 type="text" value="<?php echo esc_attr( $title ); ?>" /> 42 </p> 43 <p> 44 <label for="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>"> 45 <?php _e( 'Text:', 'text_domain' ); ?> 46 </label> 47 <input class="widefat" 48 id="<?php echo esc_attr( $this->get_field_id( 'text' ) ); ?>" 49 name="<?php echo esc_attr( $this->get_field_name( 'text' ) ); ?>" 50 type="text" value="<?php echo esc_attr( $text ); ?>" /> 51 </p> 52 <p> 53 <label for="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>"> 54 <?php _e( 'Textarea:', 'text_domain' ); ?> 55 </label> 56 <textarea class="widefat" 57 id="<?php echo esc_attr( $this->get_field_id( 'textarea' ) ); ?>" 58 name="<?php echo esc_attr( $this->get_field_name( 'textarea' ) ); ?>"><?php 59 echo wp_kses_post( $textarea ); 60 ?></textarea> 61 </p> 62 <p> 63 <input id="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>" 64 name="<?php echo esc_attr( $this->get_field_name( 'checkbox' ) ); ?>" 65 type="checkbox" value="1" <?php checked( '1', $checkbox ); ?> /> 66 <label for="<?php echo esc_attr( $this->get_field_id( 'checkbox' ) ); ?>"> 67 <?php _e( 'Show additional block', 'text_domain' ); ?> 68 </label> 69 </p> 70 <p> 71 <label for="<?php echo $this->get_field_id( 'select' ); ?>"> 72 <?php _e( 'Display variant', 'text_domain' ); ?> 73 </label> 74 <select name="<?php echo $this->get_field_name( 'select' ); ?>" 75 id="<?php echo $this->get_field_id( 'select' ); ?>" class="widefat"> 76 <?php 77 $options = array( 78 '' => __( '— Select —', 'text_domain' ), 79 'option_1' => __( 'Option 1', 'text_domain' ), 80 'option_2' => __( 'Option 2', 'text_domain' ), 81 'option_3' => __( 'Option 3', 'text_domain' ), 82 ); 83 foreach ( $options as $key => $name ) { 84 printf( 85 '<option value="%s" %s>%s</option>', 86 esc_attr( $key ), 87 selected( $select, $key, false ), 88 esc_html( $name ) 89 ); 90 } 91 ?> 92 </select> 93 </p> 94 <?php 95 } 96 97 public function update( $new_instance, $old_instance ) { 98 $instance = $old_instance; 99 $instance['title'] = isset( $new_instance['title'] ) 100 ? sanitize_text_field( $new_instance['title'] ) : ''; 101 $instance['text'] = isset( $new_instance['text'] ) 102 ? sanitize_text_field( $new_instance['text'] ) : ''; 103 $instance['textarea'] = isset( $new_instance['textarea'] ) 104 ? wp_kses_post( $new_instance['textarea'] ) : ''; 105 $instance['checkbox'] = isset( $new_instance['checkbox'] ) ? 1 : false; 106 $instance['select'] = isset( $new_instance['select'] ) 107 ? sanitize_text_field( $new_instance['select'] ) : ''; 108 return $instance; 109 } 110 111 public function widget( $args, $instance ) { 112 $title = isset( $instance['title'] ) 113 ? apply_filters( 'widget_title', $instance['title'] ) : ''; 114 $text = isset( $instance['text'] ) ? $instance['text'] : ''; 115 $textarea = isset( $instance['textarea'] ) ? $instance['textarea'] : ''; 116 $select = isset( $instance['select'] ) ? $instance['select'] : ''; 117 $checkbox = ! empty( $instance['checkbox'] ) ? $instance['checkbox'] : false; 118 119 echo $args['before_widget']; 120 echo '<div class="widget-text wp_widget_plugin_box">'; 121 122 if ( $title ) { 123 echo $args['before_title'] . esc_html( $title ) . $args['after_title']; 124 } 125 if ( $text ) { 126 echo '<p>' . esc_html( $text ) . '</p>'; 127 } 128 if ( $textarea ) { 129 echo '<div class="widget-textarea">' . wp_kses_post( $textarea ) . '</div>'; 130 } 131 if ( $select ) { 132 echo '<p class="widget-select">' . esc_html( $select ) . '</p>'; 133 } 134 if ( $checkbox ) { 135 echo '<p class="widget-checkbox-result">' 136 . esc_html__( 'Additional block activated', 'text_domain' ) . '</p>'; 137 } 138 139 echo '</div>'; 140 echo $args['after_widget']; 141 } 142 } 143 144 function my_register_custom_widget() { 145 register_widget( 'My_Custom_Widget' ); 146 } 147 add_action( 'widgets_init', 'my_register_custom_widget' );
Placez ce fichier dans wp-content/plugins/my-widget-plugin/, activez le plugin et faites glisser le widget dans n'importe quelle barre latérale via Apparence → Widgets. Remplissez les champs, enregistrez et vérifiez votre site.
Le code final est également disponible sur GitHub: wpexplorer/my-widget-plugin, où vous pouvez le comparer avec la version originale de 2017 et voir exactement ce que nous avons changé.
⁉️🤔 Foire aux questions
Pourquoi mettre un widget dans un plugin alors que l'on peut simplement ajouter du code dans le functions.php du thème?
Le code dans functions.php est lié au thème actif. Changez de thème, et le widget disparaît. Un plugin fonctionne indépendamment du thème. De plus, un plugin peut être activé sélectivement sur différents sites, alors que le code du thème ne le peut pas. Si le widget répond à un besoin métier (par exemple, afficher un formulaire d'abonnement avec une mise en page spécifique), il a sa place dans un plugin.
Pourquoi sanitize_text_field() est-elle meilleure que wp_strip_all_tags()?
sanitize_text_field()ne se contente pas de supprimer les balises HTML; elle normalise également les espaces blancs, supprime les caractères de contrôle invisibles et convertit la chaîne en UTF-8. C'est un nettoyage plus approfondi.wp_strip_all_tags()laisse un texte «brut» avec tous les artefacts d'espaces blancs intacts. Pour les champs de formulaire textuels, choisissez toujourssanitize_text_field().
Pourquoi avez-vous remplacé extract() par un accès direct au tableau?
La fonction
extract()a été exclue des standards de codage de WordPress depuis la version 4.3. Elle crée des variables nommées d'après les clés du tableau dans la portée locale; si une clé correspond à une variable existante, vous allez l'écraser et vous retrouver avec un bug difficile à trouver. L'accès direct comme$args['before_widget']est lisible et sûr.
Dois-je prendre en charge les widgets de blocs (Gutenberg)?
Le
WP_Widgetclassique fonctionne avec les barres latérales basées sur les blocs grâce à la rétrocompatibilité: WordPress l'enveloppe automatiquement dans un bloc de widget hérité. Cela est suffisant pour la plupart des scénarios. Si vous souhaitez créer de véritables blocs natifs, consultez le Manuel de l'éditeur de blocs, qui dispose d'une API distincte. Mais commencer parWP_Widgetest plus simple: le code est plus court, le débogage est plus rapide et cela fonctionne sur toutes les versions de WordPress sans plugins de compatibilité supplémentaires.
Comment déboguer un widget qui n'apparaît pas dans le panneau d'administration?
Vérifiez trois choses. Premièrement: le hook
widgets_inits'est-il déclenché? Ajoutezerror_log( 'Widget registered' )à la fonction d'enregistrement et vérifiez les logs. Deuxièmement: y a-t-il une erreur fatale dans le constructeur? ActivezWP_DEBUGdans wp-config.php. Troisièmement: le nom de la classe dansregister_widget()correspond-il au nom de la classe qui étendWP_Widget? Une seule faute de frappe, et le widget n'apparaîtra pas.
Et ensuite: du modèle à votre propre widget
Nous avons construit un plugin fonctionnel avec cinq types de champs. Ce n'est pas un produit fini; c'est un squelette. Prenez-le comme base et adaptez-le à vos besoins. Besoin d'un widget de formulaire d'abonnement? Remplacez les champs de texte par des champs email et nom, et ajoutez un appel API à un service de mailing dans widget(). Besoin d'un bloc avec des promotions et des bannières? Chargez des images via le téléverseur de médias et affichez-les dans votre balisage. La mécanique est toujours la même: form() dessine les champs, update() les sauvegarde, widget() produit le rendu.
Commencez petit: copiez le code complet ci-dessus, activez-le sur un site de test et expérimentez avec les paramètres. Une fois que vous avez compris comment les données circulent du formulaire vers le front-end, commencez à ajouter vos propres champs. Et si le widget plante avec un écran blanc, revenez à l'étape 2 et vérifiez le constructeur.



