I recently became disgruntled with the way my blogs displayed search results. By default, WordPress blogs will show searched posts exactly as they might appear on an index or archives page: Typically as an extract, or perhaps even as the full entry.
This doesn’t help at all if you’re looking for something in particular – It’s a much better idea to show the post within the context of the search query, as real search engines do.
See it in practice here.
This is a fairly easy thing to actually get working in WordPress. It’ll take just a couple of minutes, and will make a big difference to blog visitors. Here’s how I did it.
Creating a search result page
If your theme doesn’t already have one, you’ll need to construct a template within your theme that WordPress will use for search results. By default, WordPress will use your index.php
template, so that’s usually a good place to start with, for normal themes.
Duplicate index.php
, and call it search.php
.
If you already have a search.php
, you’re all set.
A note about theme engines
A special case here is for theme engines like Thematic (which I use for this blog). For Thematic, it’s a matter of un-hooking the provided search ‘loop’ from within your child theme, and replacing it with your own.
In my case, with a Thematic child theme, this takes place within functions.php
. First, one needs an ‘init’ action, to remove the existing hooks.
function mytheme_init() { remove_action('thematic_searchloop', 'thematic_search_loop'); } ... add_action('init', 'mytheme_init', 10); |
Then, it needs a replacement function to perform the search result loop:
function mytheme_search_loop() { while ( have_posts() ) : the_post(); ?> <div id="post-" class=""> <div class="entry-content"> </div> </div><!-- .post --> <?php endwhile; } ... add_action('thematic_searchloop', 'mytheme_search_loop'); |
Some smarts to show context
What I did was replace the content of each post displayed with some code that constructs and displays some context around the search terms found in the post.
In your search.php
(or your search loop function, if you’re using a theme engine), look for the line that inserts the post content. Chances are, it’ll look something like . In the case of the Thematic child theme above, it's
.
Delete that line, and replace it with the following (here’s a plain-text version, if that’s easier):
<?php // Configuration $max_length = 400; // Max length in characters $min_padding = 30; // Min length in characters of the context to place around found search terms // Load content as plain text global $wp_query, $post; $content = (!post_password_required($post) ? strip_tags(preg_replace(array("/r?n/", '@<s>@'), array(' ', "n"), apply_filters('the_content', $post->post_content))) : ''); // Search content for terms $terms = $wp_query->query_vars['search_terms']; if ( preg_match_all('/'.str_replace('/', '/', join('|', $terms)).'/i', $content, $matches, PREG_OFFSET_CAPTURE) ) { $padding = max($min_padding, $max_length / (2*count($matches[0]))); // Construct extract containing context for each term $output = ''; $last_offset = 0; foreach ( $matches[0] as $match ) { list($string, $offset) = $match; $start = $offset-$padding; $end = $offset+strlen($string)+$padding; // Preserve whole words while ( $start > 1 && preg_match('/[A-Za-z0-9'"-]/', $content{$start-1}) ) $start--; while ( $end $last_offset ) $context = '...'.$context; $output .= $context; $last_offset = $end; } if ( $last_offset != strlen($content)-1 ) $output .= '...'; } else { $output = $content; } if ( strlen($output) > $max_length ) { $end = $max_length-3; while ( $end > 1 && preg_match('/[A-Za-z0-9'"-]/', $output{$end-1}) ) $end--; $output = substr($output, 0, $end) . '...'; } // Highlight matches $context = nl2br(preg_replace('/'.str_replace('/', '/', join('|', $terms)).'/i', '<strong>$0</strong>', $output)); ?> <p class="search_result_context"> </p> <a href="" rel="bookmark">Read this entry</a> |
Save, and search for something on your blog — you should see contextual search results, now.
One final tweak: Results per page
WordPress has a setting for the number of posts to show per page. You may want to use a different number of search results per page, given that each result is now shorter than a full post.
To override this ‘posts per page’ setting, you’ll want to find the line just before the search loop. It’ll probably look like , or, if your theme doesn't bother with that part,
.
Before that line, insert the following:
query_vars; $v['posts_per_page'] = 10; query_posts($v); ?> |
This will take the current query (including the search phrase, page number, etc.), add a ‘posts per page’ parameter, then pass it back to WordPress’s query engine.
Great thoughts Michael. This is helpful for me. I will save your URL and of course your post for future reference.
Might you consider making a plugin to do this? I would cheerfully donate to own such a helpful thing without having to get my fingers into code (at which I purely suck).
Mike, awesome thoughts, did you decide if you will make a plugin to help do so?
Great idea. I re-implemented it using filter hooks (
the_excerpt
andget_the_excerpt
) so this works with any theme that usesthe_excerpt()
to display post excerpts, without replacingsearch.com
. You can see it at bililite.com/blog/2012/08/01/contextual-search-results-in-wordpress. That would be trivial to put into a plugin.Nice one Danny!
This came handy. With your and Danny’s examples I was able to achieve just what I wanted for one of my sites (as php still remains pretty much a mystery for me :] ). Thanks for sharing.
this is way late but… I’m getting HTML char codes instead of certain characters in my excerpt: ’
is there a quick work around?
Hmm, I’m not sure, actually – I think this is a feature of the WordPress excerpt system, unless I’m mistaken. You could try adding, just before the “// Search content for terms” comment:
$content = str_replace("&", "&", $content);
That would undo the escaping of “&” characters that is being applied.
yea… no. actually, it converts apostrophes and quotes into HTML chars but that IS probably part of WP.
thanks anyway!
so this line:
$content = htmlspecialchars_decode($content, ENT_NOQUOTES);
did the trick