10 erreurs à éviter absolument dans vos plugins WordPress

Bien que l’article date de 2009, il n’y a rien strictement rien à en ôter, sauf peut-être des considérations sur les fonctions PHP5, devenues obsolètes maintenant que WordPress a définitivement abandonné PHP4 (mais qui redeviendront d’actualité lors du passage à PHP6). L’article se concentre sur des erreurs qu’il est très facile de corriger, ou d’éviter. J’en rajouterai quelques unes ! En effet, travaillant souvent avec des stagiaires, j’ai le sentiment de ré-expliquer souvent les mêmes choses !
Un mauvais code c’est :
- un code qui peut fonctionner mais qui est affreux
- un code qui va fonctionner un moment avant de se planter
- un code qui ne fonctionne pas
C’est quoi la différence entre un bon et un mauvais code ? L’indice “What the Fuck” !

Bon code ou mauvais code, ça s’entend ! Dessin par Thom Holwerda “volé sans permission”
C’est quoi ce foutoir ?
( Catégorie : c’est affreux )
Le code n’est pas commenté, pas indenté, ou alors de manière artistique, aléatoire et perverse (honnêtement : comment peut-on coder sans indenter ?)
Dans un concours, cela fait baisser la note, sur le repository.org aussi, et en tant qu’utilisateur [qui sait coder], installer ce genre de daube est juste un “no way”.
Un plugin sans commentaire et sans indentation pour rendre le code lisible crie une seule chose : il est abandonné, ne sera jamais mis à jour ou maintenu, parce que dans un mois ou deux son auteur sera totalement paumé en le reprenant, ne se souviendra plus pourquoi, comment il a voulu faire ça ou ça, ni même à quoi “ça” correspond.
Il est inutile de développer trop longtemps, c’est du simple bon sens. Si vous codez de façon bordélique, ou ne comprenez pas ce dont je parle, voici un article à lire : Make clean and readable sources: why and how . En espérant que cela vous fera changer d’avis et que vous vous déciderez à suivre les Coding Standards de WordPress.
NB : si les Coding Standards sont une partie de la doc officielle, ce n’est pas pour des prunes !
T’es qui toi ?
Des noms de fonctions pas assez précis
( Catégorie 2 : un code qui va se planter )
Là encore, un grand basique, mais 40% des plugins revus étaient beaucoooooooooooup trop imprécis dans leur convention de nommage des fonctions. Surtout si on travaille en procédural, l’essentiel est de ne jamais voir “Fatal error: Cannot redeclare your poorly named function” s’afficher.
Un nom de fonction doit être descriptif et unique. On trouve dans des plugins des fonctions avec des noms aussi simples que pretty_title(), pages() ou update_options(). On peut même se retrouver dans le cas où une même personne va écrire plusieurs plugins qui ne peuvent pas être activés sur le même blog, parce qu’ils utilisent les mêmes déclarations de fonctions.

Des noms de fonctions identiques… problème !
Pour ces exemples, de meilleurs noms auraient été, par exemple, joeplugin_post_pretty_title(), joeplugin_list_pages() ou joeplugin_update_options(). Encapsuler les fonctions dans une classe permet de leur conserver un nom plus court, mais la classe elle-même doit avoir un nom à la fois descriptif et unique.
NB : c’est pourquoi, au début de mes plugins, j’inscris le préfixe que je mettrai devant chaque nom de fonction, qui est du type llncci_ (pour Lumiere de LuNe Creative Commons Image ). En revanche, ceux qui copient des tutos sans faire gaffe se retrouvent avec les mêmes noms de fonction partout )
T’as vraiment besoin de tout ça ?
87 nouveaux enregistrements dans la table wp_options ?
( Catégorie : “ça va pas la tête” )

Abondance d’options et de tables nuit
C’est un des dadas de Ozh, et aussi un des miens : ne pas enregistrer chaque option dans une ligne différente de la base de données (ou chaque champ personnalisé, comme le fait Yoast, même quand la valeur est nulle). Enregistrez les options de façon sérialisée, dans une seule entrée.
- ça encombre la base de données pour rien, quand le plugin est désactivé
- ça oblige à faire de nombreuses requêtes SQL au lieu d’une seule
Évidemment, il faut nuancer : si le plugin a un nombre monstrueux d’options, que seul un petit nombre d’entre elles vont être utilisées en front-end, tandis que le reste ne le sera que dans l’admin, il est plus logique de les séparer en ensembles cohérents, et de les enregistrer avec l’autoload à “non”.
Pourquoi ?
Parce que WordPress fonctionne de cette façon : au démarrage, il charge tous les fichiers dont il a besoin, ensuite il appelle dans la base de données toutes les options pour lesquelles autoload = yes, et les met en cache.
NB: j’ai vu dans une discussion sur l’autoload “oui mais c’est comme ça que WordPress.com fonctionne”, ce à quoi je répond :
- wordpress.com a des serveurs autrement plus puissants que de nombreux mutualisés, surtout en entrée de gamme
- wordpress.com n’accepte pas n’importe quoi et maîtrise de A à Z la qualité du code.
Comment préciser qu’une option ne doit pas être chargée automatiquement ? C’est le dernier argument de la fonction add_option :
add_option('option_name', 'option_value', '', 'no');
Tu ne peux pas faire comme tout le monde ?
Tu as vraiment besoin de créer de nouvelles tables pour ça ?
( Catégorie : c’est WordPress que tu utilises, pas Drupal )
WordPress propose plusieurs tables pour tous les types de données dont vous pouvez avoir besoin : utilisateurs, informations supplémentaires (meta) sur les utilisateurs, contenus et leurs informations supplémentaires, etc…
Bien sûr, il est possible que votre plugin ait besoin de créer une ou plusieurs tables supplémentaires (comme le font Gravity Forms, WPML ou WooCommerce), mais avant de le faire, réfléchissez bien et assurez vous que les tables existantes ne vont vraiment pas suffire. En particulier, regardez bien comment vous pouvez utiliser wp_options et les tables wp_*meta.
Si vous devez réellement créer de nouvelles tables, alors faites le en leur donnant un nom significatif et unique (c’est à dire préfixé avec quelque chose qui rappelle votre plugin), et surtout, commencez toujours par le préfixe WordPress, grâce à $wpdb->prefix
Par exemple :
$wpdb->prefix.'myplugintitle_custom_logs'
(NB ce travail d’analyse, c’est ce que nous avons fait dans la définition du modèle de données).
Tu ne peux pas ranger tes affaires ?

Petit désordre ordinaire
Pas de fonction de désinstallation
( Catégorie : Bécassine met de l’ordre )
Depuis la 2.7, WordPress a des fonctions qui permettent de désinstaller des plugins. Vous avez deux façons de le faire :
- en faisant appel à un hook uninstall (certains trouvent cela un peu compliqué, mais croyez moi c’est simle)
- utiliser un petit fichier de désinstallation, une solution super simple
Gérer la désinstallation du plugin n’est pas une obligation, mais c’est mieux quand le plugin ne laisse pas de trace dans la base de données une fois qu’on s’en est débarrassé.
Mais je trouve totalement inacceptable qu’un plugin créé une énorme quantité d’options ou des tables supplémentaires et ne fournisse pas un moyen de nettoyer les choses lors de la suppression.
( NB : c’est vrai, mais ce n’est pas si simple, il faut gérer deux étapes, la désactivation et la désinstallation. Cela fera l’objet d’un article à part ).
Tu pourrais être moins envahissant ?
Du CSS et du js ajouté sur chacune des pages de l’admin
( Catégorie : on ne voit que toi )
Ca c’est un grand classique. Quand vous créez une page d’option pour votre plugin et que vous avez besoin d’un javascript spécifique, siouplait, please, bitte, min fadlik, aafak, pajalouista, pretty please, ne rajoutez pas simplement vos bouts de jQuery via admin_head.
En faisant cela, vous les faites apparaitre dans toutes les pages de l’admin, un jour où l’autre il y aura un de ces fameux conflits de javascript qui va empêcher un autre plugin de fonctionner.
Et en plus, ça ne sert à rien.
Insérer votre script ou votre CSS sur les seules pages qui l’utilisent est très simple. Il suffit d’utiliser un hook dédié à cette page : admin_print_scripts-(page_hook)
Voici un exemple de code, fourni par Andrew Ozz :
$mypage = add_management_page( 'myplugin' , 'myplugin', 9 , __FILE__ , 'myplugin_admin_page' ) ; add_action( "admin_print_scripts-$mypage" , 'myplugin_admin_head' ) ; function myplugin_admin_head( ) { // on appelle les scripts, styles, etc nécessaires }
Edit : Julien Maury, de TweetPress, m’a proposé une meilleure solution :
https://mobile.twitter.com/TweetPressFr/status/507121946597658625
Avec un exemple de code sur Pastebin :
add_action( 'admin_menu', 'add_my_admin_menus' ); // le hook pour le menu /** * La page d'admin et le menu, chargement conditionnel des scripts */ function add_my_admin_menus(){ $my_page = add_menu_page( 'Page Title', 'Menu Title', 'manage_options', 'menu-slug', 'show_page_content' ); // le js uniquement sur cette page add_action( 'load-' . $my_page, 'load_admin_js' ); }

Nonce ecclésiastique celui-la
Tu la regarde, la définition de poste ?
Des formulaires non sécurisés et des nonces qui ne sont pas compris
( Catégorie : aie confiaaaaaaaaaaaaaaaaance )
Quand un plugin envoie des données via un formulaire, dans l’admin – typiquement une page d’options – il doit vérifier si l’utilisateur a les droits pour le faire (avec des fonctions comme is_admin() et current_user_can() ) et surtout, s’il avait l’intention réelle de le faire. Pour cela, il utilise un nonce, qui n’est pas apostolique.
Qu’est ce que cela veut dire ?
Il suffit de connaitre l’ID d’un post pour construire l’url qui permet de le supprimer.
Il suffirAIT d’envoyer cette url, dissimulée derrière un bit.ly à un blogueur, s’il était connecté à son blog, il supprimerait son article sans le vouloir.
Les nonces sont des champs cachés dans les formulaires de l’admin, qui génèrent une clé temporaire, le nonce, qui doit être vérifiée lors de l’exécution de la requête. En faisant cela, on s’assure que l’utilisateur se trouvait bien sur sa page d’admin quand il a envoyé la requête, donc, qu’à moins d’être totalement stone ou demeuré, il avait bien l’intention de faire cela. En tout cas, il n’a pas été pris par surprise…
On te dit de sauter, tu sautes ?
Des actions déclenchées sans vérifier les données reçues via un GET
( Catégorie : aie confiaaaaaaaaaaaaaaaaance )
On voit souvent du code comme celui-ci, et ça fait peur :
add_action('init', 'myplugin_init'); function myplugin_init() { global $wpdb; call_user_func($_GET['action']); $wpdb->query('DELETE from sometable WHERE somefield = '. $_GET['value']); }

Le saut à l’élastique
C’est similaire à l’erreur précédente : une requête qui n’a pas été vérifiée, cette fois-ci elle va exécuter du code, ou même une requête SQL.
yoursite.com?action=bleh&value=wot
Il est absolument essentiel de mettre en place des mécanismes de sécurité, car il n’a a même pas besoin d’envoyer un bit.ly à un administrateur de blog pour ouvrir la faille : n’importe qui peut le faire.
Là encore, les nonces sont la solution. Si vous voulez utiliser $_GET[‘action’] pour lancer une action, alors faites le de cette façon, au lieu d’utiliser
site.com/?action=something
utilisez
$url = "site.com/?action=something"; $action = "myplugin-update"; $link = wp_nonce_url( $url, $action ); echo "<a href='$link'>cliquez ici</a>"; // ce que vous faites pour afficher le lien
et dans la fonction lancée par les paramètres $_GET, faites :
if ( isset( $_GET['action'] ) && $_GET['action'] == 'trucmachin' ) { check_admin_referer( 'myplugin-update' ); // die si le nonce est invalide ou manquant // reste du code }
Vous voyez ? C’est simple d’utiliser les nonces. Une fonction appelle le formulaire ou le lien, une fonction appelle le process.
On peut quand même vérifier qu’on est bien dans l’admin – if ( is_admin() ) – et qu’on a les droits de faire ce qu’on veut – if ( current_user_can() )
Tu fais “vraiment’ confiance à n’importe qui ?
Passer des données utilisateurs sans vérification
( Catégorie : aie confiaaaaaaaaaaaaaaaaance )
C’est la plus critique des failles de sécurité que vous pouvez coder, particulièrement quand elle est liée à l’erreur précédente, comme je le vois dans de nombreux plugins.
Où que ce soit, quand vous exécutez une requête SQL sur la base d’une donnée envoyée par l’utilisateur, validez cette donnée, sinon vous risquez une injection SQL.

Si on te dit que c’est une pomme, vérifie !
Si vous envoyez un paramètre qui est censé être un entier, utiliser intval() avant de le stocker dans la base de données.
Si vous autorisez le HTML, échappez le avec esc_attr() ,
si vous attendez une chaîne, traitez-là avec preg_replace(‘/[^a-zA-Z]/’, ”) . Etc, etc.
Une fois que les données de votre requête SQL sont validées, envoyez la requête en utilisant $wpdb->prepare() avant de l’exécuter.
prepare() est similaire à l’utilisation de sprintf(), et gère pour vous tous les escapes, les quote et les cast dont vous pouvez avoir besoin. C’est aussi simple que :
$calvin = "6 ans"; $hobbes = "rembourré"; // "Prepare" la requête $sql = $wpdb->prepare( "INSERT INTO $wpdb->joeplugin_table( id, field1, field2 ) VALUES ( %d, %s, %s )", $_POST['id'], $calvin, $hobbes ); //L'exécuter $wpdb->query( $sql );
A partir du moment où vous mettez le doigt dans le SQL, vous devez comprendre et utiliser la classe wpdb.
( NB : et ne pas mettre du “prepare” sans rien y comprendre, avec
$sql = $wpdb->prepare( “INSERT INTO $wpdb->joeplugin_table( id,”.$calvin.”, “.$hobbes .”) VALUES ( %d, %s, %s )”, $_POST[‘id’] ) ;
comme on le voit souvent ! )
Toi y’en a ton tailor is rich ?
Une mauvaise localisation
( Catégorie : RTFM )
C’est l’erreur la plus fréquente que j’ai rencontrée : penser que __(‘some string’) sera suffisant pour rendre un plugin traduisible.
La bonne syntaxe à utiliser pour rendre la localisation d’un plugin possible est __(‘some string’, ‘myplugin’) où “myplugin” est un identifiant unique du textdomain, initialisé avec load_plugin_textdomain().
(NB sans parler, bien sûr, des plugins qui ne font même pas l’effort, ou des thèmes écrits directement “en français” pour que cela soit plus simple pour l’utilisateur… parce que le php est en français, lui ?)
Vous pouvez trouver ici les sources des images sous licence CC que j’ai utilisées (vivement mon plugin, vivement mon plugin…)
Un grand merci pour cet article ! Il y a quelques jours, je m’amusais à bidouiller pour un plugin… Résultat, mon site a planté et j’ai passé une nuit entière à le relancer… Je ne suis certainement pas le seul à qui c’est arrivé !
Quand je lis cet article, je m’aperçois que je n’avais pas fais qu’une erreur, mais plusieurs ! Et le pire avec WordPress, c’est qu’il n’est pas toujours possible de revenir en arrière en effaçant un bout de code foiré… Surtout quand son site est une usine à gaz !
Maintenant, je laisse le code aux codeurs… C’est bien plus simple ! :)
Un article plein de bon sens.
Et aussi pour les utilisateurs atteints de “pluginite aigüe” qui en installent à tour de bras pour la moindre petite fonction.
Quand on a ce genre de plugin mal développé : bonjour les dégâts.