Tout savoir sur WordPress

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.

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 :

function geekpress_generate_summary($post_id, $post, $update)  {
	
	// Sinon la fonction se lance dès le clic sur "ajouter"
	if(!$update) {
		return;
	}

	// On ne veut pas executer le code lorsque c'est une révision
	if(wp_is_post_revision($post_id)) {
		return;
	}

	// On évite les sauvegardes automatiques
	if ( defined( 'DOING_AUTOSAVE' ) and DOING_AUTOSAVE ) {
 	  	return;
	}

	// Seulement pour les articles
	if ($post->post_type != 'post'){
    		return;
	}

	// La suite ici
}
add_action('save_post', 'geekpress_generate_summary', 10, 3);

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 :

  1. Analyser le HTML pour extraire chaque titre
  2. Dans le contenu, ajouter des ancres afin que le sommaire puisse faire des liens vers elles
  3. Générer un sommaire que l’on stockera dans une post meta
  4. 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) :
	// On charge le parser HTML
	require_once(get_template_directory() . '/lib/simple_html_dom.php');
	
	// On prépare le contenu
	$html = str_get_html($post->post_content);  
 
	// On recherche les titres principaux
	foreach($html->find('h2, h3, h4, h5, h6') as $element):
		
		// on récupère le titre brut (= le texte à l'intérieur de la balise)
		$title = $element->innertext;

		// On évite les titres vides
		if ($title != ""):
				
			// Provisoire
			var_dump($title);
		
			// La suite ira ici 

		endif;
	endforeach;

	// Ajoutez provisoirement le die à la fin de la boucle pour arrêter le script
	// et voir le résultat du var_dump
	die();

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

hierarchie titres

Enregistrez votre article. Si tout se passe bien notre var_dump va afficher les titres trouvés :

titres trouves

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 :

<a href="#mon-ancre">Aller vers mon ancre</a>
	
<!-- plus loin ... -->
<h2 id="mon-ancre">Mon ancre</h2>

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 :

// On recherche les titres principaux
foreach($html->find('h2, h3, h4, h5, h6') as $element):
  
  // on récupère le titre brut (= le texte à l'intérieur de la balise)
  $title = $element->innertext;

  // On évite les titres vides
  if ($title != ""):

    // on crée un slug du titre
    // Mon titre deviendra alors mon-titre
    $slug = sanitize_title($title);


    // on ajoute id="mon-titre" à chaque titre
    $element->id = $slug;

  endif;
endforeach;

// On met à jour le contenu de l'article avec les titres modifiés

// Pour éviter une boucle infinie, on désactive le hook
remove_action('save_post', 'geekpress_generate_summary', 10, 3);

// On met à jour le HTML de l'article
wp_update_post( array( 'ID' => $post_id, 'post_content' => $html->innertext ) );

// réactiver le hook
add_action('save_post', 'geekpress_generate_summary', 10, 3);

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 :

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

function geekpress_generate_summary($post_id, $post, $update)  {
	  // Sinon la fonction se lance dès le clic sur "ajouter"
	  if(!$update) {
		    return;
  		}

	// Autosave, do nothing
	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return;
	}

	// On ne veut pas executer le code lorsque c'est une révision
	if(wp_is_post_revision($post_id)) {
		return;
	}

	// Seulement pour les articles
	if ($post->post_type != 'post'){
		return;
	}

	// On charge le parser HTML
	require_once(get_template_directory() . '/lib/simple_html_dom.php');

	// On prépare le contenu
	$html = str_get_html($post->post_content); 

	// On initialise le html du sommaire
	$summary = "<ul class='summary'>";

	// On recherche les titres principaux
	foreach($html->find('h2, h3, h4, h5, h6') as $element):

		// on récupère le titre brut (= le texte à l'intérieur de la balise)
		$title = $element->innertext;

		// On évite les titres vides
		if ($title != ""):

			// on crée un slug du titre
			// Mon titre deviendra alors mon-titre
			$slug = sanitize_title($title);

			// on ajoute id="mon-titre" à chaque titre dans le contenu de l'article
			$element->id = $slug;

			// On ajoute une entrée au sommaire
			$summary .= '<li><a href="#'.$slug.'">'.$title.'</a></li>';

		endif;
	endforeach;

	// On met à jour le contenu de l'article avec les titres modifiés

	// Pour éviter une boucle infinie, on désactive le hook
	remove_action('save_post', 'geekpress_generate_summary', 10, 3);

	// On met à jour le HTML de l'article
	wp_update_post( array( 'ID' => $post_id, 'post_content' => $html->innertext ) );

	// réactiver le hook
	add_action('save_post', 'geekpress_generate_summary', 10, 3);


	// On referme le sommaire
	$summary .= "</ul>";

	// Et on l'enregistre dans une meta liée à l'article
	update_post_meta( $post_id, '_summary', $summary );

}
add_action('save_post', 'geekpress_generate_summary', 10, 3);

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.

update_post_meta( $post_id, '_summary', $summary );

4. Afficher le sommaire

Pour afficher le sommaire, nous faisons appel à notre post_meta via la fonction get_post_meta :

    <div class="summary">
      <p>Sommaire du cours</p>
      <?php echo get_post_meta($post->ID, '_summary', true); ?>
    </div>

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 :

template

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 !) :

sommaire

Ajoutez à votre convenance un peu de CSS pour rendre ça joli :

sommaire wordpress

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

(function($) {
  $(document).ready(function() {

    // Smooth Scroll 

    $('body').on('click', 'a[href*=#]:not([href=#])', function(e){
      e.preventDefault();
      var anchor = $(this).attr('href');
      $('html,body').animate({scrollTop: $(anchor).offset().top}, 300);
    });

  });
})(jQuery);

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 :

seomix sommaire

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 !

Cet article a été mis à jour il y a 245 jours

Article écrit par Maxime BJ

Développeur, bloggeur et formateur Web spécialisé WordPress. 31 ans. Grenoblois. Co-fondateur de WPChef, l’organisme de formation WordPress.

Organisateur de WPInAlps, le meetup WordPress Grenoblois. Vous pouvez me rencontrer lors d’événements tels que WordCamp Paris et Europe. Traducteur Français de l’extension Advanced Custom Fields. Également développeur d’applications web avec MeteorJs. Je m’occupe un site pour apprendre l’informatique aux débutants gratuitement.

J’aime les jeux vidéo, la rando, la bouffe bien grasse et les voyages.

7 Commentaires

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

  1. 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 :
    < ?php// juste après avoir fait ton update de post_meta, return $summary (au lieu de ne rien renvoyer)… // puis faire une fonction pour l'affichage…function summary() { $obj = get_queried_object(); if ( in_array( $obj->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.

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

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

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

112e2f4b44085de86e7a643de4879a70////////////////////