Système de filtres avancés pour custom post type

Comment créer un système de filtre avancés pour vos custom post type ?

Cet article fait suite aux deux précédents articles sur les systèmes de filtres et bouton ajax load more dans les articles et les custom post type.

Mon système de filtre avancé

Ici, je poste un autre exemple avec un système de filtre un peu plus avancé, avec deux taxonomies :  la catégorie et le niveau. Mon arborescence et la structure des fichiers est la même que pour les deux derniers articles cités. Je ne poste donc ici que la partie de code utile.

Voici le rendu souhaité de mes filtres : deux rangées, une pour chaque taxonomie. Chaque rangée possède un bouton de réinitialisation, pour que l’utilisateur puisse annuler le filtre d’une rangée tout en gardant un autre filtre activé.

Mon custom post type

Je commence donc par créer mon custom post type, ainsi que les taxonomies associées.

/*AVIS*/
function cc_cpt_review() {
/* Formation */
$labels = array(
'name' => _x('Avis', 'Post Type General Name', 'text_domain'),
'singular_name' => _x('Avis', 'Post Type Singular Name', 'text_domain'),
'menu_name' => _x('Avis', 'text_domain'),
'name_admin_bar' => _x('Avis', 'text_domain'),
'all_items' => _x('Tous les Avis', 'text_domain'),
'add_new_item' => _x('Ajouter un nouvel Avis', 'text_domain'),
'add_new' => _x('Ajouter', 'text_domain'),
'new_item' => _x('Nouvel avis', 'text_domain' ),
'edit_item' => _x('Editer l\'avis', 'text_domain'),
'update_item' => _x('Mettre à jour l\'avis', 'text_domain'),
'view_item' => _x('Voir l\'avis', 'text_domain'),
'search_items' => _x('Rechercher un avis', 'text_domain'),
'not_found' => _x('Aucun avis trouvé', 'text_domain'),
'not_found_in_trash' => _x('Aucun avis trouvé dans la corbeille', 'text_domain'),
);

$args = array(
'label' => _x('Avis', 'text_domain'),
'description' => _x('Avis', 'text_domain'),
'labels' => $labels,
'supports' => array('title', 'editor', 'thumbnail', 'comments', 'revisions', 'custom-fields'),
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'menu_position' => 5,
'menu_icon' => 'dashicons-admin-home',
'show_in_admin_bar' => true,
'show_in_nav_menus' => true,
'can_export' => true,
'has_archive' => false,
'exclude_from_search' => false,
'publicly_queryable' => true,
'query_var' => 'avis',
'rewrite' => array( 'slug' => 'tous-les-avis' ),
'capability_type' => 'post',
'show_in_rest' =>'true',
);
register_post_type('avis', $args);
}
add_action('init', 'cc_cpt_review', 10);



function cc_add_taxonomies_review() {
// Catégorie de série
$labels_cat_avis = array(
'name' => _x( 'Catégories de l\'avis', 'taxonomy general name'),
'singular_name' => _x( 'Catégories de l\'avis', 'taxonomy singular name'),
'search_items' => __( 'Rechercher une catégorie'),
'popular_items' => __( 'Catégories populaires'),
'all_items' => __( 'Toutes les catégories'),
'edit_item' => __( 'Editer une catégorie'),
'update_item' => __( 'Mettre à jour une catégorie'),
'add_new_item' => __( 'Ajouter une nouvelle catégorie'),
'new_item_name' => __( 'Nom de la nouvelle catégorie'),
'add_or_remove_items' => __( 'Ajouter ou supprimer une catégorie'),
'choose_from_most_used' => __( 'Choisir parmi les catégories les plus utilisées'),
'not_found' => __( 'Pas de catégories trouvée'),
'menu_name' => __( 'Catégorie d\'avis'),
);

$args_cat_avis = array(

'hierarchical' => true,
'labels' => $labels_cat_avis,
'show_ui' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'categories-avis' ),
);

register_taxonomy( 'categories-avis', 'avis', $args_cat_avis );
}
add_action( 'init', 'cc_add_taxonomies_review', 0 );

function cc_avis_level_taxonomies() {

$labels_level_avis = array(
'name' => _x( 'Niveaux de l\'avis', 'taxonomy general name'),
'singular_name' => _x( 'Niveau de l\'avis', 'taxonomy singular name'),
'search_items' => __( 'Rechercher un avis'),
'popular_items' => __( 'Niveaux populaires'),
'all_items' => __( 'Tous les Niveaux'),
'edit_item' => __( 'Editer un niveau'),
'update_item' => __( 'Mettre à jour un niveau'),
'add_new_item' => __( 'Ajouter un nouveau niveau'),
'new_item_name' => __( 'Nom de le nouveau niveau'),
'add_or_remove_items' => __( 'Ajouter ou supprimer un niveau'),
'choose_from_most_used' => __( 'Choisir parmi les niveaux les plus utilisés'),
'not_found' => __( 'Pas de niveau trouvé'),
'menu_name' => __( 'Niveau de l\'avis'),
);

$args_level_avis = array(

'hierarchical' => true,
'labels' => $labels_level_avis,
'show_ui' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'niveau-avis' ),
);

register_taxonomy( 'niveau-avis', 'avis', $args_level_avis );
}
add_action( 'init', 'cc_avis_level_taxonomies', 0 );

Mon modèle de page ou page d’archive

Dans ma page d’archive personnalisée ou modèle de page, je crée deux formulaires de filtrage, un pour chacune de mes taxonomies.

<div class="flex-row">
<form action="#" method="POST" id="avis_filters" class="flex-row">
<div class="flex-row">
<input type="radio" value="all_categ" id="all_categ" class="avis_filter" name="category_avis_filters"><label for="all_categ">Toutes</label>

<?php
if( $terms = get_terms( array( 'taxonomy' => 'categories-avis' ) ) ) :
foreach( $terms as $term ) :
echo '<input type="radio" id="' . $term->term_id . '" value="' . $term->term_id . '" name="category_avis_filters" class="avis_filters"/><label for="' . $term->term_id. '">' . $term->name . '</label>';
endforeach;
endif;
?>
</div>
<div class="flex-row">
<input type="radio" value="all_niveau" id="all_niveau" class="avis_filter" name="niveau_avis_filters"><label for="all_niveau">Toutes</label>

<?php
if( $terms = get_terms( array( 'taxonomy' => 'niveau-avis' ) ) ) :
foreach( $terms as $term ) :
echo '<input type="radio" id="' . $term->term_id . '" value="' . $term->term_id . '" name="niveau_avis_filters" class="avis_filters"/><label for="' . $term->term_id. '">' . $term->name . '</label>';
endforeach;
endif;?>
</div>
<!-- required hidden field for admin-ajax.php -->
<input type="hidden" name="action" value="ccavisfilter" />
</form>
</div>

<?php
// le début de ma boucle
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args = array(
'post_type' =>'avis',
'posts_per_page' =>5,
'paged' => $paged
);
$query = new WP_Query( $args ); ?>
<?php if ( $query->have_posts() ) : ?>
<div id="cc_avis_wrap" class="flex-row">
<?php while ( $query->have_posts() ) : $query->the_post();
$taxonomies = array('categories-avis','niveau-avis');
$termsArray = wp_get_object_terms($post->ID, $taxonomies, array( 'orderby' => 'term_order' ) );
$termsString = "";
foreach ( $termsArray as $term ) {
$termsString .= $term->slug.' ';
}

Tout à la fin de mon fichier, j’ajoute mon bouton ajax load more :

<?php	

if ( $query->max_num_pages > 1 ) :
echo '<div class="loadmore_block"><div id="cc_avis_loadmore">Afficher plus d\'avis</div></div>'; // you can use <a> as well
endif;

Juste après j’appelle mon fichier js qui va contenir mes fonctions pour les filtres et le bouton loadmore, et je défini ici mes variables pour l’ajax;

<script>
var posts_myajax_avis = '<?php echo serialize( $query->query_vars ) ?>',
current_page_myajax_avis = 1,
max_page_myajax_avis = <?php echo $query->max_num_pages ?>
</script>
<script src="<?php bloginfo('template_url')?>/js/load-more-avis.js"></script>

Les fonctions php

Dans un fichier à part, que j’appellerai avis-query.php, je crée ma fonction pour les filtres. Tout est décomposé ici pour rendre les choses plus claires. Je crée mes conditions pour chaque cas d’utilisation.

<?php 
add_action('wp_ajax_ccavisfilter', 'cc_avis_filter_function');
add_action('wp_ajax_nopriv_ccavisfilter', 'cc_avis_filter_function');



function cc_avis_filter_function(){

if( isset( $_POST['all_categ'] ) || isset( $_POST['all_niveau'] )){
$niveau = get_terms( array( 'taxonomy' => 'niveau-avis' ) );
$categ = get_terms( array( 'taxonomy' => 'categories-avis' ) );
$args=array(
'post_type' => 'avis',
'posts_per_page' => 5,
'tax_query' => array(
'relation' => 'OR',
array(
'taxonomy' => 'categories-avis',
'field' => 'term_id',
'terms' => $categ,
),
array(
'taxonomy' => 'niveau-avis',
'field' => 'term_id',
'terms' => $niveau ,
),
),
);

}
if( isset( $_POST['all_categ'] ) && isset( $_POST['all_niveau'] )){
$niveau = get_terms( array( 'taxonomy' => 'niveau-avis' ) );
$categ = get_terms( array( 'taxonomy' => 'categories-avis' ) );
$args=array(
'post_type' => 'avis',
'posts_per_page' => 5,
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'categories-avis',
'field' => 'term_id',
'terms' => $categ,
),
array(
'taxonomy' => 'niveau-avis',
'field' => 'term_id',
'terms' => $niveau ,
),
),
);

}
// for taxonomies / categories
if( isset( $_POST['category_avis_filters'] ) || isset( $_POST['niveau_avis_filters'] )){
$args=array(
'post_type' => 'avis',
'posts_per_page' => 5,
'tax_query' => array(
'relation' => 'OR',
array(
'taxonomy' => 'categories-avis',
'field' => 'term_id',
'terms' => $_POST['category_avis_filters'],
),
array(
'taxonomy' => 'niveau-avis',
'field' => 'term_id',
'terms' => $_POST['niveau_avis_filters'],
),
),
);

}
if( isset( $_POST['category_avis_filters'] ) && isset( $_POST['niveau_avis_filters'] )){
$args=array(
'post_type' => 'avis',
'posts_per_page' => 5,
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'categories-avis',
'field' => 'term_id',
'terms' => $_POST['category_avis_filters'],
),
array(
'taxonomy' => 'niveau-avis',
'field' => 'term_id',
'terms' => $_POST['niveau_avis_filters'],
),
),
);

}




$query = new WP_Query( $args );
if ( $query->have_posts() ) :

ob_start();
while ( $query->have_posts() ) : $query->the_post();

$taxonomies = get_object_taxonomies( array( 'post_type' => 'avis') );
$termsArray = get_the_terms( $post->ID, $taxonomies );
$termsString = "";
foreach ( $termsArray as $term ) {
$termsString .= $term->slug.' ';
}
?>

<?php // ici le html/php pour mes articles ?>

<?php
endwhile;
$posts_html = ob_get_contents();
ob_end_clean();
else:
$posts_html = '<p>Aucun résultat.</p>';
endif;
echo json_encode( array(
'posts' => json_encode( $query->query_vars ),
'max_page' => $query->max_num_pages,
'found_posts' => $query->found_posts,
'content' => $posts_html
) );
die();
}

Ici je fais en sorte qu’on puisse sélectionner une catégorie et un niveau par catégorie, OU, un niveau et une catégorie par niveau.

Les fonction js

Dans un fichier js à part, que je nomme load-more-avis.js, je crée donc mes deux fonctions : une pour le bouton load more, et l’autre pour les filtres.

jQuery(function($){

/* LOAD MORE FUNCTION ON avis ARCHIVE PAGE */
$('#cc_avis_loadmore').click(function(){

data = {
'action': 'loadmoreavisbutton',
'query': posts_myajax_avis,
'page' : current_page_myajax_avis
};

$.ajax({
url : '/numgrade/wp-admin/admin-ajax.php', // AJAX handler
data : data,
type : 'POST',
beforeSend : function ( xhr ) {
$('#cc_avis_loadmore').text('Recherche...'); // some type of preloader
},
success : function( posts ){
if( posts ) {

$('#cc_avis_loadmore').text( 'Afficher plus d\'avis' );
$('#cc_avis_wrap').append( posts ); // insert new posts
current_page_myajax_avis++;

if ( current_page_myajax_avis == max_page_myajax_avis )
$('#cc_avis_loadmore').hide(); // if last page, HIDE the button

} else {
$('#cc_avis_loadmore').hide(); // if no data, HIDE the button as well
}
}
});
return false;
});
/* FILTERING FUNCTION ON avis ARCHIVE PAGE */
$('#avis_filters').change(function(){
$.ajax({
url : '/numgrade/wp-admin/admin-ajax.php',
data : $('#avis_filters').serialize(), // form data
dataType : 'json', // this data type allows us to receive objects from the server
type : 'POST',

success : function( data ){
// when filter applied:
// set the current page to 1
current_page_myajax_avis = 1;
// set the new query parameters
posts_myajax_avis = data.posts;
// set the new max page parameter
max_page_myajax_avis = data.max_page;
// change the button label back
// insert the posts to the container
$('#cc_avis_wrap').html(data.content);
// hide load more button, if there are not enough posts for the second page
if ( data.max_page < 2 ) {
$('#cc_avis_loadmore').hide();
} else {
$('#cc_avis_loadmore').show();
}
}
});
// do not submit the form
return false;

});

});

Maintenant, quelque soit le choix de l’utilisateur, le bouton load-more ne chargera que la suite des articles de la catégorie et du niveau sélectionnés.