Wordpress - Wordpress Search - display taxonomy terms in results
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
).
The Code/Steps
In 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' ) );
In 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()
?>
In 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 Part 1: 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 Part 2: 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>