Dans le cours précédent, on a vu comment utiliser le hook save_post pour calculer le temps de lecture lors de l’enregistrement d’un article. On va maintenant voir un autre cas de figure : la génération d’un sommaire. Nouvelle subtilité ici puisqu’on va devoir modifier le contenu de l’article tout fraichement enregistré.
Sommaire du cours
On va chercher à générer cette fois un sommaire cliquable, nous permettant de nous rendre rapidement à une section d’un article, comme c’est le cas sur ce site :
Le sommaire est un élément de design idéal pour les contenus longs. Et d’autant plus s’il est généré automatiquement en se basant sur les titres de la publication (H2, H3, H4…).
Aparté: en réalité…
Dans cette capture, le sommaire est en fait généré par un bloc Gutenberg que vous retrouverez dans mon extension Advanced Gutenberg Blocks. Celui-ci génère le sommaire à la volée via Javascript. Si le développement de blocs vous intéresse, consultez ma formation Gutenberg. Si par contre votre sommaire n’est pas dans le contenu, mais par exemple en barre latérale, il faudra donc mettre en application ce que l’on va voir dans ce cours.
En ce qui concerne les bases, on va utiliser la même technique que pour calculer le temps de lecture, en passant par le hook save_post
.
Vous noterez que j’ai créé une nouvelle fonction pour cela, mais j’aurais pu tout autant mettre le code que l’on va voir dans la même fonction que pour le temps de lecture. Après tout les conditions sont les mêmes.
Du coup, pour générer le temps de lecture et le sommaire, je préfère utiliser 2 fonctions différentes, qui seront basées sur le même hook.
Préparer le terrain : le sommaire et les ancres
Avant de se lancer corps et âme dans le code, on va déjà réfléchir à ce que l’on a besoin de faire.
Ce que l’on veut, c’est générer le sommaire et l’enregistrer dans les post metas. Ca maintenant, on sait faire. Mais pour que notre sommaire fonctionne, il va falloir que ses liens mènent quelque part.
Et pour cela on va devoir créer des ancres, c’est-à-dire des liens menant ailleurs au sein de la page. Par conséquent on a besoin du lien d’un côté, dans le sommaire, commençant par #, ainsi que de la cible de ce lien, l’ancre, dans le contenu. Elle s’exprime sous forme d’un ID, en attribut de la balise titre.
Donc générer le sommaire ne va pas suffire. Il faudra également modifier le contenu renvoyé par le hook, en ajoutant les identifiants des ancres sur chaque titre, et enfin réenregistrer ce contenu.
Avant de continuer, créez un article avec différents niveaux de titres et insérez du faux texte entre grâce à un générateur de Lorem Ipsum.
Tâche 1 : Générer les ancres
Dans le but de générer des ancres, on va d’abord devoir analyser le contenu de notre article.
Dans notre fonction lancée par le hook save_post
, on a vu qu’on a accès au contenu de l’article via le paramètre $post
. Il va falloir maintenant le décortiquer pour retrouver nos titres.
Voici le code nécessaire pour cette première étape :
Trouver les titres via une expression régulière
La première chose à faire, c’est de parcourir le contenu de l’article afin de trouver les titres et d’y injecter notre ancre.
Autant en JS on peut facilement analyser ou parser le DOM (les balises) d’un document, autant ce n’est pas évident en PHP. Mais heureusement, on va pouvoir utiliser une expression régulière (Ouaiiiis ! Berk).
Aparté: les expressions régulières
Les expressions régulières sont indispensables en informatique : elles sont très puissantes mais parfois un peu compliquées à lire. On les utilise notamment pour vérifier des textes, comme par exemple contrôler qu’une adresse e-mail est bien composée d’un texte, puis d’un arobase, puis d’un autre texte (le domaine), d’un point et enfin d’un dernier texte (l’extension .fr par exemple).
Et pour cela on va utiliser la fonction preg_replace_callback()
qui va nous permettre de rechercher nos titres, et de les remplacer avec notre version contenant l’ancre.
Paramètre 1 : l’expression régulière
Le premier paramètre, c’est l’expression régulière. On cherche des balises qui commencent par un h et suivies d’un chiffre entre 2 et 4 : h2, h3, h4…
Chaque groupe de parenthèse correspond à une zone de contrainte : (.*?)
veut par exemple dire qu’on peut trouver n’importe quel type de caractères. Cela nous permet de récupérer une éventuelle classe appliquée au titre (<h2 class="truc">
).
Paramètre 2 : la fonction de remplacement
Le second paramètre, c’est une fonction à exécuter lorsque preg_replace_callback
trouve un titre. Afin d’éviter de créer une fonction plus loin dans le code, j’applique une fonction anonyme, qui ne sera utilisée que dans ce cas précis.
La fonction me renvoie $matches
, qui sont les données trouvées par chaque jeu de parenthèses :
$matches[0]
n’existe pas (jamais) ;$matches[1]
correspond au niveau de la balise titre (2, 3 ou 4) ;$matches[2]
correspond à l’eventuelle classe ajoutée ;$matches[3]
correspond au titre affiché ;$matches[4]
correspond au niveau de titre de la balise fermante.
Pour ce dernier, il est sensé être le même que $matches[1]
.
En ce qui concerne l’ID, je vais nettoyer les caractères spéciaux de mon titre pour avoir une URL propre. Pour cela j’utilise la fonction de WordPress sanitize_title()
. Mon Titre deviendra alors mon-titre
.
Paramètre 3 : le texte à analyser
Et enfin, le troisième paramètre est simplement la chaine de texte à analyser, soit dans notre cas le contenu de l’article, disponible dans $post->post_content
.
Enfin, on récupère le résultat de cette fonction dans une nouvelle variable que j’appelle $content
.
Enregistrer le contenu : attention à la boucle infinie !
Bien. Maintenant il faut enregistrer à nouveau l’article. Et là on va faire face à un problème : pour enregistrer l’article on va utiliser wp_update_post()
, qui va appeler le hook save_post
, qui va appeler notre fonction, qui va appeler wp_update_post()
… Vous voyez où je veux en venir ? On vient de créer une boucle infinie !
Pour éviter ça, on va donc provisoirement couper notre hook via remove_action()
, pour le réactiver tout de suite après.
On va maintenant voir si l’ajout des ancres a bien fonctionné. Pour cela, enregistrez votre article, et rechargez la page de l’éditeur, car sinon vous ne verrez pas votre ancre apparaitre.
Cliquez ensuite sur un titre et depuis l’inspecteur à droite, regardez dans Avancé. Vous devriez apercevoir votre ancre.
Super ! Ça a bien fonctionné. Si jamais vous avez un bandeau rouge, c’est que vous avez une erreur dans votre code PHP. Consultez le log d’erreur dans wp-content/error.log
pour en connaitre la cause.
Tâche 2 : Générer le sommaire
Maintenant que nos ancres ont été ajoutées et notre contenu réenregistré, on va devoir créer une post meta contenant notre sommaire cliquable.
En premier lieu on va créer une variable $summary
qui va stocker le HTML de notre sommaire.
On va ensuite utiliser la fonction PHP preg_match_all()
pour retrouver tous nos titres. Cette fois, on ne veut pas faire du remplacement, mais récupérer les titres trouvés pour générer notre sommaire.
Je crée ensuite une boucle pour analyser les titres trouvés. Pour chacun d’entre-eux, j’insère dans mon sommaire un nouveau lien qui commence par # et qui permettra de pointer vers le slug de notre ancre.
Et à la fin, on enregistre la donnée dans une meta nommée summary
.
Afficher le sommaire dans le template
Maintenant, on va tenter d’afficher notre sommaire afin de vérifier s’il marche correctement. De la même manière que pour le temps de lecture, on va utiliser cette fonction dans notre template single.php
:
Ajoutez un peu de CSS pour obtenir un résultat plus joli, et vous devriez obtenir quelque chose comme ça :
En cliquant sur les liens, vous devriez être amené directement à la bonne section !
Créer un défilement animé en CSS (Smooth Scroll)
Pour en terminer avec ce sommaire, ce serait encore plus sympa le défilement (ou scroll en anglais) pouvait être animée. Non pas juste pour être joli, mais aussi pour donner un indice visuel sur ce qu’il se passe : on n’a pas changé de page, on est juste allé plus bas.
Voici le code à utiliser dans votre CSS :
La propriété scroll-behavior
permet d’activer le smooth scrolling dans votre page sans utiliser Javascript. La propriété scroll-margin-top
, appliquée au titre, permet de laisser un peu d’espace lorsque le scroll est terminé : au lieu que le titre soit collé tout en haut de l’écran, il y aura 40px d’espace.
C’est d’ailleurs très pratique pour éviter que votre titre ne finisse sous un menu fixe.
Vous savez désormais tirer pleinement partie du hook save_post
. Il vous sera utile à de nombreuses reprises dans le développement de vos thèmes.
Pour terminer, il existe pleins d’autres hooks intéressants dans WordPress, n’hésitez pas à faire un tour sur la documentation à ce sujet.
Fabrice
Le 29 juin 2020
Bonjour capitaine,
J’aurais voulu connaître le but de faire le remove_action/wp_update_post/add_action dans la boucle.
Est-ce-qu’il ne serait pas mieux de le faire après ?
En tout cas, super site, supers tutos, je recommande !!
Maxime BJ
Le 29 juin 2020
La fonction update_post() appelle le hook save_post(). Du coup, si tu n’interrompt pas le lancement de ce hook à ce moment, tu créées une boucle infinie.
Fabrice
Le 3 juillet 2020
Bonjour Maxime,
Je me suis mal expliqué.
Je connais ce piège, je suis tombé dedans quand j’ai commencé à faire du dev sur WP. Jolie perte de temps à l’époque, l’appel des hook est invisible en soi, et donc quand tu ne connais pas le mécanisme interne de WP, tu ne trouve pas tout de suite d’où vient le problème.
Ma question concernait le fait de faire le save dans la boucle. En fait, tu fais un enregistrement à chaque Hx trouvé. Est-ce-qu’il ne faudrait pas plutôt ne faire qu’un seul save après la boucle ou est-ce-qu’il y a un intérêt caché ? (temps de sauvegarde par exemple ?)
Arnaud ZELER
Le 7 août 2020
Je pense moi aussi qu’il serait plus judicieux d’effectuer le wp_update_post() ) la fin de la boucle $html->find( ‘h2, h3, h4’ ). Mais sinon à part ça, super auto pour comprendre le fonctionnement du hook save_post. Merci !
Maxime BJ
Le 10 août 2020
Oui en effet ce serait bien plus judicieux, je vais corriger ça au plus vite.
PIERRE GINGUENEAU
Le 30 avril 2021
Bonjour Maxime,
Questions : Si tout le contenu du post est généré par un flexible d’ACF.
Est ce que ça va fonctionner ?
Maxime BJ
Le 30 avril 2021
Peu importe : ce hook sera toujours invoqué lorsque tu enregistrera une publication. Dans les paramètres passés par la fonction, tu as le contenu de la publication (mais tu ne l’utilises peut-être pas) mais aussi l’id, avec lequel tu vas pouvoir aller faire un get_field( ‘flexible’, $post_id ); afin de le modifier ou de le vérifier !
Arnaud BeLO.
Le 14 mars 2022
Coucou Maxime,
Un énorme merci à ce big tuto qui m’a éclairci sur un grand nombre de points par des exemples à la fois simples mais extrêmement concrets, c’est du sacré bon boulot !!!
Juste 2 p’tites remarques qui me sont venues à la lecture de cette « démonstration » :
1. Tu ne gères pas les conflits provoqués par la présence éventuelle de titres aux contenus identiques, qui conduirait à plusieurs id identiques (solution cependant non bloquante pour le HTML), mais j’ai bien conscience que ça deviendrait autrement plus complexe s’il fallait en tenir compte (d’ailleurs je n’ai pas vraiment réfléchi à comment faire dans ce cas).
2. Tu n’as pas parlé de la disparition des éventuels attributs ($match[2]) des tags Hn lorsque tu leur attribues un id :
cela permet en effet d’enregistrer plusieurs fois de suite le post sans que les attributs id= » » ne se retrouvent à nouveau répliqués au sein de la même balise Hn (côté sommaire, le problème ne risque pas de se poser puisque celui-ci est entièrement reconstruit par un « annule et remplace »), mais génère aussi la disparition de tous les autres attributs (notamment des classes que l’on aurait attribuées au titre). En soit c’est une situation qui ne devrait pas vraiment se produire, mais c’est bon de le préciser 😉
Encore merci !
Maxime BJ
Le 14 mars 2022
Oui mais est-ce que ça vaudrait le coup de s’occuper de ça ? Il n’y a aucun intérêt à avoir 2 fois le même titre dans la même page, ce serait même pénalisant en terme de SEO. Pour le second point c’est vrai que je pourrais améliorer ma regex pour capter les classes et les réappliquer. Mais franchement, qui applique des classes personnalisées à ses titres ? Si le design système (aka la charte) est bien conçue, c’est un cas qu’on ne devrait jamais rencontrer. J’ai donc préféré rester simple.
pierre
Le 4 mai 2023
hello,
merci pour le cours, vraiment classe 🙂
Comment se procurer ton plugin ? https://advanced-gutenberg-blocks.com => lien kcé
Maxime BJ
Le 5 mai 2023
En effet je ne l’ai plus maintenu depuis un certain temps. Les blocs ne doivent plus fonctionner et je n’ai pas de remplacement à proposer hélas.