Créer une page d’archive d’un custom post type en classant selon les termes d’une taxonomie

Mis à jour le :

Créer une page d’archive d’un type de contenu est relativement simple, il convient de nommer le fichier archive-CPT.php dans le dossier de votre thème.

La problématique ici consistait non seulement à lister tous les contenus mais en tenant compte des termes qui leur étaient reliés.

Si je devais résumer, voici le résultat souhaité :

  • Terme 1
    • Contenu A
    • Contenu B
    • Contenu C
  • Terme 2
    • Contenu A
    • Contenu B
    • Contenu C
  • Terme 3
    • Contenu A
    • Contenu B
    • Contenu C

Et ainsi de suite jusqu’à ce que tous les contenus aient été listés dans ma page d’archive.

Pour ce faire, j’ai donc commencé par lister les termes à l’aide de la fonction get_terms en spécifiant le nom de la taxonomie reliée à mon type de contenu.

Ensuite, pour chaque terme, une requête WP_Query pour récupérer les contenus qui sont bien associés à ce terme. Une fois dans la boucle, à vous de choisir les éléments que vous souhaitez récupérer : le titre, le permalien, l’image à la une, etc.

Ce code se place dans le fichier archive-CPT.php de votre thème, ou mieux de votre thème enfant, en remplaçant CPT par l’identifiant de votre type de contenu : archive-livre.php pour un type livre, par exemple. WordPress n’utilise ce modèle que si le type de contenu a été déclaré avec l’argument has_archive. Remplacez aussi NOM_CPT et NOM_TAXONOMIE, aux lignes surlignées, par les identifiants de votre type de contenu et de votre taxonomie.

Voici l’intégralité du code du tutoriel avec des éléments facultatifs pour bien présenter les choses dont le titre de la page d’archive.

<?php if (! defined('ABSPATH')) die('Restricted Area'); ?>
<?php get_header(); ?>
	<header class="page-header">
	<h1 class="container"><?php post_type_archive_title(); ?></h1>
	</header>
<div class="content">
	<div class="container">
		<?php
			$post_type = 'NOM_CPT';
			$taxonomy = 'NOM_TAXONOMIE';

			$terms = get_terms(array(
			 'taxonomy' => $taxonomy,
			 'orderby' => 'name',
			 'order' => 'ASC'
			 ));

			if (! is_wp_error($terms) && $terms) { foreach($terms as $term) {
		?>

		<?php 
			$args = array(
			'post_type' => $post_type,
			'posts_per_page' => -1,
			'tax_query' => array(
			array(
				'taxonomy' => $taxonomy,
				'terms' => array($term->term_id),
				'include_children' => true,
				'operator' => 'IN'
				)
			)
		);
		$my_query = new WP_Query($args); if ($my_query->have_posts()) { ?>
			<div class="row">
				<h2 class="term-title"><?php echo esc_html($term->name); ?></h2>

				<?php while ($my_query->have_posts()) : $my_query->the_post(); ?>
					<div class="col-sm-2">
						<?php if (has_post_thumbnail()) : ?>
							<?php the_post_thumbnail('thumbnail', array('class' => 'img-responsive aligncenter')); ?>
						<?php endif; ?>
						
						<h3><a href="<?php the_permalink() ?>" rel="bookmark"><?php the_title(); ?></a></h3>
					</div>
				<?php endwhile; ?>
			</div>

		<?php wp_reset_postdata(); } 
		 } } ?>
	</div>
</div>
<?php get_footer(); ?>

Le titre de l’archive est placé hors de la boucle principale, qui l’aurait répété pour chaque contenu, et chaque terme devient un titre de niveau 2. Sans posts_per_page à -1, chaque requête s’arrêterait au nombre fixé dans Réglages > Lecture par le réglage « Nombre de publications récentes affichées sur les pages d’archive » (10 par défaut). wp_reset_postdata() rétablit le contenu courant après chaque boucle, et le test is_wp_error() évite des avertissements PHP si le nom de la taxonomie est erroné.

Si votre taxonomie est hiérarchique, comme les catégories, un contenu classé dans un terme enfant apparaît sous ce terme et sous son parent : passez include_children à false pour ne l’afficher que sous son propre terme. Les termes sans contenu et les contenus sans terme n’apparaissent pas.

Le reste vous l’aurez compris n’est que mise en forme HTML / CSS. Ici, je me base sur les classes de Bootstrap 3 : depuis Bootstrap 4, img-responsive s’appelle img-fluid. Adaptez ces classes à votre thème.

Et avec un thème à blocs ?

Avec un thème à blocs, comme Twenty Twenty-Five, ce fichier PHP n’est pas la bonne solution. WordPress l’utilise bien, mais get_header() et get_footer() n’y trouvent ni header.php ni footer.php : ils chargent alors un en-tête et un pied de page de secours, obsolètes depuis WordPress 3.0, au lieu de ceux de votre thème. Placez plutôt la même logique dans un code court (shortcode), à ajouter au fichier functions.php de votre thème enfant :

/**
 * [wpc_cpt_terms] shortcode: lists the posts of a post type grouped by the terms of a taxonomy.
 */
function wpc_cpt_terms_shortcode() {
	$post_type = 'NOM_CPT';
	$taxonomy  = 'NOM_TAXONOMIE';

	$terms = get_terms( array(
		'taxonomy' => $taxonomy,
		'orderby'  => 'name',
		'order'    => 'ASC',
	) );

	if ( is_wp_error( $terms ) || ! $terms ) {
		return '';
	}

	$output = '';

	foreach ( $terms as $term ) {
		$my_query = new WP_Query( array(
			'post_type'      => $post_type,
			'posts_per_page' => -1,
			'tax_query'      => array(
				array(
					'taxonomy' => $taxonomy,
					'terms'    => array( $term->term_id ),
				),
			),
		) );

		if ( ! $my_query->have_posts() ) {
			continue;
		}

		$output .= '<h2 class="term-title">' . esc_html( $term->name ) . '</h2><ul>';

		while ( $my_query->have_posts() ) {
			$my_query->the_post();
			$output .= '<li><a href="' . esc_url( get_permalink() ) . '">' . esc_html( get_the_title() ) . '</a></li>';
		}

		$output .= '</ul>';
		wp_reset_postdata();
	}

	return $output;
}
add_shortcode( 'wpc_cpt_terms', 'wpc_cpt_terms_shortcode' );

Ouvrez ensuite Apparence > Éditeur > Modèles, cliquez sur Ajouter un modèle et choisissez Archives : Livre, Livre étant le nom de votre type de contenu. Ce choix n’apparaît que si le type de contenu a été déclaré avec has_archive et show_in_rest à true.

Éditeur du site de WordPress, rubrique Modèles (Apparence > Éditeur > Modèles), thème Twenty Twenty-Five : la fenêtre « Ajouter un modèle », ouverte par le bouton du même nom en haut à droite, affiche « Sélectionnez ce à quoi le nouveau modèle doit s’appliquer : » et les choix Page d’accueil, Pages, Archives des auteurs/autrices, Archives des catégories, Archives des dates, Archives des étiquettes, Archives : Livre, Élément unique : Article, Élément unique : Livre et Modèle personnalisé ; en arrière-plan, la colonne Modèles et les aperçus des modèles du thème.
L’éditeur du site propose un modèle d’archive pour chaque type de contenu qui en possède une

Dans la fenêtre Choisir une composition qui s’ouvre, sélectionnez le Contenu de secours, tiré des modèles de votre thème. Remplacez-y le bloc Boucle de requête par un bloc Code court contenant [wpc_cpt_terms], puis cliquez sur Enregistrer.

Éditeur du site de WordPress avec Twenty Twenty-Five, modèle « Archives : Livre » (barre du haut « Archives : Livre · Modèle », bouton Enregistrer actif) : sous l’en-tête Site démo (menu Accueil, À propos, Contact), le Titre d’archive (texte d’exemple « Type d’archive : nom ») et « Description du terme », un bloc Code court sélectionné contient [wpc_cpt_terms] à la place de la Boucle de requête ; le pied de page du thème suit ; la barre latérale, onglet Bloc, présente le bloc Code court (« Insérer un élément personnalisé additionnel avec un code court WordPress. ») et le fil d’Ariane indique Modèle > Groupe > Code court.
Dans le modèle d’archive, le bloc Code court remplace la Boucle de requête

Pour maîtriser l’adresse de cette archive, voyez comment définir l’URL d’archive d’un Custom Post Type.

Avatar de Aurélien Denis

Protégez votre site WordPress

Vos données sont précieuses,
ne prenez pas le risque de tout perdre.
Confiez-moi votre maintenance !

Ces articles devraient t’intéresser…

15 réponses

  1. Avatar de Jonathan
    Jonathan

    Bonjour Aurélien

    Je cherche à faire la même chose mais sur les pages des mots clés ( pages des tags). Pour le moment, wordpress affiche tous les articles qui contiennent chaque mots clés. Moi, je voudrai qu’il m’affiche les articles mais classés par catégorie.

    Exemple :

    Page d’un mot clé :

    Catégorie 1
    Contenu A
    Contenu B
    Contenu C
    Catégorie 2
    Contenu A
    Contenu B
    Contenu C
    Catégorie 3
    Contenu A
    Contenu B
    Contenu C

    De plus, j’aimerais également qu’il ne m’affiche pas les articles provenant des sous-catégories d’une catégorie en particulier. est ce possible de faire ca ?

    Merci pour votre aide

  2. Avatar de A
    A

    Bonjour,

    J’ai créé une page template comme celle-ci, mais rien ne s’affiche est-ce normal ?

    (a qui puis-je envoyer le lien en MP pour vérification ?)

    Phil

    1. Avatar de Aurélien Denis
      Aurélien Denis

      Ce tutoriel est plutôt technique et je vous invite à vérifier chaque ligne de code en fonction de votre site au préalable.

  3. Avatar de Aurélien Denis
    Aurélien Denis

    @Julio : je pense qu’il y a une coquille sur le foreach non ? Et sinon oui ça fonctionne nickel. 🙂

  4. Avatar de Grégoire Noyelle
    Grégoire Noyelle

    Merci à Aurélien et à Julio. J’ai eu un casse tête similaire avec des query sur mesure sur les archives des custom taxonomy et c’est le pre_get_posts qui m’a sauvé.

    1. Avatar de Aurélien Denis
      Aurélien Denis

      Oui très pratique ! Et différentes techniques existent.

      P.S : oui oui 😉

  5. Avatar de Ligo (@SiloweNet)
    Ligo (@SiloweNet)

    Merci les gars!

    @Julien, pourquoi serait suicidaire de mettre tous les articles sur une seule page? (pour exemple, une page dédiée à une liste d’articles d’une catégorie spécifique)…

  6. Avatar de Julio Potier
    Julio Potier

    Hello !
    As-tu remarqué que celà ne charge que les X articles pour chaque terme (X=selon les options de lecture de ton WP) ? Tu as oublié de mettre « -1 » en « posts_per_page » !

    Aussi, comme tu es sur une page d’archives, WordPress a déjà une requête qu’il a été faite pour toi, la « main query » et tu ne l’utilises pas !?

    Puis, tu vas faire une WP_Query pour chaque terme ? Omg :O

    Aussi, n’oublie pas que si tu souhaites afficher TOUS les articles sur une page, c’est assez suicidaire, ça va si tu en a peu sinon … PAF la pastèque.

    Pour en revenir à la main query, je pense qu’il serait mieux de toucher à pre_get_posts, check si on est en is_home pour cette main query, dans ce cas mettre -1 en posts_per_page, ce qui va te permettre d’avoir tous les articles dans la page d’archive.

    Maintenant, plus aucune query à faire, TOUS tes articles sont là, à toit de filtrer l’affichage à base de has_term.
    Et enfin, tout ceci manque de cache, où est le transient ? A chaque reload de chaque visiteur je dois tout re-récup ? wowowow.

    Bon, je viens de tester le code et le faire à ma sauce : http://pastebin.com/fwnMEgYw
    Qu’en dis-tu ? Il prends en charge les transients (je suis passé de 335 reqs à 61), 0 WP_Query, pas de reset query car pas de the_post qui écrase la globale, chacune des fonctions utilisée peut prendre un post ou un ID en param, et ça liste vraiment tout grâce à ce hook à mettre en plugin/mu-plugin/functions.php (au choix).

    (et code commenté ^^)

    Bisous

    1. Avatar de Aurélien Denis
      Aurélien Denis

      Hey, pour tout te dire je me demandais quand tu allais enfin commenter mon code ! 🙂

      Je vais tester cela dès demain vu qu’il s’agit d’un projet en cours héhé. Oui le posts_per_page je pensais l’avoir mis mais non…

      Merci l’ami !

    2. Avatar de Julio Potier
      Julio Potier

      auto réponse, il faut lire « is_post_type_archive( $post_type ) » au lieu de « is_home() » et le pastebin du hook ici : http://pastebin.com/Y9ywcCHp

      1. Avatar de Aurélien Denis
        Aurélien Denis

        Tout fonctionne au poil ! Merci bien !

        1. Avatar de Julio Potier
          Julio Potier

          Reste un point : vider le transient sinon le cache ne se régen pas. Il faut le faire au save_post, delete_post, save_term, delete_term, etc etc

          1. Avatar de Aurélien Denis
            Aurélien Denis

            Justement je me suis aperçu que effectivement ça ne vidait pas le cache… tu mets à jour ton code ?

          2. Avatar de Julio Potier
            Julio Potier

            Voilà quelques hooks qui devraient faire l’affaire.
            http://pastebin.com/9ABV2ByF
            Aussi, après réflexion, il faut mettre ce transient en cache timé pendant 1 an, le but est qu’il ne soit chargé qu’à la demande et pas non stop (autoload) car il pourrait alors être assez gros et faire grimper la mémoire PHP utilisée pour rien.
            « 365*DAY_IN_SECONDS » pour le timeout 🙂

            Pas trop décousue comme réponse ? ^^