0

I have been looking to extend the ability to search users in the backend for some time and have tried variations of several functions recommended in these articles and posts along with others not linked to shown below -

-https://wordpress.stackexchange.com/questions/339209/making-users-php-search-include-a-specific-user-meta-data-field-without-messing

-https://rudrastyh.com/wordpress/pre_user_query.html

-https://wordpress.stackexchange.com/questions/5811/how-to-search-all-user-meta-from-users-php-in-the-admin

I have used the function below as a template -

add_action('pre_user_query','rudr_extend_user_search');

function rudr_extend_user_search( $u_query ){ // make sure that this code will be applied only for user search if ( $u_query->query_vars['search'] ){ $search_query = trim( $u_query->query_vars['search'], '*' ); if ( $_REQUEST['s'] == $search_query ){ global $wpdb;

        // let's search by users first name
        $u_query->query_from .= " JOIN {$wpdb->usermeta} fname ON fname.user_id = {$wpdb->users}.ID AND fname.meta_key = 'first_name'";
        
        // you can add here any meta key you want to search by
        // $u_query->query_from .= " JOIN {$wpdb->usermeta} cstm ON cstm.user_id = {$wpdb->users}.ID AND cstm.meta_key = 'YOU CUSTOM meta_key'";

        // let's search by all the post titles, the user has been published
        $u_query->query_from .= " JOIN {$wpdb->posts} psts ON psts.post_author = {$wpdb->users}.ID";
        
        // what fields to include in the search
        $search_by = array( 'user_login', 'user_email', 'fname.meta_value', 'psts.post_title' );
        
        // apply to the query
        $u_query->query_where = 'WHERE 1=1' . $u_query->get_search_sql( $search_query, $search_by, 'both' );
    }
}

}

and also developed a function which is not working at all where I am attempting collect an email domain and role if they are entered into the search input as a string such as "@yahoo contributor" by imploding the string and then searching for the first value in the users table using a LIKE operator and perform a JOIN to the usermeta table where I would then search wp_capabilities for a value that is comparable to the role that would be the second value of the imploded string. I use the "pre_user_query" hook with my function but am only getting errors and feel I'm very far away from a solution. Any help or guidance is very appreciated.

2
  • You can do this without the joins, all the capability is right there in WP_User_Query. Give me an hour and i'll post some sample code you can work from.
    – t31os
    Commented Oct 8, 2022 at 20:20
  • Updated for my answer, scroll down for revised code with a notin: parameter.
    – t31os
    Commented Oct 9, 2022 at 21:10

1 Answer 1

0

You can do this more simply by using the pre_get_users hook, grab the search string extract the role from it and add that as a role parameter for WP_User_Query.

Edited my original code, here's a refined version, bit cleaner.

Additional edit: Scroll further down for update (see follow-up).

class wpse_410251_user_search {
    
    private $role = false;
    private $search = '';
    
    public function __construct() { 
        add_action( 'admin_init', array( $this, 'admin_init' ) );
    }
    
    public function admin_init() {
        add_action( 'pre_get_users', array( $this, 'pre_get_users' ) );
    }
    
    public function pre_get_users( $query ) {
        
        // $query is a WP_User_Query object
        
        global $pagenow;    
        
        // If not admin or the user listing
        if( !is_admin() || 'users.php' !== $pagenow )
            return;
        
        // If not a search
        if( empty( $query->get('search') ) )
            return;
        
        // Do escaping early and trim off the wildcards
        $search = trim( esc_attr( $query->get('search') ),'*' );
        
        // Check the search for appropriate search terms and set class vars, returns true/false as appropriate
        if( !$this->is_role_search( $search ) )
            return;
        
        // Update the search query var (adding back the wildcards)
        $query->set( 'search', "*{$this->search}*" );
        // Set the role query var
        $query->set( 'role', $this->role );
    }
    private function is_role_search( $search ) {
        
        // Split the search on space
        $search = explode( ' ', $search );
        
        // If no second search term or @ isn't the first search character
        if( !isset( $search[1] ) || '@' !== mb_substr( $search[0], 0, 1 ) )
            return false;
        
        // Bring the WP roles into scope
        global $wp_roles;
        
        // Use the $wp_roles->role_names array to iterate over and see if we get a match to an existing role
        foreach( $wp_roles->role_names as $role => $name ) {
            // Check against the lowercase slug and the display name
            if( $search[1] === $role || $search[1] === $name )
                // Store the matched role
                $this->role = $role;
        }
        // If no role set for the class, there's no match, so not a role search
        if( !$this->role )
            return false;
        
        // Store the first part of the search for updating the search query var
        $this->search = $search[0];
        
        // Else we reach here, a role has been set, return true
        return true;
    }
}
$wpse_410251_user_search = new wpse_410251_user_search;

Shame there's no convenient action hook on the users page to insert a dropdown next to the search box, it would have been nice to build it into a dropdown selection so you can select a role as you search (instead of having to type it in).

Now you can search on the users page using, for example @yahoo Subscriber or @gmail.com author, and so on. You can use the role slug or display name, it will work with either case.

If you want to limit this all to only working for admins, simply add a if( !current_user_can( 'manage_options' ) ) return; or similar check inside the pre_get_users function.

Follow-up:

You asked about adding in a notin: parameter so you can filter your users by role where their email isn't matching. This isn't as clean as the above code, because there's no built-in query vars with WP_User_Query to say search the user fields such as name, email, login, etc, with a NOT LIKE match.

It is possible however to add an additional hook onto pre_user_query to tweak the query_where data before the query runs and perform a string replacement on the part of the query that checks the user's email, simply swapping the LIKE portion to NOT LIKE so it does an inverted match.

I took this opporunity to also switch around the order of parameters, so it's Role :type @email.

Searching for users with a role and email matching a domain is Subscriber in: @gmail.com and users with a role whose email doesn't match the domain is Subscriber notin: @gmail.com.

class wpse_410251_user_search {
    
    private $role = false;
    private $update_where = false;
    private $search = '';
    
    public function __construct() { 
        add_action( 'admin_init', array( $this, 'admin_init' ) );
    }
    
    public function admin_init() {
        add_action( 'pre_get_users', array( $this, 'pre_get_users' ) );
        add_action( 'pre_user_query', array( $this, 'pre_user_query' ) );
    }
    public function pre_user_query( $query ){
        if( $this->update_where )
            $query->query_where = str_replace( 'user_email LIKE', 'user_email NOT LIKE', $query->query_where );
    }
    
    public function pre_get_users( $query ) {
        
        // $query is a WP_User_Query object
        
        global $pagenow;    
        
        // If not admin or the user listing
        if( !is_admin() || 'users.php' !== $pagenow )
            return;
        
        // If not a search
        if( empty( $query->get('search') ) )
            return;
        
        // Do escaping early and trim off the wildcards
        $search = trim( esc_attr( $query->get('search') ),'*' );
        
        // Check the search for appropriate search terms and set class vars, returns true/false as appropriate
        if( !$this->is_role_search( $search ) )
            return;
        
        // Update the search query var (adding back the wildcards), less the role and in/notin parts
        $query->set( 'search', "*{$this->search}*" );
        // Set the role query var
        $query->set( 'role', $this->role );
    }
    private function is_role_search( $search_terms ) {
        
        // Bail early if there's no @ character
        if( false === strpos( $search_terms, '@' ) )
            return false;
        
        // Otherwise move along and explode the search
        $search_terms = explode( ' ', $search_terms );      
        
        // There should be 3 parameters for our use case
        if( 3 !== count( $search_terms ) )
            return false;
        /*
            in: no query_where changes
            notin: set a flag and adjust query_where
            
            This keeps the array structure consistent
        
            [0] = role 
            [1] = type (in/not in)
            [2] = @example.com 
            
            Example use:
            Subscriber notin: @wordpress.com
            Author in: @gmail.com
        */
        
        // Check one of the two allowed values is provided
        if( !in_array( $search_terms[1], array( 'in:', 'notin:' ) ) )
            return false;
        
        // Bring the WP roles into scope
        global $wp_roles;
        
        // Using the $wp_roles->role_names array to iterate over and find a matching role
        foreach( $wp_roles->role_names as $role => $name ) {
            // Check against the lowercase slug and the display name
            if( $search_terms[0] === $role || $search_terms[0] === $name )
                // Store the matched role
                $this->role = $role;
        }
        
        // If no role set return false
        if( !$this->role )
            return false;
        
        // Only need to change pre_user_query when using notin:
        if( 'notin:' == $search_terms[1] )
            $this->update_where = true;

        // The @email portion of the search
        $this->search = $search_terms[2];
        
        // If we're still here, return true
        return true;
    }
}
$wpse_410251_user_search = new wpse_410251_user_search;

If i were building something like this for a project myself i'd probably look at using some jQuery/js to insert extra form elements into the user listing so it can be managed easier with select boxes instead of having to use the search field.

Was a fun little exericse though. Hope that helps.

4
  • Thank you @t31os_ for providing this solution and taking the time to edit and comment it! This is extraordinarily helpful and works great in my local environment. I'd give the answer fifty upvotes if I had the capability. This is also much more elegant than what I was trying to accomplish with custom sql. I'm trying to add a way to search by role now and exclude domains specified in the search input box by adding a word like "notin:" that would then have a domain after the colon, so that if I entered something like "subscriber notin:@gmail" and return subscribers who don't have a gmail domain.
    – Johnathan
    Commented Oct 9, 2022 at 18:12
  • That's when you'd have to start messing with the pre_user_query and tweak the $query->query_where parameter, and you'd need to change the earlier logic i wrote that assumes the search starts with an @. There's some handy not__in parameters for other query vars, but not one specifically when looking at some of the regular user fields, like name, email etc. Let me have a little think on it and see if i can come up with a simple adjustment to the code i posted without making it too messy.
    – t31os
    Commented Oct 9, 2022 at 19:38
  • This is incredibly helpful @t31os_! I would love to know of any recommendations you might have for learning how to develop custom code such as you have here. I had a tricky time working on this before you came to the rescue and was reading through docs and very old articles which were somewhat cryptic at times. Reading through your comments and following the code you've created has really improved my understanding of how to modify the users search.
    – Johnathan
    Commented Oct 10, 2022 at 0:51
  • The function reference on Wordpress and playing around with example plugin code is a good hands on way of getting to grips with how to use hooks. It certainly helps to have a good grasp on PHP beforehand, but google / documentation is every developers best friend.
    – t31os
    Commented Oct 10, 2022 at 14:24

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.