8

So today I've decided to implement a search form onto my site that obviously returns a result when the user searches for it, but I've run into a little problem.

My Problem

When the user searches for a result without inputting anything into the search form for some reason it returns 5 Pages whereas I would like it to return a Message that says something along the lines of 'Search Fields was empty - Search Again'.

How would I go about making this happen? I've inserted my code below that controls my searchform button and the content it outputs when search.

Code Controlling the Search Form

searchform.php This is the Search form which is where the user inputs what they want (I have a feeling I need to enter some code here to check if it is empty)

<form role="search" method="get" id="searchform" action="<?php echo home_url('/'); ?>">
    <div>
        <label class="screen-reader-text" for="s">Search: </label>
        <input type="text" value="" name="s" id="s" placeholder="<?php the_search_query(); ?>" />
        <input type="submit" id="searchsubmit" value="Search" />
    </div>
</form>

search.php This is showing the user what they have searched for and the getting the template content-search to show the Page name(s).

<div class="search-result">
    <div class="container">
        <div class="row">
            <div class="search">
                <?php if (have_posts()) : ?>

                    <h2>Search Result For: <?php the_search_query(); ?></h2>
                    <div class="light-separator small center"></div>

                    <?php
                    while (have_posts()) : the_post();

                    get_template_part('content-search', get_post_format());

                    endwhile;
                    else :
                        echo '
                        <div class="no-content"> 
                            <h3>Ooopss, looks like nothing matches your result.</h3>
                        </div>';
                    endif;
                ?>
                <div class="search-form"><?php get_search_form(); ?></div>
            </div>        
        </div>
    </div>
</div>

content-search.php - This displays the Pages Title and the Permalink for the Page

<div class="searchs">
    <h4>Pages - <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h4>
</div>
2
  • This seems like a bug. I will look into that and see if I can trace it ;-) Feb 4, 2016 at 15:58
  • Posting an answer as we speak Feb 4, 2016 at 16:20

6 Answers 6

11

Just an alternative to the informative answer by @PieterGoosen.

After Pieter posted this part:

if ( ! empty( $q['s'] ) ) {
    $search = $this->parse_search( $q );
}

it came to mind that it might be possible to re-parse the search query, within the posts_search filter, for empty search string. But the parse_search() method is protected and even if we could access it, the empty search would just give us:

AND (((wp_posts.post_title LIKE '%%') OR (wp_posts.post_content LIKE '%%'))) 

and that would just search for everything. So that path wasn't fruitful ;-)

In WordPress 4.5 it has changed to

if ( strlen( $q['s'] ) ) {
    $search = $this->parse_search( $q );
}

Instead we could try to halt the main query, in the case of an empty search:

/**
 * Halt the main query in the case of an empty search 
 */
add_filter( 'posts_search', function( $search, \WP_Query $q )
{
    if( ! is_admin() && empty( $search ) && $q->is_search() && $q->is_main_query() )
        $search .=" AND 0=1 ";

    return $search;
}, 10, 2 );
9
  • 2
    @CoderSte I would think that this should solve your issue better than my approach. Please feel free to unaccept my answer and accepting this one by birgire ;-) Feb 4, 2016 at 19:09
  • That is actually a good idea, by halting the main qurey it takes them to the search.php page which is what I wanted really instead of returning them back to the 404 page, although @PieterGoosen method work perfectly if users do want to redirect them to the 404 Page, Thanks for the additional information @birgire
    – Stephen
    Feb 4, 2016 at 19:09
  • 2
    @PieterGoosen I have done although your answer works fine too, if someone wants to redirect an empty search to an 404 then your idea works perfectly :)
    – Stephen
    Feb 4, 2016 at 19:13
  • @CoderSte great, enjoy and good luck on your project ;-) Feb 4, 2016 at 19:15
  • 3
    phew, just had to go out into the currently crazy weather here, just catching my breath again, ;-) This was mostly about my failed initial idea and was just meant to be an appendix to the answer by @PieterGoosen. CoderSte: I'm generally so lazy that I tend to use anonymous functions a lot on this site ;-) Here 10 is the priority and 2 is the number of callback inputs.
    – birgire
    Feb 4, 2016 at 19:25
8

I'm not sure if this is an intended bug or just a bug that was never anticipated, but it is definitely a flaw in design.

This behavior exists in the following cases that I took note of before

  • Setting an empty array to post__in returns all posts

  • Passing an invalid term to a tax_query or using the name field with a name with special characters or more than one word removes the join clause from the SQL query which also results in all posts being returned. I did an answer regarding this issue

So what happens here is that when we pass a valid string to our search function, the WHERE clause is altered to include our search terms. Normally this is how the WHERE clause looks when we enter a search term called search

AND (((wp_posts.post_title LIKE \'%search%\') 
OR (wp_posts.post_content LIKE \'%search%\'))) 
AND wp_posts.post_type IN (\'post\', \'page\', \'attachment\', \'information\', \'event_type\', \'cameras\') 
AND (wp_posts.post_status = \'publish\' 
OR wp_posts.post_author = 1 
AND wp_posts.post_status = \'private\')

When we pass an empty string, the search clause is removed from the WHERE clause which causes all posts to be returned. This is how the WHERE clause looks like when we pass an empty string

AND wp_posts.post_type IN (\'post\', \'page\', \'attachment\', \'information\', \'event_type\', \'cameras\') 
AND (wp_posts.post_status = \'publish\' OR wp_posts.post_author = 1 
AND wp_posts.post_status = \'private\')

This is the section in WP_Query which is responsible for this

if ( ! empty( $q['s'] ) ) {
    $search = $this->parse_search( $q );
}

The easiest way to get out of this is to return a 404 whenever we pass an empty string as search terms. For this we need to check if we have a valid search string or not, and then set a 404 according to that. You can try the following

add_action( 'pre_get_posts', function ( $q )
{
    if(    !is_admin() // Only target the front end
        && $q->is_main_query() // Only target the main query
        && $q->is_search() // Only target the search page
    ) {
        // Get the search terms
        $search_terms = $q->get( 's' );

        // Set a 404 if s is empty
        if ( !$search_terms ) {
            add_action( 'wp', function () use ( $q )
            {
                $q->set_404();
                status_header(404);
                nocache_headers();
            });
        }
    }
});
5
  • Thanks for the answer - it's really weird as the site has like 15 pages and it returns only 5 and the 5 it returns is really random. I haven't tested your code yet as I'm not near a computer but when I get by one I will try it - basically I wanted it to return the 404 page like you've suggest as that's the only page with the search form
    – Stephen
    Feb 4, 2016 at 17:13
  • Quite strange, on my local install it returns all posts from all post types. But anyways, WP_Query is quite messy to work through and sometimes behaves quite weird. ;-). Take your time and test this fully and let me know if you find any bugs. Enjoy ;-) Feb 4, 2016 at 17:17
  • I know it's very strange the thing is I have like 7 pages and some have child pages and the thing is it doesn't just return the parent pages it returns 2 parent pages and then 3 child pages. I will test and let you know thanks for your reply :)
    – Stephen
    Feb 4, 2016 at 17:19
  • What's amazing is that every time I close the page and re-open it and then empty search again it returns different pages xD I added your code to my functions.php and it worked like a charm it just keeps on the 404 Page - would there be any way to display a different title instead of like 404 display something like Your Search was not found? Thanks for your help Pieter :)
    – Stephen
    Feb 4, 2016 at 18:03
  • Will do after I try to do it my self and Google gives me no answers on it xD
    – Stephen
    Feb 4, 2016 at 18:06
2

If you want to exclude search strings with shorter than a defined length and the empty search string too, you can use this altered function of birgire's solution. In this example empty and search phrases with only one letter give no results:

    add_filter( 'posts_search', function( $search, \WP_Query $q )
    {
        $sphrase = get_search_query();
        $slen = strlen($sphrase);
        $minlen = 2;
            if( ! is_admin() && $slen < $minlen && $q->is_search() && $q->is_main_query() ){
                $search .=" AND 0=1 ";
            }
        return $search;
    }, 10, 2 );
1

If you are using the standard search.php file, it's a lot easier to just add logic in the if ( have_posts()) conditional...

if ( have_posts() && $_GET['s'] != '' )

That way if the search parameter is empty, it doesn't perform the content loop.

1
  • Much easier to implement than the add_filter solution, and works beautifully with a customised search.php, too.
    – fbmd
    Mar 12, 2021 at 9:40
0

To exclude search strings with shorter than a defined length and the empty search string too, you can use this altered function of birgire's solution:

add_filter( 'posts_search', function( $search, \WP_Query $q )
{
    $sphrase = get_search_query();
    $slen = strlen($sphrase);
    $minlen = 2;
        if( ! is_admin() && $slen < $minlen && $q->is_search() && $q->is_main_query() ){
            $search .=" AND 0=1 ";
        }
    return $search;
}, 10, 2 );
0

Adding onto Chad's answer, you can also make it prevent searches with just spaces, which for me was also returning results:

if ( have_posts() && $_GET['s'] !== '' && !ctype_space($_GET['s']) )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.