Utiliser le hook save_post pour générer un sommaire
Aujourd’hui nous allons voir comment tirer parti du hook save_post afin de générer le sommaire d’un article lors de son enregistrement.
Dans le précédent tutoriel, nous avons utilisé le hook save_post afin de définir le temps de lecture d’un article. J’y expliquais les bases d’un hook et comment les utiliser pour modifier le comportement natif de WordPress sans modifier directement son code.
- Lire le tutoriel précédent : Utiliser le hook save_post pour générer un sommaire
Créer le hook
On va reprendre la même base que dans le tutoriel précédent et ne garder pour l’instant que l’appel du hook et les tests de base. Placez ce code dans functions.php à la suite du précédent hook :
[pastacode lang=”php” manual=”function%20geekpress_generate_summary(%24post_id%2C%20%24post%2C%20%24update)%20%20%7B%0A%09%0A%09%2F%2F%20Sinon%20la%20fonction%20se%20lance%20d%C3%A8s%20le%20clic%20sur%20%22ajouter%22%0A%09if(!%24update)%20%7B%0A%09%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20On%20ne%20veut%20pas%20executer%20le%20code%20lorsque%20c’est%20une%20r%C3%A9vision%0A%09if(wp_is_post_revision(%24post_id))%20%7B%0A%09%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20On%20%C3%A9vite%20les%20sauvegardes%20automatiques%0A%09if%20(%20defined(%20’DOING_AUTOSAVE’%20)%20and%20DOING_AUTOSAVE%20)%20%7B%0A%20%09%20%20%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20Seulement%20pour%20les%20articles%0A%09if%20(%24post-%3Epost_type%20!%3D%20’post’)%7B%0A%20%20%20%20%09%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20La%20suite%20ici%0A%7D%0Aadd_action(‘save_post’%2C%20’geekpress_generate_summary’%2C%2010%2C%203)%3B” message=”” highlight=”” provider=”manual”/]
Alors oui, je vous voir venir, on pourrait très bien se content d’un seul hook pour faire le calcul du temps de lecture ET la génération du sommaire. Mais je préfère faire 2 hooks différents, de cette manière mon code est plus modulaire :
Si dans un prochain site je veux seulement le temps de lecture, j’aurais juste à copier/coller le hook concerné, sans me poser de questions.
Le sommaire et les ancres
On cherche à faire en sorte qu’à l’enregistrement, on analyse le HTML afin de trouver les titres (balises H2 à H6) pour définir le sommaire.
On veut ensuite ajouter des ancres sur chaque titre dans le contenu afin que lorsque l’on clique sur une entrée du sommaire, on soit amené directement à la bonne partie dans la page.
On va devoir alors :
- Analyser le HTML pour extraire chaque titre
- Dans le contenu, ajouter des ancres afin que le sommaire puisse faire des liens vers elles
- Générer un sommaire que l’on stockera dans une post meta
- Afficher le sommaire sur notre site
1. ANALYSER LE HTML ET EXTRAIRE LES TITRES
Bon, autant en Javascript il est facile de manipuler le DOM (= le code HTML de la page) et trouver les titres, autant PHP ne sait pas faire nativement. On va donc faire appel à une petite librairie plutôt simple à utiliser.
- Téléchargez ce fichier simple_html_dom.php depuis GitHub (pas besoin de toute la libraire)
- Placez le dans un dossier /lib/ de votre thème actuel (par exemple)
- Ajoutez ce code à la suite (mais toujours dans votre fonction de hook) :
[pastacode lang=”php” manual=”%09%2F%2F%20On%20charge%20le%20parser%20HTML%0A%09require_once(get_template_directory()%20.%20’%2Flib%2Fsimple_html_dom.php’)%3B%0A%09%0A%09%2F%2F%20On%20pr%C3%A9pare%20le%20contenu%0A%09%24html%20%3D%20str_get_html(%24post-%3Epost_content)%3B%20%20%0A%20%0A%09%2F%2F%20On%20recherche%20les%20titres%20principaux%0A%09foreach(%24html-%3Efind(‘h2%2C%20h3%2C%20h4%2C%20h5%2C%20h6’)%20as%20%24element)%3A%0A%09%09%0A%09%09%2F%2F%20on%20r%C3%A9cup%C3%A8re%20le%20titre%20brut%20(%3D%20le%20texte%20%C3%A0%20l’int%C3%A9rieur%20de%20la%20balise)%0A%09%09%24title%20%3D%20%24element-%3Einnertext%3B%0A%0A%09%09%2F%2F%20On%20%C3%A9vite%20les%20titres%20vides%0A%09%09if%20(%24title%20!%3D%20%22%22)%3A%0A%09%09%09%09%0A%09%09%09%2F%2F%20Provisoire%0A%09%09%09var_dump(%24title)%3B%0A%09%09%0A%09%09%09%2F%2F%20La%20suite%20ira%20ici%20%0A%0A%09%09endif%3B%0A%09endforeach%3B%0A%0A%09%2F%2F%20Ajoutez%20provisoirement%20le%20die%20%C3%A0%20la%20fin%20de%20la%20boucle%20pour%20arr%C3%AAter%20le%20script%0A%09%2F%2F%20et%20voir%20le%20r%C3%A9sultat%20du%20var_dump%0A%09die()%3B” message=”” highlight=”” provider=”manual”/]
En premier lieu, on charge la librairie. Ensuite, on amorce le parseur. Et enfin, on recherche les titres <Hx>. Je n’ai pas mis H1 dans la liste, mais vous avez bien compris pourquoi. Non ? Le titre <h1> ne devrait apparaitre qu’une seule fois par page, et il correspond au titre de la page, donc de l’article.
On ajoute un var_dump() pour afficher les valeurs à l’écran afin de contrôler que ça marche. Le die() est placé après la boucle afin que le script ne s’interrompe pas à la première itération de la boucle.
Créez maintenant un article et ajoutez du Lorem Ipsum, puis insérez des titres. Respectez la hierachie des titres ! On est pas chez les sauvages ici. En termes simples : pas de <h3> s’il n’y a pas eu un <h2> avant. C’est fou le nombre de gens qui ne comprennent pas encore ça. La sémantique joue un rôle non négligeable dans votre référencement naturel, et pour l’accessibilité.
Enregistrez votre article. Si tout se passe bien notre var_dump va afficher les titres trouvés :
Pensez ensuite à retirer le var_dump() et le die() pour que l’enregistrement de l’article se fasse bien.
2. Générer les ancres
Pour rappel une ancre est un lien interne. Il est orchestré par la balise <a> exactement comme un lien, mais au lieu de viser une nouvelle page, on vise un endroit dans notre page actuelle. Voici comment marche une ancre :
[pastacode lang=”markup” manual=”%3Ca%20href%3D%22%23mon-ancre%22%3EAller%20vers%20mon%20ancre%3C%2Fa%3E%0A%09%0A%3C!–%20plus%20loin%20…%20–%3E%0A%3Ch2%20id%3D%22mon-ancre%22%3EMon%20ancre%3C%2Fh2%3E” message=”” highlight=”” provider=”manual”/]
Le lien href de l’ancre commence par un croisillon (dièse c’est pour la musique, hashtag c’est pour les hipsters) et pointe vers un nom que vous allez définir avec l’attribut id de n’importe quelle base. Pour nous, ce seront les titres.
Voici désormais le code à l’intérieur ET après le foreach :
[pastacode lang=”php” manual=”%2F%2F%20On%20recherche%20les%20titres%20principaux%0Aforeach(%24html-%3Efind(‘h2%2C%20h3%2C%20h4%2C%20h5%2C%20h6’)%20as%20%24element)%3A%0A%20%20%0A%20%20%2F%2F%20on%20r%C3%A9cup%C3%A8re%20le%20titre%20brut%20(%3D%20le%20texte%20%C3%A0%20l’int%C3%A9rieur%20de%20la%20balise)%0A%20%20%24title%20%3D%20%24element-%3Einnertext%3B%0A%0A%20%20%2F%2F%20On%20%C3%A9vite%20les%20titres%20vides%0A%20%20if%20(%24title%20!%3D%20%22%22)%3A%0A%0A%20%20%20%20%2F%2F%20on%20cr%C3%A9e%20un%20slug%20du%20titre%0A%20%20%20%20%2F%2F%20Mon%20titre%20deviendra%20alors%20mon-titre%0A%20%20%20%20%24slug%20%3D%20sanitize_title(%24title)%3B%0A%0A%0A%20%20%20%20%2F%2F%20on%20ajoute%20id%3D%22mon-titre%22%20%C3%A0%20chaque%20titre%0A%20%20%20%20%24element-%3Eid%20%3D%20%24slug%3B%0A%0A%20%20endif%3B%0Aendforeach%3B%0A%0A%2F%2F%20On%20met%20%C3%A0%20jour%20le%20contenu%20de%20l’article%20avec%20les%20titres%20modifi%C3%A9s%0A%0A%2F%2F%20Pour%20%C3%A9viter%20une%20boucle%20infinie%2C%20on%20d%C3%A9sactive%20le%20hook%0Aremove_action(‘save_post’%2C%20’geekpress_generate_summary’%2C%2010%2C%203)%3B%0A%0A%2F%2F%20On%20met%20%C3%A0%20jour%20le%20HTML%20de%20l’article%0Awp_update_post(%20array(%20’ID’%20%3D%3E%20%24post_id%2C%20’post_content’%20%3D%3E%20%24html-%3Einnertext%20)%20)%3B%0A%0A%2F%2F%20r%C3%A9activer%20le%20hook%0Aadd_action(‘save_post’%2C%20’geekpress_generate_summary’%2C%2010%2C%203)%3B” message=”” highlight=”12,16,24,27,30″ provider=”manual”/]
Pour chaque titre trouvé, on crée un slug, c’est-à-dire un élément nettoyé des caractères spéciaux. Mon titre devient alors mon-titre.
La librairie simple HTML DOM nous simplifie pas mal la vie puisqu’en plus de pouvoir trouver les titres, elle nous permet d’injecter un attribut, ici l’id.
après le foreach, on réenregistre le contenu de l’article doté de ses nouvelles ancres. Afin d’éviter une boucle infinie, on désactive momentanément le hook (wp_update_post active le save_post, du coup on pourrait détruire l’univers si on ne fait rien).
Enregistrez votre article, et si tout se passe bien, affichez le texte (onglet à droite de l’éditeur visuel) et vous devriez voir vos ancres :
Cool ! Il nous faut maintenant générer un sommaire
3. Générer le sommaire
Maintenant, nous allons devoir générer le sommaire en HTML. Nous allons pour cela créer une liste <ul><li> avec un lien <a> sur chaque titre, qui nous mènera vers chacune de nos ancres crées précédemment.
Pour simplifier l’exemple je n’afficherais pas la hiérarchie des titres (par exemple une sous liste pour un h3, sous-sous liste pour un h4 etc…)
Voici donc le code complet du hook
[pastacode lang=”php” manual=”function%20geekpress_generate_summary(%24post_id%2C%20%24post%2C%20%24update)%20%20%7B%0A%09%20%20%2F%2F%20Sinon%20la%20fonction%20se%20lance%20d%C3%A8s%20le%20clic%20sur%20%22ajouter%22%0A%09%20%20if(!%24update)%20%7B%0A%09%09%20%20%20%20return%3B%0A%20%20%09%09%7D%0A%0A%09%2F%2F%20Autosave%2C%20do%20nothing%0A%09if%20(%20defined(%20’DOING_AUTOSAVE’%20)%20%26%26%20DOING_AUTOSAVE%20)%20%7B%0A%09%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20On%20ne%20veut%20pas%20executer%20le%20code%20lorsque%20c’est%20une%20r%C3%A9vision%0A%09if(wp_is_post_revision(%24post_id))%20%7B%0A%09%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20Seulement%20pour%20les%20articles%0A%09if%20(%24post-%3Epost_type%20!%3D%20’post’)%7B%0A%09%09return%3B%0A%09%7D%0A%0A%09%2F%2F%20On%20charge%20le%20parser%20HTML%0A%09require_once(get_template_directory()%20.%20’%2Flib%2Fsimple_html_dom.php’)%3B%0A%0A%09%2F%2F%20On%20pr%C3%A9pare%20le%20contenu%0A%09%24html%20%3D%20str_get_html(%24post-%3Epost_content)%3B%20%0A%0A%09%2F%2F%20On%20initialise%20le%20html%20du%20sommaire%0A%09%24summary%20%3D%20%22%3Cul%20class%3D’summary’%3E%22%3B%0A%0A%09%2F%2F%20On%20recherche%20les%20titres%20principaux%0A%09foreach(%24html-%3Efind(‘h2%2C%20h3%2C%20h4%2C%20h5%2C%20h6′)%20as%20%24element)%3A%0A%0A%09%09%2F%2F%20on%20r%C3%A9cup%C3%A8re%20le%20titre%20brut%20(%3D%20le%20texte%20%C3%A0%20l’int%C3%A9rieur%20de%20la%20balise)%0A%09%09%24title%20%3D%20%24element-%3Einnertext%3B%0A%0A%09%09%2F%2F%20On%20%C3%A9vite%20les%20titres%20vides%0A%09%09if%20(%24title%20!%3D%20%22%22)%3A%0A%0A%09%09%09%2F%2F%20on%20cr%C3%A9e%20un%20slug%20du%20titre%0A%09%09%09%2F%2F%20Mon%20titre%20deviendra%20alors%20mon-titre%0A%09%09%09%24slug%20%3D%20sanitize_title(%24title)%3B%0A%0A%09%09%09%2F%2F%20on%20ajoute%20id%3D%22mon-titre%22%20%C3%A0%20chaque%20titre%20dans%20le%20contenu%20de%20l’article%0A%09%09%09%24element-%3Eid%20%3D%20%24slug%3B%0A%0A%09%09%09%2F%2F%20On%20ajoute%20une%20entr%C3%A9e%20au%20sommaire%0A%09%09%09%24summary%20.%3D%20’%3Cli%3E%3Ca%20href%3D%22%23′.%24slug.’%22%3E’.%24title.’%3C%2Fa%3E%3C%2Fli%3E’%3B%0A%0A%09%09endif%3B%0A%09endforeach%3B%0A%0A%09%2F%2F%20On%20met%20%C3%A0%20jour%20le%20contenu%20de%20l’article%20avec%20les%20titres%20modifi%C3%A9s%0A%0A%09%2F%2F%20Pour%20%C3%A9viter%20une%20boucle%20infinie%2C%20on%20d%C3%A9sactive%20le%20hook%0A%09remove_action(‘save_post’%2C%20’geekpress_generate_summary’%2C%2010%2C%203)%3B%0A%0A%09%2F%2F%20On%20met%20%C3%A0%20jour%20le%20HTML%20de%20l’article%0A%09wp_update_post(%20array(%20’ID’%20%3D%3E%20%24post_id%2C%20’post_content’%20%3D%3E%20%24html-%3Einnertext%20)%20)%3B%0A%0A%09%2F%2F%20r%C3%A9activer%20le%20hook%0A%09add_action(‘save_post’%2C%20’geekpress_generate_summary’%2C%2010%2C%203)%3B%0A%0A%0A%09%2F%2F%20On%20referme%20le%20sommaire%0A%09%24summary%20.%3D%20%22%3C%2Ful%3E%22%3B%0A%0A%09%2F%2F%20Et%20on%20l’enregistre%20dans%20une%20meta%20li%C3%A9e%20%C3%A0%20l’article%0A%09update_post_meta(%20%24post_id%2C%20’_summary’%2C%20%24summary%20)%3B%0A%0A%7D%0Aadd_action(‘save_post’%2C%20’geekpress_generate_summary’%2C%2010%2C%203)%3B” message=”” highlight=”28,29,47,48,65,66,68,69″ provider=”manual”/]
Afin de générer le sommaire, on crée une nouvelle variable $summary qui va nous permettre de stocker le HTML. Après la boucle, nous allons enregistrer $summary dans un post meta, comme on l’avait fait pour le temps de lecture dans le précédent tutoriel.
On utilise alors la fonction update_post_meta, après la boucle foreach.
[pastacode lang=”php” manual=”update_post_meta(%20%24post_id%2C%20’_summary’%2C%20%24summary%20)%3B” message=”” highlight=”” provider=”manual”/]
4. Afficher le sommaire
Pour afficher le sommaire, nous faisons appel à notre post_meta via la fonction get_post_meta :
[pastacode lang=”php” manual=”%20%20%20%20%3Cdiv%20class%3D%22summary%22%3E%0A%20%20%20%20%20%20%3Cp%3ESommaire%20du%20cours%3C%2Fp%3E%0A%20%20%20%20%20%20%3C%3Fphp%20echo%20get_post_meta(%24post-%3EID%2C%20’_summary’%2C%20true)%3B%20%3F%3E%0A%20%20%20%20%3C%2Fdiv%3E” message=”” highlight=”” provider=”manual”/]
Dans cet exemple j’utilise le thème twentysixteen. Afin d’afficher le sommaire dans les articles il faut se rendre dans single.php : pour afficher le sommaire avant le contenu j’insère le code dans le bloc principal (main) et avant la boucle WordPress :
Si tout se passe bien, on devrait avoir notre sommaire affiché et fonctionnel. Cliquez sur une ancre et vérifiez que vous êtes bien amenés au bon titre. Faites en sorte d’avoir assez de contenu. Si votre page n’est pas assez haute, on risque de ne pas voir de différence (votre navigateur ne peut pas scroller au delà de votre page !) :
Ajoutez à votre convenance un peu de CSS pour rendre ça joli :
Un peu de Javascript pour l’ergonomie
Pour améliorer l’ergonomie, je vous conseille tout d’abord d’afficher votre sommaire en position: fixed; afin qu’il vous suive lors du scroll.
Il existe plusieurs techniques pour faire en sorte que votre sommaire soit d’abord fixe dans la page puis se mette à vous suivre lors du scroll. Le site CSS Tricks vous donne quelques exemples à ce propos.
On peut également faire en sorte d’ajouter un scroll animé, ou SmoothScroll, afin que l’internaute comprenne visuellement que la page se déplace verticalement pour atteindre le bon endroit, et que l’on ne change pas de page. Personnellement, j’utilise ces quelques lignes que je place dans mon script.js
[pastacode lang=”javascript” manual=”(function(%24)%20%7B%0A%20%20%24(document).ready(function()%20%7B%0A%0A%20%20%20%20%2F%2F%20Smooth%20Scroll%20%0A%0A%20%20%20%20%24(‘body’).on(‘click’%2C%20’a%5Bhref*%3D%23%5D%3Anot(%5Bhref%3D%23%5D)’%2C%20function(e)%7B%0A%20%20%20%20%20%20e.preventDefault()%3B%0A%20%20%20%20%20%20var%20anchor%20%3D%20%24(this).attr(‘href’)%3B%0A%20%20%20%20%20%20%24(‘html%2Cbody’).animate(%7BscrollTop%3A%20%24(anchor).offset().top%7D%2C%20300)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%7D)%3B%0A%7D)(jQuery)%3B” message=”” highlight=”” provider=”manual”/]
Petite précision sur le sélecteur CSS qui peut paraitre un peu barbare, mais qui est totalement legit : Je cible tous les liens de la page qui commencent (*= veut dire commence par) par un # (croisillon donc…) mais qui ne sont pas seulement des liens vides ne contenant que # (qui seront plus des actions internes pilotées par JS).
Pensez bien à appeler votre script dans WordPress via le hook wp_enqueue_scripts.
On peut également ajouter un espion, ou ScrollSpy, afin de mettre en avant sur le sommaire la section dans laquelle vous naviguez actuellement.
On retrouve un très bon exemple chez nos confrères de SeoMix, avec le petit indicateur jaune qui se déplaceautomatiquement d’une section à l’autre :
Conclusion
Et voilà, vous savez désormais utiliser le hook save_post à votre avantage pour générer des données lors de l’enregistrement. Ce hook me sert très souvent et s’avère très pratique !
Les puristes diront que ce genre de fonctionnalités ne devraient pas se trouver dans le thème, mais plutôt dans un plugin à part. Vous pouvez tout à faire créer ces hooks dans une extension plutôt que dans functions.php.
Si vous avez des remarques ou des questions, les commentaires sont là pour ça !
7 Commentaires
Salut,
Joli code :-)
Il y a juste un point qui me gène un peu : c’est qu’il est alors nécessaire d’update l’ensemble de ces contenus pour générer le sommaire.
Du coup au lieu d’appeler directement la meta, pourquoi ne pas faire une fonction du genre :
post_type, array( 'page', 'post' ) ) ) {
if ( ! $sommaire = get_post_meta( $obj->ID, '_summary', true ) ) {
$sommaire = geekpress_generate_summary( $obj->ID, $obj, true );
}
}
return $sommaire;
}
Qu’en penses-tu ?
Oui ça peut le faire aussi. Après je part du principe que je génère en avance ce dont j’ai besoin au moment où j’ai toutes les informations nécessaires. Je trouve ça, peut être à tord, plus logique.
Oui c’est tout à fait logique :-)
En fait la fonction de fallback dont je parle ne s’exécute – une seule fois – que si aucune meta « _summary » n’est trouvée.
Ce sera par exemple le cas pour les personnes qui implémenteront ton astuce sur un site existant, puis qui se rendront compte qu’il faut aller mettre à jour l’ensemble de leurs articles pour que cela fonctionne. L’autre option serait sans doute de déclencher un bulk save_post sur les contenus…
Bien expliqué bien détaillé !
J’avais bricolé un truc, mais sans rien stocké en base, c’était généré en front.
Pour les curieux c’est ici : https://github.com/posykrat/dfwp/blob/master/Doublefou/Components/Summary.php
Hello geekpress :),
un petit message pour vous dire que le tuto est excellent. Vraiment sympa. je suis en train d’utiliser votre code pour faire un plugin avec boilerplate décrit dans l’un de vos articles.
cependant je crois qu’il y a un souci avec le code énoncé. Quand j’utilise ce code :
foreach($html->find(‘h2, h3’, ‘h4’, ‘h5’, ‘h6’) as $element):
j’obtiens cette erreur : Invalid argument supplied for foreach(). l’argument find est invalide. j’ai résolu l’erreur en utilisant ce code :
foreach($html->find(‘h2, h3, h4, h5, h6’) as $element):
et là plus d’erreur :). d’ailleurs ce même code ci dessous se trouve dans une copie d’écran (4ème copie d’écran de code) de votre article.
voili voilou, j’espère que ma remarque va aider les lecteurs de geekpress. bon aller je vous laisse j’ai un plugin à finir ;)
a bientôt
Tu as tout à fait raison ! je vais corriger. C’est en effet un seul paramètre qui va contenir un sélecteur CSS (h1, h2) donc il n’y a pas de quotes à mettre entre. Merci pour la correction
Bonjour,
J’ai bien suivi votre code et également votre article sur le boilerplate. C’était extrêmement intéressant. Du coup avec l’aide de vos articles j’ai fait un plugin :). Vous pourrez trouver le plugin sur mon compte github : https://github.com/wyde22/wp-dynamic-summary-for-post
Vos articles ont été très formateur pour moi. Je vous en remercie et continuez à produire des articles techniques…j’adore ça !!
N’hésitez pas à le tester à revenir vers moi si vous trouvez des choses à redire dans le code. J’ai par exemple un souci avec la traduction du plugin. Poedit ne reconnait pas le fichier pot du dossier languages. J’ai du mal à comprendre mon erreur.
Encore merci et à bientôt