Les hooks des menus de navigation de WordPress
Il y a quelque temps maintenant, j’ai rédigé un tutoriel pour expliquer comment créer et utiliser les Walkers personnalisés, ces classes qui permettent de redéfinir le comportement de certains éléments de WordPress tels que les menus de navigation.
J’aime utiliser les Walkers car cela offre énormément de possibilités mais cela a aussi un inconvénient : il faut les tenir à jour.
Les customs walkers copient une partie des fonctions du core de WordPress, et donc si l’on veut tirer parti des dernières évolutions du code apporté au walkers originels du CMS, il faudra, de temps en temps, les mettre à jour.
Je précise cependant qu’il n’y a pas de risque de voir son code ne plus fonctionner du jour au lendemain ; c’est juste qu’au fil des années, votre code ne sera peut-être plus adapté pour jouer avec les fonctionnalités les plus récentes de WordPress.
Bref, tout cela pour dire que le précédent article montrait comment personnaliser les menus de navigation (wp_nav_menu) grâce aux Walkers, aujourd’hui j’ai envie de vous montrer tout ce qu’il est possible de faire en utilisant les quelques hooks de ces mêmes menus.
Vous allez voir que l’on peut faire exactement la même chose (et même davantage), tout en n’ayant pas ce risque d’obsolescence 🙂
Let’s Go !
Modifier les classes des éléments de menus
C’est ici un des filtres les plus connus et utilisé du wp_nav_menu. nav_menu_css_class est appliqué aux classes d’un élément de menu, avant que celui-ci ne soit retourné, dans Walker_Nav_Menu::start_el().
On peut donc l’utiliser pour nettoyer les classes que nous n’utiliserons pas en css, ou en ajouter de nouvelles qui nous manqueraient.
Ce hook filter dispose de trois arguments : le tableau des classes à filtrer, l’élément sous forme d’objet php, et les paramètres passés à la fonction wp_nav_menu.
Voici un exemple d’utilisation :
- ligne 8, je demande de ne conserver que les classes que j’utilise en CSS
- ligne 16, j’ajoute une classe « empty-item » si l’élément ne contient qu’un lien vide
- ligne 21, si l’élément courant est une archive de custom post type, et que l’on est justement sur la page singulière d’une entrée de ce type de contenu, on ajoute la classe « current-menu-ancestor »
Je vous laisse donc jouer avec ce hook pour voir tout ce qu’il permet.
Modifier la balise des liens « vides » et « current »
Pour intégrer certaines maquettes, on est parfois obligé de faire des éléments de menus vides (qui contiennent juste #). C’est par exemple le cas lorsque vous avez un sous-menu plein de liens, mais que l’élément de menu parent ne sert qu’à afficher le sous-menu au survol.
C’est crade de laisser un lien sans attribut href utile… autant employer un autre élément html tel qu’un span.
De même, Daniel m’a fait remarquer qu’on peut aussi en profiter supprimer le lien de l’élément actuel, pour éviter de faire un lien vers la page actuel, ce qui est mauvais en SEO.
C’est ce que nous faisons ici, grâce au filtre walker_nav_menu_start_el :
Ce filtre est appliqué au contenu d’un élément de menu (sans le <li> ouvrant donc…) avant qu’il ne soit retourné dans Walker_Nav_Menu::start_el(). Il propose quatre arguments : la sortie à filtrer, l’élément courant (objet), la profondeur courante (racine, sous-menu, sous-sous-menu…) ainsi que les paramètres passés à wp_nav_menu.
Si l’attribut url de l’élément est une ancre vide, ou s’il s’agit de l’élément actuel, alors on applique une petite regex pour remplacer la balise et tout ses attributs par un span.
Nous réutiliserons ce hook un peu plus loin dans un tout autre dessein…
Ouvrir les liens externes du menu dans un nouvel onglet
L’interface de l’administration des menus permet déjà de sélectionner la destination des liens, mais si on veut automatiser la chose, il faut utiliser le filtre nav_menu_link_attributes. Il filtre un tableau d’attributs qui sera renvoyé dans Walker_Nav_Menu::start_el() pour construire les attributs du lien.
Ici, donc, si l’attribut href ne commence pas par le domaine du site, on force le target à blank, et on avertit l’utilisateur en changeant le title du lien.
Notez qu’on aurait pu également ajouter des attributs si besoin, tels que des aria…
Ajouter un bouton pour le menu responsive
Il existe deux techniques pour gérer la navigation en responsive : utiliser un menu alternatif, masqué par défaut :-/ ou bien modifier l’apparence d’un unique menu, en fonction de la dimension de l’écran. Pour cette deuxième méthode, il est souvent nécessaire d’utiliser un bouton pour déployer le menu.
Alors certes, on pourrait mettre ce bouton directement dans le template, ou bien le pousser en paramètre de wp_nav_menu, mais imaginez que vous intervenez sur un thème enfant : allez vous vraiment dupliquer chaque fichier du thème parent qui contient un menu, juste pour ajouter ce petit paramètre ? Si vous faites ça vous allez perdre tout le bénéfice des mises à jour du thème.
Pour une fois sortons un peu de Walker_nav_menu pour explorer les hooks autour ! Cette fois le filtre que nous allons employer est appliqué directement dans la fonction wp_nav_menu, et permet d’en modifier la sortie. wp_nav_menu ne prend que deux paramètres : l’output, et les arguments originels.
Voici ce que nous allons faire :
Si le menu correspond à tel emplacement, alors on ajoute un bouton juste avant. Il ne restera plus qu’à gérer son affichage en css et son comportement en javascript.
Ajouter une barre de recherche au menu
Voici un petit exemple qui va me permettre d’illustrer un nouveau hook : wp_nav_menu_items
Ce hook est un filtre qui est appliqué à la liste des éléments html, une fois qu’elle a été générée par le walker, et avant d’être poussée dans son <ul> global. L’intérêt de ce filtre est de pouvoir ajouter des éléments de liste, au début ou à la fin du menu.
Si l’on souhaite ajouter un formulaire de recherche à la fin du menu, il suffit de procéder comme ceci :
Dans cet exemple, je n’ajoute la barre de recherche que si le thème_location est « primary » (menu par defaut du thème twentyfifteen).
Mettre en cache les menus grâce aux transients
Les menus de WordPress son extrêmement requêtovaures !
Cela s’explique par le fait qu’à chaque élément de menu correspond une entrée en base de type nav_menu_item, et que la plupart de ces éléments en désignent d’autres à aller chercher en base également.
Pour un thème_location, il faut trouver le menu correspondant, puis ces éléments, puis les cibles de ces éléments, et éventuellement d’autres informations.
Pour de gros menus on arrive vite à plus de cent requêtes.
Pour optimiser ça, on peut enregistrer chaque menu dans un transient : une option de WordPress destinée à stocker des données temporaires.
Nous allons utiliser deux hooks pour cela :
- wp_nav_menu, pour écrire notre transient, s’il n’existe pas ;
- pre_wp_nav_menu pour récupérer notre transient et le retourner directement, sans générer le menu (économisant ainsi de nombreuses requêtes MySQL). Ce second hook se trouve au tout début de wp_nav_menu, et son comportement et de court-circuiter le reste de la fonction s’il retourne autre chose que « null ».
La seule valeur qui identifie un transient, c’est son nom. Il faut donc veiller à ce qu’il soit unique pour chaque menu, et qu’il ne dépasse pas 45 caractères. C’est pour cela que je pousse tous les arguments de la fonction en md5…
Flusher les transients lors des mises à jour du menu
Si vous mettez à jour votre menu, le transient ne sera pas automatiquement détruit, et les anciennes infos toujours retournées. Nous allons donc utiliser les hooks d’action wp_update_nav_menu et wp_delete_nav_menu afin de supprimer les transients lorsque le menu est mis à jour ou supprimé :
Comme précisé dans le code : pas moyen de faire dans la dentelle ici : on supprime tous nos transients lorsqu’un seul menu est mis à jour. C’est simplement dû à notre nommage : nous n’avons pas accès ici aux paramètres de wp_nav_menu.
Encore quelques petits détails
Si vous utilisez cette technique, vous ne pourrez plus utiliser correctement les classes css désignant l’élément courant ou ancêtre, car le transient est commun à toutes les pages. Il faudra donc filtrer les classes comme nous l’avons vu tout en haut de l’article.
Ou bien, vous pouvez enregistrer un transient par page, ce qui alourdira un peu votre table des options (mais sans conséquence sur la performance du site), en poussant l’URL courant dans le nom de votre transient :
Bien sûr, il faut veiller à exclure les 404, sinon la table risque d’exploser 😛
Créer un sous-menu dynamique
Une des fonctionnalités particulièrement intéressante des walkers était de pouvoir concevoir des sous-rubriques dynamiques, où les éléments du menus sont générés automatiquement.
En fait on peut faire la même chose avec walker_nav_menu_start_el (vous vous souvenez, on l’a vu plus haut…). Après avoir construit le contenu d’un <li>, le filtre est appliqué. On dispose ici des infos nécessaires pour construire un sous-menu, comme par exemple une liste de catégories :
Dans ce code, si l’ID de l’objet ciblé par l’élément est la home du blog (et non, la home du site) on liste les 5 catégories les plus utilisées du site grâce à la fonction wp_list_categories.
Attention : les classes CSS ont déjà été ajoutées à ce stade. Si vous souhaitez préciser une classe du type « menu-item-has-children », il faudra aussi faire un petit test sur cet élément via le filtre nav_menu_css_class vu plus haut.
N’afficher le sous-menu que pour l’élément courant
Il y a plusieurs mois, Daniel d’SeoMix, m’avait posé la colle suivante :
Comment, dans un menu avec des sous-menus, n’afficher le sous-menu que pour la rubrique courante (que l’on se situe sur l’élément parent, un enfant ou un descendant) ?
Je lui avais alors pondu un code à rallonge, utilisant un walker et faisant des tests dans tous les sens pour déterminer la filiation entre la page en cours et un élément de menu.
En fait il y a beaucoup plus simple…
Dans wp_nav_menu, la fonction _wp_menu_item_classes_by_context est exécutée afin de construire la liste des objets à afficher dans le menu. C’est au sein de cette fonction que de nombreuses opérations sont effectuées pour déterminer la filiation avec la page en cours (cela sert pour générer les classes).
Nul besoin de ré-inventer la poudre donc, il suffit d’utiliser le filtre wp_nav_menu_objects pour trier les éléments de menu avant de les envoyer au walker.
Je vous montre la fonction, et je vous la commente ensuite :
- Tout d’abord, on applique le filtre, qui prend deux paramètres : la liste des objets de menu, triés, ainsi que les arguments de la fonction de menu.
- Ensuite on crée un tableau qui contiendra les ID des éléments de niveau 0 qui conserveront les sous menus.
- Pour chaque élément :
- si c’est un sous-élément, que son parent n’est pas déjà dans le tableau, et qu’il s’agit de la page en cours ou d’un ancêtre, on ajoute son parent au tableau ;
- si c’est un élément de niveau 0, qu’il n’est pas dans le tableau et qu’il s’agit de la page en cours ou d’un ancêtre, on l’ajoute au tableau.
- Puis on repasse sur chaque élément :
- si c’est un sous-élément dont le parent n’est pas dans le tableau, on le supprime ;
- si c’est un élément de niveau 0 qui n’est pas dans le tableau, et qu’il a la classe menu-item-has-children, on supprime cette classe.
Voilà un bon exemple d’utilisation du filtre wp_nav_menu_objects 🙂
Forcer l’utilisation d’un walker sur tous les menus
Pour finir, je pense qu’il est intéressant de parler du filtre wp_nav_menu_args qui permet de modifier les paramètres d’une fonction wp_nav_menu.
Prenons cet exemple : j’ai envie d’appliquer un walker, en masse, à tous mes menus. J’en ai pas mal, et je n’ai pas envie d’aller modifier les fonctions une par une. Voici ma solution :
Ce filtre est bien pratique aussi si vous souhaitez changer le container du menu, son wrapper, ses classes ou ses ids…
Voilà, j’en ai fini de vous présenter les possibilités des hooks des menus de navigation. Je suis volontairement passé à côté de quelques hooks tels que nav_menu_item_id ou wp_nav_menu_container_allowedtags qui présentent, selon moi, moins d’intérêt en terme de fonctionnalité.
Et lorsque vous souhaiterez modifier le fonctionnement de WP, n’hésitez pas à aller fouiller le code source à la recherche de d’autres hooks 🙂
… mais ce code est là pour l’exemple, à chacun de le faire évoluer 🙂
Bisous 🙂
Je suis désespérement attristé et désabusé par WordPress 🙂
Par contre ça devient un peu hors-sujet sur l’article ^^
J’en parlais avec Thierry cette semaine. Ton tuto tombe à Pic, je voulais m’y mettre. Merci.
Je comptais aussi faire cohabiter nos gravatars dans la foulée 🙂
Hahaha pour les gravatars XD
Il suffit donc d’ajouter le code suivant avant la ligne du preg_replace :