WordPress taxonomy filter
30.03.17For one of my projects I needed to create a taxonomy filter for a webshop to filter by terms like width, height and other features of a product. The filter should also be able to function for different kind of product categories. So in my case I had one taxonomy called “shop-category” which was meant to create multiple categories in my webshop and in these sections you are able to filter by features like width and height for example.
So an example filter url looks like: https://www.webshop.com/shop/shop-category/wooden-boxes/width/90-cm/height/180-cm/
- The /shop/ part is the post type “shop”.
- The /shop-category/wooden-boxes/ part is the taxonomy “shop-category” with the category (term) “Wooden boxes”.
- The /width/90-cm/ part is the “width” taxonomy with a “90 cm” term.
- The /height/180-cm/ part is the “height” taxonomy with a “180 cm” term.
So created the following post type and taxonomies like:
// Register shop post type function theme_register_post_types() { $args = array( 'label' => __( 'Shop', 'theme_name' ), 'description' => __( 'A shop for standard products', 'theme_name' ), 'supports' => array( 'title', 'editor', 'excerpt', 'author', 'thumbnail', 'custom-fields', ), 'hierarchical' => false, 'taxonomies' => array( 'shop-category' ), 'public' => true, 'show_ui' => true, 'show_in_menu' => true, 'menu_position' => 5, 'menu_icon' => 'dashicons-cart', 'show_in_admin_bar' => true, 'show_in_nav_menus' => true, 'can_export' => true, 'has_archive' => true, 'exclude_from_search' => false, 'publicly_queryable' => true, 'capability_type' => 'page', ); register_post_type( 'shop', $args ); } add_action( 'init', 'theme_register_post_types', 0 ); // Shop related taxonomies $shop_taxonomies = array( // Shop category 'shop-category' => array( 'labels' => array( // Labels ), 'hierarchical' => true ), // Width 'width' => array( 'labels' => array( // Labels ), 'rewrite' => array( 'slug' => _x( 'width', 'Taxonomy rewrite slug', 'theme_name' ) ), ), // Height 'height' => array( 'labels' => array( // Labels ), 'rewrite' => array( 'slug' => _x( 'height', 'Taxonomy rewrite slug', 'theme_name' ) ), ) // Other shop filter taxonomies ); // Register the taxonomies foreach ( $shop_taxonomies as $taxonomy => $values ) { $args = array( 'labels' => $values['labels'], 'public' => true, 'show_ui' => true, 'show_admin_column' => true, 'show_in_nav_menus' => true, ); if ( isset( $values['hierarchical'] ) ) { $args['hierarchical'] = $values['hierarchical']; } if ( isset( $values['rewrite'] ) ) { $args['rewrite'] = $values['rewrite']; } register_taxonomy( $taxonomy, array( 'shop' ), $args ); }
Custom rewrite rules
It was important for me to have my url structured and workable so that the “shop” post type was on the first place, followed by the “shop-category” taxonomy with the right term and as last the other filter taxonomies with corresponding term.
To achieve this I needed to create some rewrite rules so that WordPress understands which filters are in use. The post type and shop-category taxonomy are always on the same place, but the other filter taxonomies must be able to operate in different places in the url because you do not know in what order a user uses the filters.
/** * Adds rewrite rules for shop taxonomy filters */ function gtp_add_shop_rewrite_rules() { global $wp_rewrite; // Get all taxonomy names related to shop post type $taxonomies = get_object_taxonomies( 'shop' ); $index = 0; // Adds taxonomy filter rewrite rules for ( $i = 1; $i <= count( $taxonomies ); $i ++ ) { $index ++; // addslashes rewrite regex with all taxonomy filters $regex[] = '(' . implode( '|', $taxonomies ) . ')/(.+?)/'; // Adds rewrite redirect $redirect[] = $wp_rewrite->preg_index( $index ) . '=' . $wp_rewrite->preg_index( $index + 1 ); // Adds regex and redirect to new rewrite rules array $new_rules['shop/' . implode( '', $regex ) . '?$'] = 'index.php?post_type=shop&' . implode( '&', $redirect ); // With page number $new_rules['shop/' . implode( '', $regex ) . 'page/([0-9]*)/?$'] = 'index.php?post_type=shop&' . implode( '&', $redirect ) . '&paged=' . $wp_rewrite->preg_index( $index + 2 ); $index ++; } // Product feed rewrte rules $new_rules['shop/shop-category/(.+?)/feed/(.+?)/?$'] = 'index.php?post_type=shop&shop-category=' . $wp_rewrite->preg_index( 1 ) . '&feed=' . $wp_rewrite->preg_index( 2 ); // Add new rewrite rules to rewrite rules array $wp_rewrite->rules = array_reverse( $new_rules ) + $wp_rewrite->rules; } add_action( 'generate_rewrite_rules', 'gtp_add_shop_rewrite_rules' );
The biggest part of the job was to create the filter functionality itself. I made 2 classes. One for initialise the filter and one for each filter item in this filter.
Taxonomy filter class
class TaxonomyFilter { // Post type for getting the right posts public $postType; // The taxonomy on which term pages the filter will be used public $sectionTaxonomy; /** * Initializes the object * * @param string $postType to get posts from this post type * @param string $sectionTaxonomy to get posts from this taxonomy */ public function __construct( $postType = 'shop', $sectionTaxonomy = 'shop-category' ) { $this->postType = $postType; $this->sectionTaxonomy = $sectionTaxonomy; } /** * Gets the posts * * To get the right taxonomies and terms to create the filters * related to these queried posts */ public function getPosts( $fixedSectionTerm = null ) { global $wp_query; if ( ! empty( $fixedSectionTerm ) ) { $term = $fixedSectionTerm; } elseif ( get_query_var( $this->sectionTaxonomy ) ) { $term = get_query_var( $this->sectionTaxonomy ); } // Get the right term if ( ! empty( $term ) ) { // Set arguments $args = array( 'post_type' => $this->postType, 'posts_per_page' => -1, 'tax_query' => array( array( 'taxonomy' => $this->sectionTaxonomy, 'field' => 'slug', 'terms' => $term ) ) ); // If filter are set if ( ! empty( $wp_query->tax_query->queries ) ) { foreach ( $wp_query->tax_query->queries as $query ) { // Get taxonomy by rewrite slug if ( $taxonomy = get_taxonomy( $query['taxonomy'] ) ) { // Add tax query $args['tax_query'][] = array( 'taxonomy' => $taxonomy->query_var, 'field' => 'slug', 'terms' => get_query_var( $taxonomy->query_var ), ); } } } // Query posts $posts = get_posts( $args ); return $posts; } return false; } /** * Gets filters * * Gets the right filters related to the queried posts * and creates a structure to be able to show the filters as a navigation */ public function getFilters( $fixedSectionTerm = null ) { // Get posts to get related taxonomies and terms if ( $posts = $this->getPosts( $fixedSectionTerm ) ) { foreach ( $posts as $post ) { // Get post's related taxonomies if ( $taxonomies = get_post_taxonomies( $post->ID ) ) { // Unset main taxonomy which is not a filter $sectionTaxIndex = array_search( $this->sectionTaxonomy, $taxonomies ); unset( $taxonomies[$sectionTaxIndex] ); foreach ( $taxonomies as $taxonomy ) { // Get terms of each post if ( $terms = get_the_terms( $post->ID, $taxonomy ) ) { foreach ( $terms as $term ) { // Get taxonomy name $taxName = get_taxonomy( $taxonomy )->labels->singular_name; // If taxonomy or term not exists in array if ( empty( $filters[$taxName] ) || ! in_array( $term->term_id, array_keys( $filters[$taxName] ) ) ) { // Add item object $filters[$taxName][$term->term_id] = new TaxonomyFilterItem( $term->term_id, $this->sectionTaxonomy, $taxonomy, $this->postType ); } } } } } } } // If there are filters if ( ! empty( $filters ) ) { foreach ( $filters as $taxonomy => $items ) { // Sort the filter items by name uasort( $filters[$taxonomy], function( $a, $b ) { if ( $a->getName() == $b->getName() ) { return 0; } return ( $a->getName() < $b->getName() ) ? 1 : -1; }); } return $filters; } return false; } }
Taxonomy filter item class
class TaxonomyFilterItem { // Stores term object protected $_term; // The taxonomy on which term pages the filter will be used protected $_sectionTaxonomy; // Item filter taxonomy protected $_taxonomy; // Item Post type protected $_postType; // Item classes protected $_class; /** * Initializes the object * * @param int $id to get the term object * @param string $sectionTaxonomy to create the url * @param string $taxonomy to get the taxonomy object * @param string|array $class adding extra classes to item */ public function __construct( $id = 0, $sectionTaxonomy = 'shop-category', $taxonomy = null, $postType = 'shop', $class = [] ) { $this->_term = get_term( $id, $taxonomy ); $this->_sectionTaxonomy = $sectionTaxonomy; $this->_taxonomy = $taxonomy; $this->_postType = $postType; $this->_classes = $class; } /** * Returns item's (term) name */ public function getName() { return $this->_term->name; } /** * Returns item's url */ public function getUrl( $urlAddition = [] ) { // Get taxonomy object $taxonomy = get_taxonomy( $this->_taxonomy ); // Get taxonomy rewrite slug for a nice (translated) url $slug = $taxonomy->name; // Creates item url $url = $this->createUrl( $slug, $this->_term->slug, $urlAddition ); return $url; } protected function createUrl( $taxonomySlug, $term, $urlAddition = [] ) { global $wp_query; // If there is already a filter running for this taxonomy if ( isset( $wp_query->query_vars[$taxonomySlug] ) ) { // And the term for this URL is not already being used to filter the taxonomy if ( strpos( $wp_query->query_vars[$taxonomySlug], $term ) === false ) { // Append the term $filterQuery = $taxonomySlug . '/' . $wp_query->query_vars[$taxonomySlug] . '+' . $term; } else { // Otherwise, remove the term if ( $wp_query->query_vars[$taxonomySlug] == $term ) { $filterQuery = ''; } else { $filter = str_replace( $term, '', $wp_query->query_vars[$taxonomySlug] ); // Remove any residual + symbols left behind $filter = str_replace( '++', '+', $filter ); $filter = preg_replace( '/(^\+|\+$)/', '', $filter ); $filterQuery = $taxonomySlug . '/' . $filter; } } } else { $filterQuery = $taxonomySlug . '/' . $term; } // Maintain the filters for other taxonomies if ( isset( $wp_query->tax_query ) ) { $existingQuery = ''; foreach ( $wp_query->tax_query->queries as $query ) { $tax = get_taxonomy( $query['taxonomy'] ); // Have we already handled this taxonomy? if ( $tax->query_var == $taxonomySlug ) continue; // Make sure taxonomy hasn't already been added to query string if ( strpos( $existingQuery, $tax->query_var ) === false ) $existingQuery .= $tax->query_var . '/' . $wp_query->query_vars[$tax->query_var] . '/'; } } if ( isset( $existingQuery ) ) { $filterQuery = $existingQuery . $filterQuery; } if ( ! empty( $urlAddition ) ) { $addition = rtrim( implode( '/', $urlAddition ), '/' ) . '/'; } else { $addition = ''; } return trailingslashit( get_post_type_archive_link( $this->_postType ) . $addition . $filterQuery ); } /** * Returns item's class */ public function getClass() { global $wp_query; // Get taxonomy object $taxonomy = get_taxonomy( $this->_taxonomy ); // Get taxonomy rewrite slug $slug = $taxonomy->name; // No classes $class = []; // If this filter is active, current class will be added if ( ! empty( $wp_query->query_vars[$slug] ) && $this->_term->slug == $wp_query->query_vars[$slug] ) { $class[] = 'current'; } if ( ! empty( $this->_class ) ) { $class = array_merge( $class, $this->_class ); } return ! empty( $class ) ? implode( ' ', $class ) : false; } /** * Adds item's classes */ public function addClass( $class = [] ) { if ( is_array( $class ) ) { $this->_class = array_merge( $this->_class, $class ); } else { $this->_class[] = $class; } } }
In action
In your shop-archive.php you can do something like below to show the filter:
<?php // Initializes taxonomy filter $filter = new TaxonomyFilter(); if ( $filters = $filter->getFilters() ) { ?> <nav class="filter"> <?php foreach ( $filters as $heading => $items ) { $count = 0; ?> <div class="nav-part"> <h2 class="heading"><?php echo $heading; ?></h2> <ul class="list"> <?php foreach ( $items as $item ) { echo '<li class="' . $item->getClass() . '"><a href="' . $item->getUrl() . '">' . $item->getName() . '</a></li>'; } ?> </ul><!--End .list--> </div><!--End .nav-part--> <?php } ?> </nav><!--End .side-navigation--> <?php } ?>
No comments
There are no comments for this post yet. Be the first one :)