Nous avons couvert les bases Ecriture d’un plugin Wordpress, 1e partie : les bases et les éléments importants Ecriture d’un plugin Wordpress, 2e partie : la structure d’un plugin Twitter de l’écriture d’un plugin dans les articles précédent. Cette fois-ci, nous allons aborder quelques techniques avancées en reprenant notre menu CSS3 (Un menu entièrement HTML et CSS, 2e partie).
Écrire un plugin Wordpress ne se limite pas à incorporer son code PHP dans un cadre qui permette de l’exécuter sous Wordpress. L’auteur qui désirerait offrir publiquement son plugin doit également proposer un certain nombre de fonctionnalités, ne serait-ce que pour faciliter la vie de l’utilisateur : fonctions de désinstallation, réinitialisation des valeurs, préservation de l’intégrité des templates, etc. Nous allons voir quelques unes de ces fonctions avancées dans le reste de cet article. Nous allons donc revoir certaines choses que j’ai déjà expliquées dans les articles précédents. Je ne réexpliquerais pas ces points et je vous renvoie à la lecture de ces articles.
Créons un dossier Menu dans le dossier plugin de notre installation Wordpress. Ce dossier va recevoir tous nos fichiers, et en premier Menu.css, qui n’est autre que la feuille de style déjà utilisée lors de l’article sur les Menus CSS3. Le fichier Menu.php, lui, accueille l’essentiel de notre plugin.
- Un plugin, c’est la classe !
En consultant les fichiers disponibles dans l’archive joint à la fin de cet article, vous remarquerez une première différence par rapport à ce que nous avons déjà vu, avec les déclarations suivantes :
if(!class_exists("PagesMenu")) {
class PagesMenu {
Nous allons en effet encapsulé le code du plugin dans une classe, après avoir vérifié grâce à la fonction class_exists que la classe n’a pas encore été définie dans WordPress.
Le premier avantage de l’encapsulation dans une classe est de nous permettre de choisir n’importe quel nom de variable ou de fonction sans risquer un seul conflit avec WordPress ou les autres plugins déjà présents grâce à la séparation apportée par la mise en classe. Le seul conflit possible est le nom de la classe, mais nous réglons cela grâce à function_exists : si une autre classe PageMenu existe déjà, nous cédons la place. Le second avantage est évidemment que cela nous permet d’utiliser les possibilités de programmation objet. On pourra par exemple définir plusieurs classes dans le même plugin et activer seulement ou conditionnellement une partie de ces classes, réutiliser du code grâce à l’héritage et l’encapsulation, utiliser le polymorphisme, etc. Je laisse libre cours à votre imagination.
La définition de la classe ne suffit pas à en faire un code exécutable, et il nous faut donc l’instancier, c’est-à-dire créer un objet de cette classe. Pour cela, j’utilise le code suivant :
if(class_exists("PagesMenu")) { $pages_menu = new PagesMenu; } if(isset($pages_menu )) { //etc…
Après avoir vérifié que la classe PagesMenu existe bien, je crée une instance de PagesMenu appelée $pages_menu. Je vérifie que la variable $pages_menu n’existe pas déjà, et ensuite je lance le code qui exécute le plugin. L’important est donc d’utiliser un nom unique pour la classe et l’instance de la classe afin d’éviter tout conflit, alors que sans l’utilisation de la classe, on serait obligé de faire des noms à rallonge pour toutes nos fonctions et variables.
- Propriété et constructeur
Les tableaux $default_options et $options contiennent les propriétés, c’est-à-dire les variables propres à notre plugin. Dans notre cas, les éléments qui varient sont les couleurs des dégradés du menu en état normal et en état de survol, le rayon des bordures arrondies ainsi que le nombre de sous-menu que le menu va afficher. Le premier contient les valeurs par défaut, et le second les valeurs définies par l’utilisateur.
Comme toute classe qui se respecte, PagesMenu dispose d’un constructeur qui sert à effectuer un ensemble d’opérations à chaque fois qu’un objet de cette classe est instancié. Dans notre cas, le constructeur va servir à afficher les dégradés et autres paramètres définis par l’utilisateur.
function __construct() { $options = get_option('pages_menu_plugin_settings'); $temp_options = $this->default_options ; if(!empty($options)) { foreach($options as $key => $value) { if(!empty($value)) $temp_options[$key] = $options[$key]; } } $this->options = $temp_options; update_option('pages_menu_plugin_settings', $temp_options); }
J’utilise d’abord la fonction get_options de WordPress afin de récupérer les options de notre plugin depuis la base de données, puis teste si $options est vide ou pas. Si elle est vide, cela signifie que notre plugin vient d’être installé. Je vais donc ajouter les valeurs par défaut dans la base de données; sinon, je remplace les options par défaut avec les options stockées dans la base de données. Si une partie seulement des options est définie dans la base de données, j’utilise les valeurs par défaut des valeurs non définies.
Cela nous permet d’assigner d’abord des dégradés, des arrondis et une profondeur à notre menu dès sa première apparition, et ensuite d’autoriser l’utilisateur à modifier ces valeurs (et de nombreux autres paramètres si nous le voulons).
Nous avons déjà vu les principes de la création d’un panneau d’administration, de l’affichage du plugin, des options et des actions et filtres. Je ne vais donc pas revenir là-dessus, juste apporter quelques compléments.
Quelques techniques avancées
- la méthode displayPageMenuHTML() se sert de la fonction wp_list_pages de Wordpress pour afficher le menu. wp_list_pages retourne les noms et titres des pages du blog sous forme de liste, en fonction d’options données en arguments. Cela permet par exemple d’indiquer la profondeur de sous-pages à afficher, mais également d’exclure certaines pages, etc. On peut aussi imaginer offrir à l’utilisateur le choix entre afficher un menu des pages ou un menu des catégories. Dans ce dernier cas, la fonction wp_list_categories sera utilisée.
- menuOptions() effectue la « magie » en ce qui concerne le traitement automatique des options. Plus besoin de filtrer $_POST ou de vérifier les nonces, elle fait tout cela en arrière plan grâce à register_setting. Mais le plus important ici est le callback array($this, 'reset'). Un callback est un nom de fonction que l’on passe en paramètre à une autre fonction afin qu’elle soit exécutée sur les données de la fonction appellante. Dans notre cas, le callback appelle la méthode reset() de la classe PagesMenu. La méthode reset() vérifie d’abord l’existence de l’option « reset » dans la requête envoyée par le formulaire d’administration. Si tel est le cas, elle remplace les valeurs actuelles par les valeurs par défaut contenues dans $this->default_options. Une fois le traitement effectué, notre callback retourne les options à register_setting afin que celle-ci poursuive le traitement (ici, l’enregistrement dans la base de données).
- array($this …. et array($pages_menu … : vous verrez que j’utilise beaucoup ces expressions dans le code, par ex dans:
add_action('wp_print_styles', array($pages_menu,"displayPageMenuCSS"),1);
add_action("admin_menu", array($pages_menu, "addMenuAdmin"),1);
Nous avons vu ce qu’était qu’un callback : un nom de fonction à exécuter. Dans le cas présent, je désire plutôt exécuter des méthodes de l’objet $pages_menu. PHP me permet de les appeler en utilisant la syntaxe array(nom de l object, nom de la méthode) (cf. call_user_func_array que WordPress utilise pour les exécuter). Il faut bien sûr que la fonction soit accessible, c’est-à-dire public, et non protected ou private.
Il se peut que vous voyez une syntaxe utilisant une référence vers l’objet dans le code d’autres plugins ou sur les tutoriaux présents sur le web :
add_action("admin_menu", array(&$pages_menu, "addMenuAdmin"),1);
Le passage par référence de $pages_menu (&$pages_menu) était nécessaire dans PHP4 afin d’utiliser l’objet courant et ne pas créer un nouvel objet. Depuis PHP5, cette obligation a disparu car les objets sont quasiment passés par référence. Vous pouvez utiliser cette ancienne syntaxe si vous désirez une compatibilité avec PHP4.
- La méthode addCustomCSS() permet d’insérer les styles personnalisés de l’utilisateur situés dans le fichier Menu.css.php.
- Affichage du menu dans WordPress
Maintenant, je vais vous montrer comment créer votre propre action dans Wordpress, et je vais l’illustrer avec l’affichage du menu. Dans les articles précédents, j’ai montré comment afficher un plugin sous forme de widget à placer dans une sidebar ; ou encore à l’aide des hooks (filtres et actions) que propose WordPress. Mais si je désire afficher le menu juste avant la Boucle (the loop) et sur toutes les pages, le meilleur moyen est de l’appeler directement depuis le template header.php. Le contenu de header.php change selon les templates, mais dans tous les cas, ce template est appelé avant la Boucle. Il suffit donc d’insérer l’appel à la toute fin de header.php.
<?php if(is_callable(array("PagesMenu", "add_my_menu"))) PagesMenu::add_my_menu(); ?>
Vous êtes sans doute déjà familier des plugins qui demandent l’ajout d’une petite fonction dans un template. Ici, cette fonction est add_my_menu(). Notez que j’aurais pu faire en sorte d’utiliser seulement add_my_menu(), sans les autres codes qui l’entourent. Il me suffisait de mettre add_my_menu en tant que simple fonction dans mon plugin (au lieu d’une méthode statique). Beaucoup de plugins choisissent cette voie, mais cela pose deux problèmes. Le premier est un problème de conflit : je n’ai plus alors qu’à espérer qu’aucun autre plugin n’utilise ce même nom de fonction. Le deuxième est qu’il ne faut pas oublier que la ligne restera dans le template même si l’utilisateur désinstalle mon plugin, et je ne dois pas présupposer que l’utilisateur va également supprimer la ligne du template. La fonction désormais inconnue va alors totalement casser Wordpress et le site affichera un beau message d’erreur. De quoi avoir plusieurs messages d’utilisateurs mécontents sur le forum de support de mon plugin, ce qui n’est pas une très bonne pub !
Je prends donc mes précautions en vérifiant d’abord que la méthode statique add_my_menu de la classe PagesMenu est exécutable (grâce à la fonction PHP is_callable). Si pour le plugin est désactivé ou a été désinstallé, l’appel ne sera jamais exécuté et aucun message d’erreur ne s’affichera.
La méthode add_my_menu appelle à son tour la fonction do_action() de WordPress. do_action() permet d’ajouter sa propre action dans WordPress :
static function add_my_menu() { do_action('add_my_menu'); } add_action('add_my_menu', array($pages_menu, "displayPageMenuHTML"),1);
La fonction statique ajoute donc une nouvelle action nommée add_my_menu, et cette action est utilisée avec add_action, qui lance la méthode displayPageMenyHTML, qui à son tour affiche le menu.
Et voilà, le menu est maintenant fonctionnel (même si vous pouvez largement étendre les fonctionnalités proposées), il dispose de tout ce qu’il faut pour faire un plugin acceptable. Toutes ? Non, pas toutes ! Que se passe-t-il si l’utilisateur décide qu’il n’aime plus mon plugin et désire le supprimer ?
Désinstallation de plugin
Il faut d’abord comprendre la différence majeure entre désactivation et désinstallation, et malheureusement, beaucoup d’auteurs de plugins, et même ceux les plus célèbres, ignorent (ou font exprès d’ignorer) cette différence.
La désactivation arrête le lien entre le plugin et WordPress, et dès lors le plugin n’est plus utilisable. Mais les fichiers du plugin sont toujours là, de même que les options qui ont été stockées dans la base de données. De cette manière, si l’utilisateur désire réactiver le plugin, il retrouvera immédiatement tous ses réglages personnalisés.
La désinstallation/suppression est un geste définitif. L’utilisateur ne désire plus utiliser le plugin, et par conséquent tous les fichiers du plugin seront effacés. Malheureusement, WordPress ne peut pas (pour l’instant ?) prévoir de mécanisme pour automatiquement supprimer les options dans la base de données, qui pour certains plugins sont composées de plus d’une dizaine de tables ! Ces données restent donc dans la base (comme les logiciels qui en se désinstallant « oublient » leurs scories dans le dossier Windows, Application Data et le Registre !) si rien n’est fait pour les nettoyer. Outre le fait que la présence de ces données inutiles entraîne une fragmentation de la base, ça fait tout simplement désordre de voir qu’il reste des traces d’un plugin qu’on a testé il y a plusieurs mois dans une base de données.
La solution est pourtant simple : créez un fichier uninstall.php dans le dossier de votre plugin, et WordPress l’exécutera lorsque l’utilisateur supprime le plugin. Dans notre cas, ce fichier contient juste le code suivant :
if( !defined( 'ABSPATH') & !defined('WP_UNINSTALL_PLUGIN') ) exit(); delete_option('pages_menu_plugin_settings');
D’abord, je vérifie l’existence de deux constantes. L’existence d’ABSPATH me permet de déterminer qu’uninstall.php est bien exécuté dans Wordpress et que quelqu’un ne tente pas de l’ouvrir directement ; WP_UNINSTALL_PLUGIN est uniquement défini lorsque la fonction uninstall_plugin($plugin) de Wordpress est exécutée, c’est-à-dire que mon plugin est bien en train d’être désinstallé (sinon, un autre plugin sous wordpress pourrait exécuter uninstall.php !). Enfin, je supprimer les options de mon plugin de la table. Ici, c’est fait très simplement avec delete_option, mais si vous avez accédé directement à la table, soit avec $wpdb, soit avec MySQL (mais là, il faudrait que vous ayez une très bonne raison car c’est inutile dans 99% des cas), c’est ici que vous supprimerez vos données et tables et restaurerez vos modifications.
Le plugin au complet : menu.zip
Voilà donc quelques unes des techniques avancées dans le développement de plugins WordPress. Avec ces techniques, et les bases présentées dans les articles précédents, vous êtes sur le bon chemin pour développer et distribuer vos propres plugins.