Des options de tri pour vos pages d’archive et de taxonomie

Aujourd’hui, nous allons voir comment concevoir un système de filtre d’article, en front-office.

Il permettra aux utilisateurs qui visitent une page d’archive ou de taxonomie d’isoler uniquement les articles qui les intéresse, et de les trier selon leurs propres critères.

Je l’utilise relativement souvent sur les thèmes que je réalise, et particulièrement sur des sites autres que blogs.

wordpress switch contents
Laissez vos utilisateurs filtrer vos contenus

Il y a plusieurs méthodes pour réaliser cela, mais celle qui va suivre à la particularité de ne pas dupliquer les urls. Elle ne créer pas de nouvelles ramifications dans la structure du site, et ne dilue pas le « jus » SEO de vos pages.

Et puis elle est simple à utiliser…

Tri d’articles par metadonnée

Pour illustrer la mise en place d’un tel système, je vais prendre pour exemple le site d’une agence immobilière.

Sur la page où sont listés les biens à vendre, il peut-être intéressant de laisser à l’utilisateur le choix  :

On imagine que les annonces correspondent à un Custom Post Type, et que le prix de chaque bien y est enregistré en tant que meta-donnée. La date de parution, elle, renvoie nativement à la publication de l’article WordPress.

On va mettre en place notre système de tri en trois étapes :

  1. créer un formulaire pour l’utilisateur
  2. retenir ses préférences d’affichage dans une variable de session
  3. puis modifier la requête en fonction de ses choix

Les codes qui vont suivre doivent être déposés dans le fichier functions.php de votre thème.

Un formulaire pour organiser l’affichage de vos contenus.

Pour permettre à l’internaute de choisir un mode d’affichage, nous allons concevoir un formulaire.

Vous allez probablement devoir le réutiliser à plusieurs endroits de votre thème. Il est donc préférable de le créer dans une fonction que nous appellerons là où il faut (dans la page d’archive, de taxonomie…).

Il n’est pas nécessaire de préciser l’action du formulaire ; lors de sa soumission, il se rechargera sur la même page.

function switcher_session() {
  ?>
  <form method="post" class="switcher">
      <!-- ici nous allons mettre les options -->
  </form>
  <?php
}

Le formulaire n’a besoin que de deux entrées :

Le changement de valeur d’un de ces éléments soumet automatiquement le formulaire grâce à l’attribut onchange= »this.form.submit() ».

//la fonction du formulaire et ses selects...
function switcher_session() {
  ?>
  <form method="post" class="switcher">
      <p><label for="post-order-by">Trier selon :</label>
        <select id="post-order-by" name="post-order-by" onchange="this.form.submit()">
            <option value="date">la date</option>
            <option value="price">le prix</option>
        </select></p>

        <p><label for="post-order">Ordre de tri :</label>
        <select id="post-order" name="post-order" onchange="this.form.submit()">
            <option value="DESC">Décroissant</option>
            <option value="ASC">Croissant</option>
        </select></p>
  </form>
  <?php
}

Il faut maintenant exécuter la fonction switcher_session() dans le template d’archive de notre custom post type, et éventuellement de taxonomie. Je vous suggère d’appeler fonction entre if( have_posts() ) et while( have_posts() ).

Si on visite la page, le formulaire y apparait maintenant.

Un fois qu’il sera validé, il faudra sauvegarder les informations quelque part. Le plus logique dans le cas présent est de les stocker dans la variable de session. Ainsi les informations seront isolées pour chaque utilisateur et persisteront tant qu’il ne ferme pas son navigateur.

Enregistrer les options dans la session

Pour créer et mettre à jour une variable de session, il faut lancer une fonction qui s’exécutera lors de l’initialisation de WordPress.

On utilise donc le hook init.

add_action( 'init', 'switch_session' );
function switch_session() {
    // notre code ira ici
}

L’enregistrement des choix de l’utilisateur se fait en trois étapes :

  1. on ouvre une session
  2. si le formulaire a été soumis, on met à jour la variable
  3. si la variable n’existe pas (et que donc le formulaire n’a pas encore été soumis), on créer la variable avec une valeur par défaut
add_action( 'init', 'switch_session' );
function switch_session() {
    // J'initialize la session
    if( ! session_id() )
        session_start();

    // Si le switcher à été utilisé, on change la valeur
    if( isset( $_POST[ 'post-order' ] ) ) {
        $_SESSION[ 'post-order' ] = ( 'ASC' == $_POST['post-order'] ) ? 'ASC' : 'DESC';
    }
    
    if( isset( $_POST[ 'post-order-by' ] ) ) {
        $_SESSION[ 'post-order-by' ] = ( 'price' == $_POST['post-order-by'] ) ? 'price' : 'date';
    }

    // S'il n'y a pas d'ordre de défini, on en met un par défaut
    if( ! isset( $_SESSION[ 'post-order' ] ) )
        $_SESSION[ 'post-order' ] = 'ASC';

    if( ! isset( $_SESSION[ 'post-order-by' ] ) )
        $_SESSION[ 'post-order-by' ] = 'price';
        
}

Il est important de s’arranger pour que les valeurs passées en post ne soient pas ajoutées telles quelles dans la variable. Simplement pour des raisons de sécurité…

WordPress à un comportement particulier avec les variables de session ; le code présenté aura besoin, dans certains cas normalement assez rares, d’être adapté.

À ce stade, les choix de l’utilisateur sont enregistrés. Par contre ils ne s’afficheront pas dans le formulaire qui vient de se recharger.

Il faut les récupérer via la session puis utiliser la fonction selected() de WordPress pour sélectionner l’option en cours.

//le switcher complet
function switcher_session() {
    $current_order = $_SESSION[ 'post-order' ];
    $current_order_by = $_SESSION[ 'post-order-by' ];
    ?>
    <form method="post" class="switcher">
        <p><label for="post-order-by">Trier selon :</label>
        <select id="post-order-by" name="post-order-by" onchange="this.form.submit()">
            <option value="date" <?php selected( $current_order_by, 'date' ); ?>>la date</option>
            <option value="price" <?php selected( $current_order_by, 'price' ); ?>>le prix</option>
        </select></p>

        <p><label for="post-order">Ordre de tri :</label>
        <select id="post-order" name="post-order" onchange="this.form.submit()">
            <option value="DESC" <?php selected( $current_order, 'DESC' ); ?>>Décroissant</option>
            <option value="ASC" <?php selected( $current_order, 'ASC' ); ?>>Croissant</option>
        </select></p>
    </form>
    <?php
}

Altérer la requête WordPress

Maintenant l’étape ultime : overider la requête.

Grâce au hook pre_get_posts, c’est quelque chose d’assez simple à faire.
Il faut y exécuter une fonction qui va filtrer les argument de WP_Query.

Tout d’abord on vérifie qu’il s’agit de la bonne requête à surcharger (page d’archive du post type ciblé, requête principale en front), puis on va chercher nos valeurs sauvegardées en session.

// L'overide de la requête
add_action( 'pre_get_posts', 'switch_output_order' );
function switch_output_order( $q ) {
    // Si on est en front et qu'il s'agit de la requête principale de la page d'archive
    if( ! is_admin() && $q->is_main_query() && is_post_type_archive( 'cpt' ) ) {

        // tri par prix
        if( 'price' == $_SESSION[ 'post-order-by' ] ) {
            $q->set( 'meta_key', '_price' );
            $q->set( 'orderby', 'meta_value_num');
        }
        /* 
        * Par défaut, WordPress tri par date, donc il n'y a pas besoin d'effectuer'
        * un autre overide pour le tri par date 😉
        *
        * Sauf si, par exemple, vous voulez trier selon une date 
        * autre que la publication de l'article...
        */

        // Tri croissant ou décroissant
        $q->set( 'order', $_SESSION[ 'post-order' ] );
    }

La variable $q représente la WP_Query ; on utilise la méthode set() pour en redéfinir certaines parties.

Si la valeur de post-order-by est « price », alors on ajoute à la requête les conditions meta_key = _price (qui correspond à la metadonnée) et orderby = meta_value_num.

Si post-order-by est défini à date, alors nous n’avons rien à faire car WordPress tri les articles par date par défaut.

Puis on défini order selon la valeur de post-order.

Il n’y a pas besoin de tester nos variables de session, puisque l’on sait qu’elles ont été correctement définies lors de l’init.

Le code complet

Notre système de tri est maintenant pleinement fonctionnel 🙂

Re-voici le code dans son intégralité :

add_action( 'init', 'switch_session' );
function switch_session() {
    // J'initialize la session
    if( ! session_id() )
        session_start();

    // Si le switcher à été utilisé, on change la valeur
    if( isset( $_POST[ 'post-order' ] ) ) {
        $_SESSION[ 'post-order' ] = ( 'ASC' == $_POST['post-order'] ) ? 'ASC' : 'DESC';
    }

    if( isset( $_POST[ 'post-order-by' ] ) ) {
        $_SESSION[ 'post-order-by' ] = ( 'price' == $_POST['post-order-by'] ) ? 'price' : 'date';
    }

    // S'il n'y a pas d'ordre de défini, on en met un par défaut
    if( ! isset( $_SESSION[ 'post-order' ] ) )
        $_SESSION[ 'post-order' ] = 'ASC';

    if( ! isset( $_SESSION[ 'post-order-by' ] ) )
        $_SESSION[ 'post-order-by' ] = 'price';

}


// L'overide de la requête
add_action( 'pre_get_posts', 'switch_output_order' );
function switch_output_order( $q ) {
    // Si on est en front et qu'il s'agit de la requête principale de la page d'archive
    if( ! is_admin() && $q->is_main_query() && is_post_type_archive( 'cpt' ) ) {

        // tri par prix
        if( 'price' == $_SESSION[ 'post-order-by' ] ) {
            $q->set( 'meta_key', '_price' );
            $q->set( 'orderby', 'meta_value_num');
        }
        /* 
        * Par défaut, WordPress tri par date, donc il n'y a pas besoin d'effectuer'
        * un autre overide pour le tri par date 😉
        *
        * Sauf si, par exemple, vous voulez trier selon une date 
        * autre que la publication de l'article...
        */

        // Tri croissant ou décroissant
        $q->set( 'order', $_SESSION[ 'post-order' ] );
    }

    // On retourne la requête
    return $q;
}

// Le code du switcher
function switcher_session() {
    $current_order = $_SESSION[ 'post-order' ];
    $current_order_by = $_SESSION[ 'post-order-by' ];
    ?>
    <form method="post" class="switcher">
        <p><label for="post-order-by">Trier selon :</label>
        <select id="post-order-by" name="post-order-by" onchange="this.form.submit()">
            <option value="date" <?php selected( $current_order_by, 'date' ); ?>>la date</option>
            <option value="price" <?php selected( $current_order_by, 'price' ); ?>>le prix</option>
        </select></p>

        <p><label for="post-order">Ordre de tri :</label>
        <select id="post-order" name="post-order" onchange="this.form.submit()">
            <option value="DESC" <?php selected( $current_order, 'DESC' ); ?>>Décroissant</option>
            <option value="ASC" <?php selected( $current_order, 'ASC' ); ?>>Croissant</option>
        </select></p>
    </form>
    <?php
}

D’autres possibilités de filtres

Tout fonctionne, mais aurait pu aller beaucoup plus loin…

Filtrer les articles par terme de taxinomie

Reprenons notre exemple d’agence immobilière, et imaginons que l’on veuille filtrer les biens selon qu’ils soient des appartements, des villas, des maisons…

Il suffit simplement d’ajouter un select dans le formulaire, et d’y lister les termes de la taxonomie genre-bien (la taxonomie relative à nos type de biens).

//on ajoute ce champ de formulaire
$genre = $_SESSION[ 'post-genre' ];
$terms = get_terms( 'genre-bien' );
if( ! is_wp_error( $terms ) {
?>
    <p><label for="post-genre">Types de biens :</label>
    <select id="post-genre" name="post-genre" onchange="this.form.submit()">
        <option value="">Tous</option>
        <?php
        foreach( $terms as $term )
            echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( $term->slug, $genre, false ) . '>' . esc_html( $term->name ) . '</option>';
        ?>
    </select></p>
<?php
}

Ensuite on sauvegarde les entrées souhaitées, en vérifiant ce qui a été saisi :

//dans switch_session() on sauvegarde la valeur de post-genre,
//on sinon on en défini une par défaut
if( isset( $_POST[ 'post-genre' ] ) ) {
    $terms = get_terms( 'genre-bien' );
    $_SESSION[ 'post-genre' ] = ( in_array( $_POST[ 'post-genre' ], wp_list_pluck( $terms, 'slug' ) ) ) ? $_POST[ 'post-genre' ] : false;
}

if( ! isset( $_SESSION[ 'post-genre' ] ) )
    $_SESSION[ 'post-genre' ] = false;

… puis on personnalise la requête.

Si plusieurs requêtes de taxonomie doivent être réalisées, il faudra composer la tax_query en plusieurs étapes (mais ici ce n’est pas le cas ;-) )

//dans switch_output_order
//dans la condition
if( false !== $_SESSION[ 'post-genre' ] ) {
    $q->set( 'tax_query', array(
        'relation' => 'AND',
        array(
            'taxonomy' => 'genre-bien',
            'field'    => 'slug',
            'terms'    => $_SESSION[ 'post-genre' ]
            )
        ) );
}

Filtrer les contenus en fonction de metadonnées

On continu avec notre agence immobilière !

Chaque bien de l’agence possède (ou non) certains équipements. Certains clients seront intéressés uniquement par les habitations qui comportent une piscine, une cuisine aménagée ou autre…

Ces différents équipements ont été entrés dans WordPress en tant que meta-donnés des hébergements.

Nous allons ajouter une checkbox « piscine » dans le formulaire :

//on ajoute ce champ de formulaire
$piscine = $_SESSION[ 'post-piscine' ];
?>
    <p><label for="post-piscine">Piscine</label>
    <input type="hidden" value="0" name="post-piscine">
    <input type="checkbox" value="1" id="post-piscine" name="post-piscine" <?php checked( true, $piscine ); ?>></p>
<?php

Vu que la checkbox qui ne sera pas cochée ne sera pas soumise en post, il faut insérer un input hidden avec le même name juste avant, mais avec une valeur différente.

Ensuite on sauvegarde l’option dans une session.

//dans switch_session() on sauvegarde la valeur de post-piscine,
//on sinon on en défini une par défaut
if( isset( $_POST[ 'post-piscine' ] ) ) {
    $_SESSION[ 'post-piscine' ] = ( 1 == $_POST[ 'post-piscine' ] ) ? true : false;
}

if( ! isset( $_SESSION[ 'post-piscine' ] ) )
    $_SESSION[ 'post-piscine' ] = false;

… et pour finir on défini une nouvelle condition dans le pre_get_posts qui ajoutera une meta_query, si besoin.

//dans switch_output_order
//dans la condition
if( false !== $_SESSION[ 'post-piscine' ] ) {
    $q->set( 'meta_query', array(
        array(
            'key' => '_equipement',
            'value'    => 'piscine',
            'compare' => '='
            )
        ) );
}

Voilà, vous pouvez maintenant réaliser vos propres filtres d’article en front office.

Le gros avantage de cette méthode est qu’elle ne créer pas plusieurs pages avec des contenus quasi-similaires. C’est particulièrement intéressant au niveau de l’optimisation pour les moteurs de recherche.

N’hésitez pas à me dire ce que vous en pensez !

Contacter l'auteur :

willy bahuaud

Je suis Willy Bahuaud, développeur spécialiste de WordPress. Mon travail est de créer d’intervenir sur la partie « technique » des projets WordPress de mes clients. Je développe des extensions (espaces membres, géolocalisation, annuaires…) pour étendre les fonctionnalités d’un site au delà de ce que WordPress ou le thème en place ne le permettent. J'interviens aussi régulièrement sur la migration de contenus, l’optimisation de thème, la création de passerelles de données, l’adaptation de plugins premium et la création de sites .
Vous pouvez me contacter pour me faire part de vos projets.

10 commentaires

  1. Par TweetPressFr — Il y a 3 années
    Excellent tuto, merci ! Le code dans un plugin de fonctionnalité et un petit echo dans les fichiers du thème et hop là !

    Ce que j’aime est que cela peut servir dans d’autres contextes également.

    En revanche toute la documentation que j’ai pu lire sur les sessions dans WordPress recommande cet ajout dans le hook init :

    
    if( !session_id())
                session_start();
    

    Il faudrait aussi un session_destroy (); sur les hooks wp_logout et wp_login au cas où un utilisateur se logue avec différents identifiants.

  2. Par Willy Bahuaud — Il y a 3 années
    Oui tout à fait, on peut s’en servir dans de nombreux contextes. Sur un site récent, je m’en suis servi pour laisser l’utilisateur choisir s’il était une entreprise ou un particulier. Les contenus restaient à peu de chose près identiques, seuls les descriptions de catégories changeaient… Il aurait été dommage de dupliquer tout le site 🙂

    Pour le session_start(), il est indispensable sinon la variable de session ne fonctionne pas.

    Par contre je ne me suis pas embêté ici avec le session_destroy()…

  3. Par Ricardo — Il y a 3 années
    Si je comprend bien, c’est le fait de retenir les préférences dans le session qui empêche la duplication?
  4. Par Willy Bahuaud — Il y a 3 années
    En fait, il y a plusieurs façon de proposer des systèmes de tri en front-office.
    La plupart de ces systèmes (dont les taxonomies WordPress, ou les archives datées) créent des pages avec des URLs différents et des contenus très similaires (mêmes articles et mêmes descriptions).

    Alors qu’un système de session, lui, ne créer pas de nouvelles URLs. C’est juste ça.

  5. Par Intelisis — Il y a 3 années
    Un tuto comme je les aime.

    J’avais testé avec un système de session de proposer sur une home des contenus selon un profil pré-déterminé, le moteur visitant celui par défaut.

    En gros je chargeais les catégories selon les profils. Il me reste plus qu’à intégrer ces filtres et la personnalisation est complète et propre.
    Avec ce code le filtre est généré à la volée, le moteur n’ayant pas de session (fin…) il y verra un seul et unique site et contenu. Dites si je me trompe !

  6. Par Pierre — Il y a 3 années
    Super article avec plein de références .. j’ai appris pas mal de truc !!

    J’ai bien aimé le check :
    ( in_array( $_POST[ ‘post-genre’ ], wp_list_pluck( ‘slug’, $terms ) ) )

    Un peu noob, mais j’aurais ajouté un wp_nonce_field dans le formulaire .. Bonne ou mauvaise idée ?

    Autre question, y aurait-il un intérêt à passer les paramètres en paramètre WP ? En tout cas, j’ai cherché à le faire pour un cas perso .. sans succès !!

  7. Par Willy Bahuaud — Il y a 3 années
    Merci , je suis content que cet article t’ai appris des trucs ;-)

    Le check de sécurité, c’est Julio de Boite a Web qui me l’a montré. Très pratique, et pas très long, j’aime bien aussi…

    Pour le nonce field, ici je ne suis pas persuadé qu’il y en ai besoin : quel est le risque ? Une CSRF pour prendre le contrôle du tri des articles d’un internaute ? 😛
    Mais après, oui, c’est possible.

    On pourrai passer les paramètres à WP, mais dans des cas plus complexes, et cela sera le thème d’un futur article 🙂

  8. Par Karine DT — Il y a 3 années
    Tout d’abord; merci beaucoup pour cet article ! Je suis débutante, mais j’apprends énormément grâce à ce genre d’article !

    J’essayais de faire en sorte que l’utilisateur puisse sélectionner plusieurs checkbox de métadonnés à la fois pour la recherche. Pourriez-vous m’aiguiller vers une solution ?

    Merci !!!

  9. Par Clément — Il y a 3 années
    Concernant l’altération de la requête par le prix, comment récupére-ton la valeur, qui doit être :

    $q->set( ‘orderby’, ‘meta_value_num’);

    j’ai du zapper un truc..
    Merci et bonne continuation

  10. Par Crunch — Il y a 2 années
    Merci pour ce tutoriel. J’ai appris pas mal de choses techniques qui me manquaient jusque la ;-)

    Par contre, j’ai une petite question : je souhaiterais initialiser tous les paramètres appliqués d’une seule traite (plusieurs taxo filtrées) pour avoir une page clean à la demande même si les données s’effacent une fois le navigateur fermé. Comment puis-je procéder ?

Commenter