Search page for an example race site

Two different ways to create a search using the ‘and’ operator for:

  • Text search (query string for this is ‘s’),
  • custom taxonomies (potentially faster loadingthat custom fields)
    • distance
    • terrain
    • location

These examples use:

  • Custom post type ‘race’

Option 01: Loading the same page with the default search results using PHP and the default query string

This option relys on the default WP query string.

The code below can be added to a theme template (eg ‘archive-race.php’) or made into a block and added to an archive template in FSE:

Note: Make sure to check the search.php or search-results.php also need the template part!

<?php
$textField = sanitize_text_field(get_query_var('s'));
$filterByDistance = sanitize_text_field(get_query_var('distance'));
$filterByTerrain = sanitize_text_field(get_query_var('terrain'));
$filterByLocation = sanitize_text_field(get_query_var('location'));
?>

Then a search form:

<form role="search" method="get" action="<?php $_PHP_SELF ?>#searchform" id="s" class="wp-block-search__button-outside wp-block-search__text-button wp-block-search">
	<div class="search-row">
    <div class="search-column">
        <label for="wp-block-search__input-1" class="wp-block-search__label">Search the site</label>
	<div class="wp-block-search__inside-wrapper ">
		<input type="search" id="wp-block-search__input-1" class="wp-block-search__input " name="s" value="<?php echo $textField ?>" placeholder="Text Search..." >
	</div>
    </div><!-- .search-column -->
    <div class="search-column">
        <label for="wp-block-search__input-9" class="wp-block-search__label">Search Distance</label>
        <div class="wp-block-search__inside-wrapper ">
            <select name="distance">
                <option value="" >Show all</option>
                    <?php 
                    $terms = get_terms( array(
                        'taxonomy' => 'distance',
                        'hide_empty' => false,
                    ) );
                    foreach ( $terms as $term ) {
                        $selected = $term->slug === $filterByDistance ? 'selected' : '';
                                                        
                        echo '<option value="' . $term->slug . '" ' . $selected . '>' . $term->name . '</option>';
                    }
                    ?>
                </select>
        </div>
    </div><!-- .search-column -->
    <div class="search-column">
        <label for="wp-block-search__input-9" class="wp-block-search__label">Search Terrain</label>
        <div class="wp-block-search__inside-wrapper ">
            <select name="terrain">
                <option value="" >Show all</option>
                    <?php 
                    $terms = get_terms( array(
                        'taxonomy' => 'terrain',
                        'hide_empty' => false,
                    ) );
                    foreach ( $terms as $term ) {
                        $selected = $term->slug === $filterByTerrain ? 'selected' : '';
                                                        
                        echo '<option value="' . $term->slug . '" ' . $selected . '>' . $term->name . '</option>';
                    }
                    ?>
                </select>
        </div>
    </div><!-- .search-column -->
    <div class="search-column">
        <label for="wp-block-search__input-9" class="wp-block-search__label">Search Location</label>
        <div class="wp-block-search__inside-wrapper ">
            <select name="location">
                <option value="" >Show all</option>
                    <?php 
                    $terms = get_terms( array(
                        'taxonomy' => 'location',
                        'hide_empty' => false,
                    ) );
                    foreach ( $terms as $term ) {
                        $selected = $term->slug === $filterByLocation ? 'selected' : '';
                                                        
                        echo '<option value="' . $term->slug . '" ' . $selected . '>' . $term->name . '</option>';
                    }
                    ?>
                </select>
        </div>
    </div><!-- .search-column -->
    </div><!-- .search-row -->
	 <button type="submit" class="wp-block-search__button  ">SUBMIT</button>			
</form>

Results

The results are pulled out using the logic of the WordPress templates.

To edit the display of the results in FSE you will need to change the the search template query loop.

Pros and cons of this option:

  • Great if you only need one search on your site and you are happy that any text search for will pull from all post types.
  • Easy if you don’t want anything to complicated

Option 02: Using a custom query string with a custom WP_query

First we need the custom query sting variables in a functions.php

<?php
function custom_search_query_vars_filter($vars) {
   $vars[] .= 'cs_s';
   $vars[] .= 'cs_distance';
   $vars[] .= 'cs_terrain';
   $vars[] .= 'cs_location';
   return $vars;
}
add_filter( 'query_vars', 'custom_search_query_vars_filter' );

For this route we are not using the default search pages so we can include the code on a page template or FSE create a block to add to any page.

Grabbing the query string:

<?php
// get santized values from the query string 
$cs_text = sanitize_text_field(get_query_var('cs_s'));
$cs_distance = sanitize_text_field(get_query_var('cs_distance'));
$cs_terrain = sanitize_text_field(get_query_var('cs_terrain'));
$cs_location = sanitize_text_field(get_query_var('cs_location'));
// set up search array with variable name and query string value
$search_array_tax = array(
    array ('distance', 'Distance', 'cs_distance', $cs_distance),
    array ('terrain', 'Terrain', 'cs_terrain', $cs_terrain),
    array ('location', 'location', 'cs_location', $cs_location)
);

And then the form: (note: the form action URL)

As this example had 3 custom taxonomies we used an array and foreach loop

<form role="search" method="get" action="https://racerpacer.com/search-page/" id="cs_s" class="wp-block-search__button-outside wp-block-search__text-button wp-block-search">
    <div class="search-row">

        <?php // text search different to the custom taxonomies ?>
        <div class="search-column">
            <label for="wp-block-search__input-1" class="wp-block-search__label">Search the site</label>
            <div class="wp-block-search__inside-wrapper ">
                <input type="search" id="wp-block-search__input-1" class="wp-block-search__input " name="cs_s" value="<?php echo $cs_text ?>" placeholder="Text Search..." >
            </div>
        </div><!-- .search-column -->

        <?php foreach ($search_array_tax as $r) { ?>
        <div class="search-column">
            <label for="wp-block-search__input-9" class="wp-block-search__label">Search <?php echo $r[0]; ?></label>
            <div class="wp-block-search__inside-wrapper ">
                <select name="<?php echo $r[2]; ?>">
                    <option value="" >Show all</option>
                    <?php 
                    $terms = get_terms( array(
                        'taxonomy' => $r[0],
                        'hide_empty' => false,
                    ) );
                    foreach ( $terms as $term ) {
                        $selected = $term->slug === $r[3] ? 'selected' : '';                       
                        echo '<option value="' . $term->slug . '" ' . $selected . '>' . $term->name . '</option>';
                    }
                    ?>
                </select>
            </div>
        </div><!-- .search-column -->
        <?php } ?>
</div><!-- .search-row -->
<button type="submit" class="wp-block-search__button  ">SUBMIT</button>			
</form>

And then the WP_query and results:

$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
$args = array(
	'post_type' => 'race', 
	'posts_per_page' => 3, 
	'post_status' => 'publish',
    'paged' => $paged,
	'ignore_sticky_posts' => true
);
if (!empty ($cs_text) ) {
	$newArgs = array(
		's' => $cs_text,
	);
	$args = array_merge($args, $newArgs);
}
if (!empty ($cs_distance) || !empty ($cs_terrain) || !empty ($cs_location) ) {
    $taxArray = array(
        'relation' => 'AND', 
    );
}
if (!empty ($cs_distance)) {
    $taxArray[] =  array(
        'taxonomy' => 'distance',
        'field'    => 'slug',
        'terms'    => $cs_distance,
    );
}
if (!empty ($cs_terrain)) {
    $taxArray[] = array(
        'taxonomy' => 'terrain',
        'field'    => 'slug',
        'terms'    => $cs_terrain,
    );
}
if (!empty ($cs_location)) {
    $taxArray[] = array(
        'taxonomy' => 'location',
        'field'    => 'slug',
        'terms'    => $cs_location,
    );
}
// print_r($taxArray);
if (!empty ($cs_distance) || !empty ($cs_terrain) || !empty ($cs_location) ) {
    $newArgs = array('tax_query' => $taxArray);
    $args = array_merge($args, $newArgs);
}
$new_query = new WP_Query($args);
if ( $new_query->have_posts() ) : ?>
 <div class="wp-block-columns align-wide">
        <?php
            while ( $new_query->have_posts() ) :
            $new_query->the_post(); 
             ?>
            <div class="wp-block-column"  style="flex-basis:33.33%">
                <?php require get_theme_file_path() . '/components/block-race/card/block-race-card.php'; ?>
                </div><!--columns/slide-->
            <?php
            endwhile; ?>
        </div><!--row-->
        <div class="wp-block-group alignwide has-global-padding is-layout-constrained" style="margin-top:var(--wp--preset--spacing--x-large)">
            <nav class="alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-container-24" aria-label="Pagination">
                    <?php if(function_exists('wp_pagenavi')) :
                        wp_pagenavi(array( 'query' => $new_query ));
                    else : ?>
                            <div class="navigation">
                                <div class="alignleft"><?php             
            previous_posts_link( __( 'Newer Entries', 'textdomain' ) ); 
                                // previous_posts_link() ?></div>
                                <div class="alignright"><?php next_posts_link( __( 'Older Entries', 'textdomain' ), $new_query->max_num_pages );
                                // next_posts_link() ?></div>
                            </div>
                    <?php endif; ?>
            </nav>
        </div>
        <!-- wp:spacer -->
        <div style="height:100px" aria-hidden="true" class="wp-block-spacer"></div>
        <!-- /wp:spacer -->
<?php else : ?>
    <p>Sorry,no posts found.</p>
<?php endif; 
wp_reset_postdata();
?>

To note:

  • We used relation ‘AND’ so you don’t get more results that the whole search you are making.
  • This used pageNavi plugin for better pagination.

Pros and cons of this option:

  • Leaves the default text search and results alone
  • You can be more specific with the query, form and results (eg: post and pages will not be inclued in the results for a CPT search).
  • Con is a bit more work
  • Good for filtering a lot of content
  • Con if the site doesn’t have a lot of results it will show no post quickly

Summary

An ajax form will do more than this and be better form anything very complicated but this is a simple way to get a better search experience.

It would be good if as you filter one field the other options adjusted to only the options that are available

Would be better for some secnarios if you don’t have to press submit

Reloading the page for results can be a bit clumsy

Using the query string does give you a URL that you can share with others or bookmark.

H

A

Search the site


Address

123 Main Street
Town
City
P05T C0D3

Tel: 01234 567 899

Mob: 01234 567 899

Email: ben@gomopress.com


Copyright 2023. Blah blah blah Company Limited