1

I'm trying to intercept the search query before it's executed in Wordpress. I've tried a bunch of things but it's just not working.

So, for example, a user enters the search term RED SHOES. I want all occurences of RED to be changed to ROSSO (it's more complex than this, but you get the idea; it's to do with translating).

I'm looking at something like this:

function modify_search_vars($search_vars) {

    $change_to = array(
        "blue"  => "blu",
        "red"   => "rosso",
    );

    if ( !empty($search_vars['s']) ) {
        $search_vars['s'] = str_ireplace(array_keys($change_to), array_values($change_to), $search_vars['s']);
    }
    return $search_vars;

}
add_filter('request', 'modify_search_vars', 99);

Any help or ideas here would be much appreciated! Thanks!

  • Can you check if the get_search_query filter works? Can't dig deep enough in the code to find out, if it is used for the query or only for displaying. (testing could be add_filter('get_search_query', function($search){ $search = str_ireplace(...); return $search });) – kero Nov 19 '18 at 17:33
  • Check my newest edit. – coolpasta Nov 21 '18 at 18:47
2

In order to achieve a deeper "injection" into the search and catch what the user has searched as soon as possible, we have to look at the WP_Query class and see how it treats a query for posts:

In its parse_query function wee that it uses a flag to signal to the whole class that, indeed, the query is a "search", not surprisingly, it looks for s inside the query:

if ( isset( $this->query['s'] ) ) {
    $this->is_search = true;
}

Great, but there are no actions like do_action( 'super_important_hook' . $handle_that_we_could_use, $query) where WordPress lets us hook into this big, vital process on a "per name basis", so what can we do?

We have parse_query which fires before pre_get_posts:

function replace_search( $query_object )
{
    if( $query_object->is_search() ) {
        $raw_search = $query_object->query['s'];

        $replacement = str_replace( 'red', 'rosso', $raw_search );

        if( $replacement ) {
            $query_object->set( 's', $replacement );
        }
    }
}

add_action( 'parse_query', 'replace_search' );

...and it works! We're hooking as early as we possibly can and modify the query as soon as it happens. The set function does exactly what it says, it's a setter for any of the internal variables used for the class.

By the way, the hook parse_tax_query fires before parse_query since $this->parse_tax_query() which contains parse_tax_query is called inside the parse_query() function of WP_Query which passes the same WP_Query Object to the action. But here comes my opinion which could be very wrong: I wouldn't use this hook as it seems parse_query is way more popular to hook into for this type of behavior.

First, Remember that unless plugins also use this hook to look for the search string, they might look for $_REQUEST['s'] which comes before these hooks and it might be modified at that point. Regardless, the earliest point where the user's search enters the system is $_REQUEST['s'] so, you'd need to hook into this value as early as possible, my answer is pretty good here, but I think there are way more things to consider when playing with search, and of course, if their functions have higher priority and they transform the string into something else, you're doomed.

Obviously, you'll have a more sophisticated "replacement" mechanism but that's just to show what hooks you need.

Second, an interesting point about these actions is that they pass their objects by reference:

/**
 * Fires after the main query vars have been parsed.
 *
 * @since 1.5.0
 *
 * @param WP_Query $this The WP_Query instance (passed by reference).
 */
do_action_ref_array( 'parse_query', array( &$this ) );

What this means and it's good to keep in mind - every single modification you do when your scripts hook into parse_query is reflected on the actual class itself that gets passed to the next script that it's hooked to or, well, your changes will be reflected for the entire life-cycle of the said class. I can't tell whether or not this was a good design choice, just letting you know in case you'll have some "why does this variable not reset? It's a new class instance.", the class behaves like a singleton, but it's not, so remember you're playing with the same class up until WP is finished with it and all the changes you do to it will be used by whatever comes after you, in short: be very mindful with what you do, there are a lot of processes / plugins using this as WP_Query is the actual core of WP itself.


If you're looking to 'hook' as late as possible, this will still modify the query itself but it'll (I believe) hook late, giving the theme / plugins possibility to play with this value before you see it:

add_filter( 'get_search_query', function( $user_search_query ) {
    $replacement = str_replace( 'red', 'rosso', $user_search_query );

    if( $replacement ) {
        return $replacement;
    }

    return $user_search_query;
}, 999);

The hook you're looking for is get_search_query. But please know that there's a lot of plugins / things that play with this, so, be aware of the implications of changing this. The main function that uses it is:

/**
 * Displays the contents of the search query variable.
 *
 * The search query string is passed through esc_attr() to ensure that it is safe
 * for placing in an html attribute.
 *
 * @since 2.1.0
 */
function the_search_query() {
    /**
     * Filters the contents of the search query variable for display.
     *
     * @since 2.3.0
     *
     * @param mixed $search Contents of the search query variable.
     */
    echo esc_attr( apply_filters( 'the_search_query', get_search_query( false ) ) );
}

From general-template.php in WP Core. This parameter is passed to our filter from get_search_query which is just a simple wrapper over getting whatever is in $_REQUEST['s'].

  • It looks like get_search_query is changed AFTER the search has been executed and not before. – arathra Nov 19 '18 at 18:15
  • @arathra Check my edit. – coolpasta Nov 19 '18 at 19:50
  • parse_query definitely fires before pre_get_posts – Milo Nov 25 '18 at 2:10
  • @Milo It actually is, on line 1620 in class-wp-query.php you see that $this->parse_query() which contains the parse_query action is called exactly before pre_get_posts, so parse_query is the earliest hook you have for the whole process. I changed my answer to reflect this. But parse_query comes after a few internals for the class have been set, so it's still not the "raw". You also have the parse_tax_query hook which fires before parse_query, it might be worth looking at it. – coolpasta Nov 25 '18 at 12:01
  • request is the earliest and fires before WordPress sets anything, which lets you “convert” one type of request to another just be setting/unsetting query vars. – Milo Nov 25 '18 at 13:43

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.