Search Functionality broken by the wp 4.2 update

Repost from wordpress.stackexchange:

I run a blog site with posts by authors, and I implemented a search that would return results if the search was like an author’s name or the search term was contained in a story. I was functioning and properly returned authors who matched the search, but now the search returns every user every single time.

Read More

I suspect it has something to do with WP_User_query since since that function was updated in 4.2, but I have no idea how I would change it to be compatible.

Here is the search code:

add_filter('user_search_columns', 'user_search_columns_bd' , 10, 3);

function user_search_columns_bd($search_columns, $search, $this){
    if(!in_array('display_name', $search_columns)){
        $search_columns[] = 'display_name';
    }
    return $search_columns;
}

add_filter( 'get_meta_sql', 'user_meta_filter', 10, 6 );

function user_meta_filter( $sql, $queries, $type, $primary_table, $primary_id_column, $context ){
    if ( $type !== 'user' ){
        return $sql;
    }

    if ( ! isset( $context->query_vars['meta_query']['replace_and'] ) || $context->query_vars['meta_query']['replace_and'] !== true ){
        return $sql;
    }

    $sql['where'] = preg_replace('/AND/', 'OR', $sql['where'], 1);
    return $sql;
}   


$args = array(
'search'         => $s,
'search_columns' => array( 'user_login', 'user_email'),
'meta_query' => array(
    'relation' => 'OR',
    'replace_and' => true, 
    array(
        'key'     => 'first_name',
        'value'   => $s,
        'compare' => 'LIKE'
    ),
    array(
        'key'     => 'last_name',
        'value'   => $s,
        'compare' => 'LIKE'
    )
)
);

// The Query
$user_query = new WP_User_Query( $args ); 

EDIT:

The relevant sql in array form:

Array ( [join] => INNER JOIN wp_usermeta ON ( wp_users.ID = wp_usermeta.user_id ) [where] => OR ( ( wp_usermeta.meta_key = 'first_name' AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%Search%' ) OR ( wp_usermeta.meta_key = 'last_name' AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%Search%' ) ) )

I’m not quite sure why this sql no longer works in the latest update. It seems to be properly formatted, but it still returns all authors.

Related posts

Leave a Reply

1 comment

  1. It seems like I was right (see comment to question): I had the possibility to test the query before and after the update, and it changed:

    # BEFORE
    SELECT DISTINCT SQL_CALC_FOUND_ROWS wp_users.* FROM wp_users INNER JOIN wp_usermeta ON ( wp_users.ID = wp_usermeta.user_id )  INNER JOIN wp_usermeta AS mt1 ON ( wp_users.ID = mt1.user_id ) WHERE 1=1 AND (user_login LIKE 'test' OR user_email LIKE 'test' OR display_name LIKE 'test') OR ( 
      ( 
        ( 
          ( wp_usermeta.meta_key = 'first_name' AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%test%' ) 
          OR 
          ( wp_usermeta.meta_key = 'last_name' AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%test%' )
        ) 
        AND 
        mt1.meta_key = 'wp_6_capabilities'
      )
    ) ORDER BY user_login ASC 
    
    
    # AFTER
    SELECT DISTINCT SQL_CALC_FOUND_ROWS wp_users.* FROM wp_users INNER JOIN wp_usermeta ON ( wp_users.ID = wp_usermeta.user_id )  INNER JOIN wp_usermeta AS mt1 ON ( wp_users.ID = mt1.user_id ) WHERE 1=1 OR ( 
      ( 
        ( 
          ( wp_usermeta.meta_key = 'first_name' AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%test%' ) 
          OR 
          ( wp_usermeta.meta_key = 'last_name' AND CAST(wp_usermeta.meta_value AS CHAR) LIKE '%test%' )
        ) 
        AND 
        mt1.meta_key = 'wp_6_capabilities'
      )
    ) AND (user_login LIKE 'test' OR user_email LIKE 'test' OR display_name LIKE 'test') ORDER BY user_login ASC
    

    So with this line in your code

    $sql['where'] = preg_replace('/AND/', 'OR', $sql['where'], 1);
    

    you’re changing the first AND found in the query with an OR, but as you can see now it targets the wrong one.

    In fact now the condition is WHERE 1=1 OR ..., that is like saying ALWAYS. 🙂

    So as I said earlier in the comment you need to target the right AND, but be careful that order can change again.

    Update

    Unfortunately there’s no ultimate way to do this because is not native (let’s hope it’ll be added soon), so you need to rely on changing the right AND.

    Maybe find a way that works for both the queries (old and new), and to do that you need to change filter hook: use pre_user_query:

    add_action('pre_user_query', 'my_custom_users_search');
    function my_custom_users_search( $args ) {
        if( isset( $args->query_vars['meta_query']['replace_and'] ) && $args->query_vars['meta_query']['replace_and'] )
            $args->query_where = str_replace(') AND (', ') OR (', $args->query_where);
    }