We created a custom post type called project. We also created two custom taxonomies tied to this one. One taxonomy called “type” which has values like ‘rendering’ or ‘animation.’ Another custom taxonomy called ‘market’ which can have values like ‘commercial’, ‘residential’, ‘civic’, or etc.
A visitor to the site needs to be able to search by selecting what type of project or all types and select what market or all markets. By default the initial screen will be all projects of all types and markets.
So the first thing to do was determine how we can alter the $args to a WP_Query call to query multiple taxonomies. A quick visit to the WordPress codex on WP_Query shows us that we need to use the query var $tax_query.
Next, we determined how we are going to create the page and have it fit in the WordPress template flow. We created a page with a slug of ‘portfolio’ so we could create a page template in the theme directory called ‘page-portfolio.php’ and easily add this page to a menu. In this template we first change the query to the initial list of projects we want shown: (NOTE: In this post, I am pulling code from an actual working theme we created for a client, but I am changing names to protect the innocent and cherry picking so this code is not tested.)
remove_action( 'genesis_loop', 'genesis_do_loop' );
add_action( 'genesis_loop', 'jhts_preloop' );
function jhts_preloop() {
global $wp_query;
$paged = get_query_var('paged');
$args = array (
'post_type' => 'jhts_projects',
'post_status' => 'publish',
'order' => 'DESC',
'orderby' => 'title',
'paged' => $paged ? $paged : 1,
'posts_per_page' => get_field('projects_per_page','option') //Using ACF WordPress to allow user to determine this
);
$wp_query = new WP_Query($args); //we've changed the query to be all projects from all taxonomies
genesis_do_loop; //call the loop. Oh, did I mention we use Genesis? In our theme we used a custom loop here.
wp_reset_query();
}
This gives us a page that lists all the projects. Now we need a search form that allows us to search based on custom taxonomies. Depending on how you plan to style the form depends on which hook you plan to use to add it to the page. We want the form on the top of the page before the list of projects so we will use the ‘genesis_before_loop’ hook. We will also need to add a couple of custom query vars to hold the two taxonomy values. We will call them ‘custom_type’ and ‘custom_market’. For security, WordPress ignores any custom query vars unless we add them. We need to do this in the ‘functions.php.’
/* add additional search query vars to search multiple taxonomies --(functions.php)----------------------------------------*/
add_filter( 'query_vars', 'prism_add_query_vars_filter' );
function prism_add_query_vars_filter($vars) {
$vars[]='custom_type';
$vars[]='custom_market';
$vars[]='search_refer'; //we will need this soon.
return $vars;
}
( Hats off to //http://wpsnipp.com/index.php/template/create-multiple-search-templates-for-custom-post-types/ for their post on this)
Now, we can use our custom query vars. WordPress won’t do anything with these variables like add them to the WP_Query. We will do that later. But first, lets use them and create our form.
add_action( 'genesis_before_loop', 'jhts_add_search_form' );
function jhts_add_search_form() {
$markets = wp_dropdown_categories (array( //use WordPress function to create a dropdown of markets
'taxonomy' => 'market',
'orderby' => 'slug',
'name' => 'custom_market', //this is our custom query var name
'echo' => 0,
'class' => 'markets-dropdown',
'show_option_all' => 'All Markets',
'selected' => get_query_var('custom_market'), //see if there is a custom market already selected
'value_field' => 'slug'
);
$type = get_query_var('custom_type'); //see if custom_type already has a value..(from a search before)
if ($type == "renderings") { // using this in the form to check a radio button
$checked = 1;
} elseif ($type == "animation") {
$checked = 2;
}
$form = '';
echo $form;
}
Things to note. We have a lot of hidden fields here. This is to put these values in the query vars. Remember WordPress is stateless most of the time so the query vars are how it determines what query it will run that determines what template to show etc. The action attribute of this form is simply our wordpress home url. Example, a form like this on jhtechservices.com would go to the url:
//jhtechservices.com/?s=searchtermhere&custom-type=animation&custom-market=0&post_type=jhts_projects&order=DESC&orderby=title&post_per_page=10&search_refer=jhts_projects
So it is the query vars and only the query vars that determines what page the user will see after they press submit.
Without doing any other coding, let us see where it goes. The url has the query var ‘s’ so WordPress says this is a search and will run a search based on the query vars: ‘s’, ‘post_type’, ‘order’, ‘orderby’, and ‘post_per_page’. It ignores our custom-type and custom-market. After the query, based on WordPress Template hierarchy, it will use the templates search.php file or if that is not there, the default index.php. So we get a listing of custom post types but it did not go back to our page-portfolio.php file so our search form is gone.
How to fix this it so that it will go back to our page-portfolio.php file? We add code to our template’s search.php file. If search.php doesn’t exist, create it. In this template we will point it back to page-portfolio.php if the query var, search_refer is equal to ‘jhts_projects’
//in search.php $search_refer = get_query_var("search_refer"); if ($search_refer == 'jhts_projects') { load_template(get_stylesheet_directory() .'/page-portfolio.php'); } else { genesis(); }
This works but our search results are gone. That’s because our page-portfolio.php overwrites the query. We need to use a conditional tag so that if this template is used during a search, it doesn’t change the query. We just need to add an ‘if’ statement to our page-portfolio.php
remove_action( 'genesis_loop', 'genesis_do_loop' );
add_action( 'genesis_loop', 'jhts_preloop' );
function jhts_preloop() {
global $wp_query;
if(!is_search()) { //don't reset the query if this is coming from search.php
$paged = get_query_var('paged');
$args = array (
'post_type' => 'jhts_projects',
'post_status' => 'publish',
'order' => 'DESC',
'orderby' => 'title',
'paged' => $paged ? $paged : 1,
'posts_per_page' => get_field('projects_per_page','option')
);
$wp_query = new WP_Query($args);
}
genesis_do_loop;
wp_reset_query();
}
Lastly, we need to alter the query so that it searches based on the multiple taxonomies. Using the values of our custom query vars, we create a $tax_query variable to submit with the query. Where to do this? We have two choices. We could do this in the page-portfolio.php file by submitting a new WP_Query but that would mean anytime someone searches with this form, it will perform two queries. A better way would be to alter the main query before it runs it. To do this we will need to hook into the ‘pre_get_posts’ hook. This has to be done in the ‘functions.php’ file as the hook is run before WordPress gets to the templates. Since this hook is run all the time, even on the dashboard, we will need some conditional tags to make sure it only alters the query if we are searching with this form.
add_action( 'pre_get_posts', 'jhts_modify_query' ); /*do this in functions.php*/
function jhts_modify_query($query) {
if($query->is_main_query() && ($query->is_search() || $query->is_archive()) && !is_admin()){
$search_refer = get_query_var("search_refer");
if ($search_refer == 'jhts_projects'){ // We are in portfolio search
$customType = get_query_var('custom_type');
$customMarket = get_query_var('custom_market');
$tax_query = array(); // to query on multiple taxonomies
$num = get_field('projects_per_page', 'option');
$query->set('posts_per_page', $num);
if( $customType && $customType != '0' ) { //if 0, we are searching for all
$tax_query[] = array(
'taxonomy' => 'type',
'field' => 'slug',
'terms' => $customType
);
}
if( $customMarket && $customMarket != '0' ) { //if 0, we are searching for all
$tax_query[] = array(
'taxonomy' => 'market',
'field' => 'slug',
'terms' => $customMarket
);
}
if(!empty($tax_query)) {
$query->set('tax_query', $tax_query);
}
}
That’s it. Let me know if this was helpful.