I have a custom post type of Hotel. Hotels can be can be categorised with the taxonomy Resort.
On my search page I need to be able to list hotels and taxonomy terms in the results, as separate results. eg:
Resort Name
Hotel Name
Hotel Name
Hotel Name
Resorts should come first, followed by hotels and anything else. As far as I know taxonomy terms are not shown by default in WordPress search.
In my functions.php I'm currently limiting the search to specific post types:
function filter_search($query) {
// Don't run in admin area
if(!is_admin()) {
// Limit search to posts
if($query->is_main_query() && $query->is_search()) {
$query->set('post_type', array('hotel', 'post', 'activities', 'page'));
}
// Return query
return $query;
}
}
add_filter('pre_get_posts', 'filter_search');
Then in my search.php I am grouping the results by post type:
<?php $types = array('hotel', 'post', 'activities', 'page');
foreach( $types as $type ){
echo '<div class="results"> '; ?>
<?php while( have_posts() ){
the_post();
if( $type == get_post_type() ){ ?>
<div class="result">
<div class="result-inner">
<h2>
<?php if ( $type == "hotel" ) { ?>
Hotel:
<?php } elseif ( $type == "activities" ) { ?>
Activity:
<?php } elseif ( $type == "post" ) { ?>
Blog Post:
<?php } elseif ( $type == "page" ) { ?>
Page:
<?php } else { ?>
Page:
<?php } ?>
<a href="<?php the_permalink() ?>"><?php the_title(); ?></a>
</h2>
<p class="blog-more"><a href="<?php the_permalink() ?>" class="button">View Page</a></p>
</div>
</div>
<?php }
}
rewind_posts();
echo '</div> ';
} ?>
What I'm struggling with is how to tell WordPress to show the taxonomy term (Resort) in the results as it's own result. Can anybody help?
UPDATE: I'm happy to preserve the default WordPress search order. The results don't have to be grouped by taxonomy term. I just need to be able to spit out the term as a result itself, with a link to the term (and be counted in the search count query). So if a search matches a taxonomy term that should come first, followed by the other results in whatever order.
I have a custom post type of Hotel. Hotels can be can be categorised with the taxonomy Resort.
On my search page I need to be able to list hotels and taxonomy terms in the results, as separate results. eg:
Resort Name
Hotel Name
Hotel Name
Hotel Name
Resorts should come first, followed by hotels and anything else. As far as I know taxonomy terms are not shown by default in WordPress search.
In my functions.php I'm currently limiting the search to specific post types:
function filter_search($query) {
// Don't run in admin area
if(!is_admin()) {
// Limit search to posts
if($query->is_main_query() && $query->is_search()) {
$query->set('post_type', array('hotel', 'post', 'activities', 'page'));
}
// Return query
return $query;
}
}
add_filter('pre_get_posts', 'filter_search');
Then in my search.php I am grouping the results by post type:
<?php $types = array('hotel', 'post', 'activities', 'page');
foreach( $types as $type ){
echo '<div class="results"> '; ?>
<?php while( have_posts() ){
the_post();
if( $type == get_post_type() ){ ?>
<div class="result">
<div class="result-inner">
<h2>
<?php if ( $type == "hotel" ) { ?>
Hotel:
<?php } elseif ( $type == "activities" ) { ?>
Activity:
<?php } elseif ( $type == "post" ) { ?>
Blog Post:
<?php } elseif ( $type == "page" ) { ?>
Page:
<?php } else { ?>
Page:
<?php } ?>
<a href="<?php the_permalink() ?>"><?php the_title(); ?></a>
</h2>
<p class="blog-more"><a href="<?php the_permalink() ?>" class="button">View Page</a></p>
</div>
</div>
<?php }
}
rewind_posts();
echo '</div> ';
} ?>
What I'm struggling with is how to tell WordPress to show the taxonomy term (Resort) in the results as it's own result. Can anybody help?
UPDATE: I'm happy to preserve the default WordPress search order. The results don't have to be grouped by taxonomy term. I just need to be able to spit out the term as a result itself, with a link to the term (and be counted in the search count query). So if a search matches a taxonomy term that should come first, followed by the other results in whatever order.
I just need to be able to spit out the term as a result itself, with a link to the term (and be counted in the search count query). So if a search matches a taxonomy term that should come first, followed by the other results in whatever order.
WP_Query::$posts
are WP_Post
objects and can't include (or be mixed with) term/WP_Term
objects.
However, there's a way to achieve what you want — use get_terms()
to search for the taxonomy terms and modify the WP_Query
's posts_per_page
and offset
parameters to make the matched terms as if they're part of the actual search results (which are posts).
Note though, for now, get_terms()
/WP_Term_Query
supports single search keywords only — i.e. foo, bar
is treated as one keyword — in WP_Query
, that would be two keywords (foo
and bar
).
functions.php
:Add this:
function wpse342309_search_terms( $query, $taxonomy ) {
$per_page = absint( $query->get( 'posts_per_page' ) );
if ( ! $per_page ) {
$per_page = max( 10, get_option( 'posts_per_page' ) );
}
$paged = max( 1, $query->get( 'paged' ) );
$offset = absint( ( $paged - 1 ) * $per_page );
$args = [
'taxonomy' => $taxonomy,
// 'hide_empty' => '0',
'search' => $query->get( 's' ),
'number' => $per_page,
'offset' => $offset,
];
$query->terms = [];
$terms = get_terms( $args );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
$query->terms = $terms;
}
$args['offset'] = 0; // always 0
$args['fields'] = 'count';
$query->found_terms = get_terms( $args );
$query->term_count = count( $query->terms );
$query->terms_per_page = $per_page; // for WP_Query::$max_num_pages
$query->is_all_terms = ( (int) $per_page === $query->term_count );
$query->set( 'posts_per_page', max( 1, $per_page - $query->term_count ) );
$query->set( 'offset', $query->term_count ? 0 :
max( 0, $offset - $query->found_terms ) );
}
In the filter_search()
, add wpse342309_search_terms( $query, 'resort' );
after this line:
$query->set( 'post_type', array( 'hotel', 'post', 'activities', 'page' ) );
search.php
, for the main WordPress query:<?php if ( have_posts() ) :
$total = $wp_query->found_posts + $wp_query->found_terms;
$wp_query->max_num_pages = ceil( $total / $wp_query->terms_per_page );
echo '<div class="results">';
// Display terms matching the search query.
if ( ! empty( $wp_query->terms ) ) {
global $term; // for use in the template part (below)
foreach ( $wp_query->terms as $term ) {
get_template_part( 'template-parts/content', 'search-term' );
}
}
// Display posts matching the search query.
if ( ! $wp_query->is_all_terms ) {
while ( have_posts() ) {
the_post();
get_template_part( 'template-parts/content', 'search-post' );
}
}
echo '</div><!-- .results -->';
// Display pagination.
//the_posts_pagination();
echo paginate_links( [
'total' => $wp_query->max_num_pages,
'current' => max( 1, get_query_var( 'paged' ) ),
] );
else :
echo '<h1>No Posts Found</h1>';
endif; // end have_posts()
?>
search.php
, for a custom WordPress query:You should always manually call wpse342309_search_terms()
.
<?php
$my_query = new WP_Query( [
's' => 'foo',
// ... your args here ...
] );
// Manually apply the terms search.
wpse342309_search_terms( $my_query, 'resort' );
if ( $my_query->have_posts() ) :
$total = $my_query->found_posts + $my_query->found_terms;
$my_query->max_num_pages = ceil( $total / $my_query->terms_per_page );
echo '<div class="results">';
// Display terms matching the search query.
if ( ! empty( $my_query->terms ) ) {
global $term; // for use in the template part (below)
foreach ( $my_query->terms as $term ) {
get_template_part( 'template-parts/content', 'search-term' );
}
}
// Display posts matching the search query.
if ( ! $my_query->is_all_terms ) {
while ( $my_query->have_posts() ) {
$my_query->the_post();
get_template_part( 'template-parts/content', 'search-post' );
}
}
echo '</div><!-- .results -->';
// Display pagination.
echo paginate_links( [
'total' => $my_query->max_num_pages,
'current' => max( 1, get_query_var( 'paged' ) ),
] );
else :
echo '<h1>No Posts Found</h1>';
endif; // end have_posts()
?>
template-parts/content-search-term.php
<?php global $term;
$term_link = get_term_link( $term, 'resort' ); ?>
<div class="result">
<div class="result-inner">
<h2><a href="<?php echo esc_url( $term_link ); ?>"><?php echo esc_html( $term->name ); ?></a></h2>
<p class="blog-more"><a href="<?php echo esc_url( $term_link ); ?>" class="button">View Page</a></p>
</div>
</div>
template-parts/content-search-post.php
<?php $type = get_post_type(); ?>
<div class="result">
<div class="result-inner">
<h2>
<?php if ( $type == "hotel" ) { ?>
Hotel:
<?php } elseif ( $type == "activities" ) { ?>
Activity:
<?php } elseif ( $type == "post" ) { ?>
Blog Post:
<?php } elseif ( $type == "page" ) { ?>
Page:
<?php } else { ?>
Page:
<?php } ?>
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</h2>
<p class="blog-more"><a href="<?php the_permalink(); ?>" class="button">View Page</a></p>
</div>
</div>
If you mean to have a display like this:
Hotels: Resort 1 <- "Resort 1" is a term in the "resort" taxonomy
Sample Hotel
Sample Hotel 2
Sample Hotel 4 <- This post is assigned to both "Resort 1" and "Resort 2"
Hotels: Resort 2 <- "Resort 2" is a term in the "resort" taxonomy
Sample Hotel 3
Sample Hotel 4 <- This post is assigned to both "Resort 1" and "Resort 2"
Blog Posts
Sample Post
Sample Post 2
Activities
Sample Activity
Sample Activity 2
Pages
Sample Page
Then this can give you that, where we first group the posts by their post type or by the "resort" term name for "hotel" posts: (this is inside search.php
)
<?php if ( have_posts() ) : ?>
<div class="results">
<?php
$_posts = $wp_query->posts;
$posts_arr = [];
$resorts = [];
foreach ( $_posts as $i => $p ) {
// Group "hotel" posts by the "resort" term name.
if ( 'hotel' === $p->post_type ) {
$terms = get_the_terms( $p, 'resort' );
if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
foreach ( $terms as $term ) {
if ( ! isset( $resorts[ $term->name ] ) ) {
$resorts[ $term->name ] = [];
}
$resorts[ $term->name ][] =& $_posts[ $i ];
}
}
// Group other posts by their type.
} else {
if ( ! isset( $posts_arr[ $p->post_type ] ) ) {
$posts_arr[ $p->post_type ] = [];
}
$posts_arr[ $p->post_type ][] =& $_posts[ $i ];
}
}
$posts_arr['hotel'] = ! empty( $resorts );
// List of post_type => Heading.
$types = [
'hotel' => 'Hotels',
'post' => 'Blog Posts',
'activities' => 'Activities',
'page' => 'Pages',
];
foreach ( $types as $type => $heading ) {
if ( ! isset( $posts_arr[ $type ] ) ) {
continue;
}
// Display "hotel" posts.
if ( 'hotel' === $type ) {
foreach ( $resorts as $name => $ps ) {
echo '<h2>Hotels: ' . $name . '</h2>';
foreach ( $ps as $post ) {
setup_postdata( $post );
get_template_part( 'template-parts/content', 'search' );
}
}
// Display posts in other post types.
} else {
echo '<h2>' . $heading . '</h2>';
foreach ( $posts_arr[ $type ] as $post ) {
setup_postdata( $post );
get_template_part( 'template-parts/content', 'search' );
}
}
}
unset( $_posts, $posts_arr, $resorts );
?>
</div><!-- .results -->
<?php endif; // end have_posts() ?>
And the template part (content-search.php
inside wp-content/themes/your-theme/template-parts
):
<div <?php post_class( 'result' ); ?>>
<div class="result-inner">
<h4><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h4>
<p class="blog-more"><a href="<?php the_permalink(); ?>" class="button">View Page</a></p>
</div>
</div>
If you want to group your search results by a custom taxonomy, you can either group your posts AFTER the query like Sally CJ proposed, but this would only work correctly if you have all of your search results on one page.
To order search results AND use pagination, you have to alter your MySQL Query, so that's what we're going to do.
Step 1: Add a filter to "posts_clauses" within the pre_get_posts action to change the generated Query:
add_action('pre_get_posts','setup_my_query_interception');
function setup_my_query_interception($query){
if(!is_admin() && $query->is_search() && $query->is_main_query()){
add_filter( 'posts_clauses', 'order_by_taxonomy_intercept_query_clauses', 20, 1 );
}
}
Step 2: In the order_by_taxonomy_intercept_query_clauses function, you enhance the Query:
function order_by_taxonomy_intercept_query_clauses($pieces){
global $wpdb;
//First we add the taxonomy and term tables to our joins...
$pieces['join'].=" LEFT JOIN $wpdb->term_relationships trt ON ($wpdb->posts.ID = trt.object_id) LEFT JOIN $wpdb->term_taxonomy ttt ON (trt.term_taxonomy_id = ttt.term_taxonomy_id) INNER JOIN $wpdb->terms termts ON (termts.term_id = ttt.term_id)";
//Now we add a WHERE condition that we only want Resort Terms
$pieces['where'].=" AND ttt.taxonomy = 'resort'"; //change this, if your taxonomy type is named different
//At last, we tell the orderby to FIRST order by the term title, after that use the wanted orderby
$pieces['orderby']="termts.name ASC, ".$pieces['orderby'];
//Remove the filter so it only runs once in our main query
remove_filter('posts_clauses', 'order_by_taxonomy_intercept_query_clauses');
return $pieces;
}
Step 3: Your search results will now be ordered by resorts first. If you want to echo the Resort name BEFORE the results, you will have to do something like this in your search.php or index.php:
before the loop:
if(is_search()){
global $activetax;
$activetax = "";
echo '<div class="mywrapper">
}
in the loop:
if(is_search()){
$resorts = get_the_terms(get_the_ID(),'resort');
if(is_array($resorts)){
if(!($activetax==$resorts[0]->name)){
$activetax = $resorts[0]->name;
?>
</div>
<h2 id="tax_id_<?php echo $resorts[0]->term_id; ?>" class="resort-header"><?php echo $activetax; ?></h2>
<div class="mywrapper">
<?php
}
}
}
get_template_part('content');
after the loop:
if(is_search()){
echo "</div>";
}
Did not test it, but it should work.
Happy Coding!
An interesting method is explained by Chandan Kumar on his blog post (Jan 2019) that combines multiple queries, cleans them up, and then presents combined results:
- Remove the old query in the search.php file.
- Get all the category, tag and custom taxonomy in array format. And loop every array and find matching name in these arrays. Then store the ids of the matched name in a separate array.
- Using wp_query, query all the posts (by tax query )with these matched ids. Then store the resulting post_ids in an array.
- Now perform a default search_query and store all the ids in an array.
- Now we have got two arrays of ids which is the result.
- Combine these two and remove duplicates.
- Now we will perform a last query and show all the posts (by post__in parameter of wp_query).
There is no license mentioned on his code, but he seems to be sharing it publicly:
<div class="page-description"><?php echo get_search_query(); ?></div>
<?php
$search=get_search_query();
$all_categories = get_terms( array('taxonomy' => 'category','hide_empty' => true) );
$all_tags = get_terms( array('taxonomy' => 'post_tag','hide_empty' => true) );
//if you have any custom taxonomy
$all_custom_taxonomy = get_terms( array('taxonomy' => 'your-taxonomy-slug','hide_empty' => true) );
$mcat=array();
$mtag=array();
$mcustom_taxonomy=array();
foreach($all_categories as $all){
$par=$all->name;
if (strpos($par, $search) !== false) {
array_push($mcat,$all->term_id);
}
}
foreach($all_tags as $all){
$par=$all->name;
if (strpos($par, $search) !== false) {
array_push($mtag,$all->term_id);
}
}
foreach($all_custom_taxonomy as $all){
$par=$all->name;
if (strpos($par, $search) !== false) {
array_push($mcustom_taxonomy,$all->term_id);
}
}
$matched_posts=array();
$args1= array(
'post_status' => 'publish',
'posts_per_page' => -1,
'tax_query' => array(
'relation' => 'OR',
array(
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => $mcat
),
array(
'taxonomy' => 'post_tag',
'field' => 'term_id',
'terms' => $mtag
),
array(
'taxonomy' => 'custom_taxonomy',
'field' => 'term_id',
'terms' => $mcustom_taxonomy
)
)
);
$the_query = new WP_Query( $args1 );
if ( $the_query->have_posts() ) {
while ( $the_query->have_posts() ) {
$the_query->the_post();
array_push($matched_posts,get_the_id());
//echo '<li>' . get_the_id() . '</li>';
}
wp_reset_postdata();
} else {
}
?>
<?php
// now we will do the normal wordpress search
$query2 = new WP_Query( array( 's' => $search,'posts_per_page' => -1 ) );
if ( $query2->have_posts() ) {
while ( $query2->have_posts() ) {
$query2->the_post();
array_push($matched_posts,get_the_id());
}
wp_reset_postdata();
} else {
}
$matched_posts= array_unique($matched_posts);
$matched_posts=array_values(array_filter($matched_posts));
//print_r($matched_posts);
?>
<?php
$paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
$query3 = new WP_Query( array( 'post_type'=>'any','post__in' => $matched_posts ,'paged' => $paged) );
if ( $query3->have_posts() ) {
while ( $query3->have_posts() ) {
$query3->the_post();
get_template_part( 'template-parts/content/content', 'excerpt' );
}
twentynineteen_the_posts_navigation();
wp_reset_postdata();
} else {
}
?>