2

I have been trying various ways and means to create a user form that would allow people to search a WordPress site using the following fields:

All of these words: < input field >
The exact phrase: < input field >
Any of these words: < input field >
None of these words: < input field >

However I am just not getting it to work - is this even possible within the WordPress scenario?

I had thought I could somehow manipulate the "s" parameter and pass in operators like plus, minus and double quotes but that doesn't seem to work.

Every plugin or page that I find on WordPress search show various ways and means of targeting categories or tags and so on but none for actually handling the "s" parameter itself

Is this possible actually or I am just going down the wrong path?

1

It is somehow possible as you can phrase SQL queries to match your conditions but it will not perform very well as the MySQL table structure is not designed for this kind of full-text search.

However, the solution requires more than just passing the search query (the user input) to the default WordPress s query variable. You'll have to adapt the complete SQL query manually.

First, let's have a look on the concrete MySQL clauses for each of your condition. In the examples I assume, that the user's search query is lorem ipsum dolor.

All of these words

WHERE 1=1 AND
    `post_content` LIKE "%lorem%"
AND 
    `post_content` LIKE "%ipsum%"
AND 
    `post_content` LIKE "%dolor%"

The example shows just the WHERE clause and only compares against the post_content field. As we want the result to match all three words, we look for every word solely and combine each comparison with an AND. % is a wildcard character that says MySQL that there might be other characters or not.

The exact phrase

WHERE 1=1 AND
    `post_content` LIKE "%lorem ipsum dolor%"

In this case we match for the exact phrase.

Any of these words

WHERE 1=1 AND
    `post_content` LIKE "%lorem%"
OR 
    `post_content` LIKE "%ipsum%"
OR
    `post_content` LIKE "%dolor%"

This one looks pretty similar to the first example except that we replaced the AND with an OR as we want to one or more of all these words.

None of these words

WHERE 1=1 AND
    `post_content` NOT LIKE "%lorem%"
AND 
    `post_content` NOT LIKE "%ipsum%"
AND
    `post_content` NOT LIKE "%dolor%"

Finally we reverse the logic and searching for entries that does not match each of the words in the search query.

Filter the WordPress query

The important filter to alter the WHERE clause in the WordPress DB query is called posts_where.

To affect only the main query I would start on pre_get_posts to assign the callback to posts_where:

<?php

add_action(
    'pre_get_posts',
    function( WP_Query $main_query ) {
        // Don't filter custom queries
        if ( ! $main_query->is_main_query() )
            return;

        // Validate the request if it comes from your search form
        if ( ! wpse235391_is_custom_search() ) {
            return;
        }

        add_filter(
            'posts_where',
            function( $where, WP_Query $query ) use ( $main_query ) {
                // Don't filter other queries than our main query
                if ( $query !== $main_query ) {
                    return $where;
                }

                // Validate and sanitize your search query here
                // Don't forget to use esc_sql() on each term!
                $search_query = trim( $_GET[ 'search_input' ] );
                $search_words = explode( ' ', $search_query );
                // remove empty words
                $search_words = array_filter(
                    $search_words,
                    function( $value ) {
                        return $value !== '';
                    }
                );
                array_map( 'esc_sql', $search_words );

                // Build the WHERE clause according to your request

                return $where;
            }
        );
    }
);

Please note, that this code is just an example that should describe the general process. I would not suggest to use this code in production as it is not testable. The usage of anonymous functions is just a way to reduce the code to the essential part. Also it is not tested by myself and might need some further improvements to increase the interoperability. E.g. the conditional tag is_search() will return FALSE in this case.

One word about scalability

These queries will be expensive in comparison to the default WordPress queries. That mean they will run slow if you have a large data pool and they will stress your MySQL server if you're expecting high traffic on your search (e.g. using that search for a auto suggest UI). That is nothing that can completely be «optimized away», we're just reaching the limits of MySQL. The better way to go in these cases is to use a optimized search index like Solr or Elasticsearch.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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