Formation « Développer un thème WordPress sur-mesure (Classique) »

Utiliser le hook « save post » pour créer un sommaire

Lecture : 6 minutes • 11

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é.

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 :

La capture montre le site Capitaine WP affichant un temps de lecture ainsi qu'un sommaire.
Le sommaire et le temps de lecture sont générés via le hook save_post

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.

PHP
functions.php

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.

Conseil

Mais en programmation, une fonction doit se concentrer sur une et une seule tâche en particulier.

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.

HTML

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.

Un article contenant des titres et du faux texte pour faire nos tests
Créez un article contenant des titres de différents niveaux et du faux texte

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 :

PHP
functions.php

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.

PHP

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.

PHP

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.

Chaque titre possède désormais une ancre HTML, affichée dans l'inspecteur à droite, rubrique Avancé
L’ancre a bien été générée pour chaque titre

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.

Un bandeau rouge indique que l'éditeur n'a pas réussi l'enregistrement de la publication
Si vous voyez ce bandeau, vérifiez votre code PHP !

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.

PHP
functions.php

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.

Attention

Dans cet exemple je suis volontairement resté simple : du coup tous les éléments du sommaire s’afficheront de la même manière sans distinction entre les niveaux de titres.

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 :

PHP
single.php

Ajoutez un peu de CSS pour obtenir un résultat plus joli, et vous devriez obtenir quelque chose comme ça :

Le sommaire généré apparait désormais sur la page de l'article.
Le sommaire apparait sur la page !

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 :

CSS
style.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.

11

Questions, réponses et commentaires

  1. 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 !!

    1. 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.

      1. 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 ?)

        1. 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 !

          1. Maxime BJ

            Le 10 août 2020

            Oui en effet ce serait bien plus judicieux, je vais corriger ça au plus vite.

  2. 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 ?

    1. 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 !

  3. 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 !

    1. 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.

  4. pierre

    Le 4 mai 2023

    hello,
    merci pour le cours, vraiment classe 🙂
    Comment se procurer ton plugin ? https://advanced-gutenberg-blocks.com => lien kcé

    1. 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.

Laisser un commentaire