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.
notin:
parameter.