Wordpress - Empty search input returns all posts
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 );
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 postsPassing an invalid term to a
tax_query
or using thename
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();
});
}
}
});