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>