: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
foreach ( $capability__not_in as $cap ) {
if ( in_array( $cap, $role_caps, true ) ) {
$role__in = array_merge( $role__in, $capability__in );
$role__not_in = array_merge( $role__not_in, $capability__not_in );
$roles = array_unique( $roles );
$role__in = array_unique( $role__in );
$role__not_in = array_unique( $role__not_in );
// Support querying by capabilities added directly to users.
if ( $blog_id && ! empty( $capabilities ) ) {
$capabilities_clauses = array( 'relation' => 'AND' );
foreach ( $capabilities as $cap ) {
$clause = array( 'relation' => 'OR' );
'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
'value' => '"' . $cap . '"',
if ( ! empty( $caps_with_roles[ $cap ] ) ) {
foreach ( $caps_with_roles[ $cap ] as $role ) {
'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
'value' => '"' . $role . '"',
$capabilities_clauses[] = $clause;
$role_queries[] = $capabilities_clauses;
if ( empty( $this->meta_query->queries ) ) {
$this->meta_query->queries[] = $capabilities_clauses;
// Append the cap query to the original queries and reparse the query.
$this->meta_query->queries = array(
array( $this->meta_query->queries, array( $capabilities_clauses ) ),
$this->meta_query->parse_query_vars( $this->meta_query->queries );
if ( $blog_id && ( ! empty( $roles ) || ! empty( $role__in ) || ! empty( $role__not_in ) || is_multisite() ) ) {
$roles_clauses = array( 'relation' => 'AND' );
if ( ! empty( $roles ) ) {
foreach ( $roles as $role ) {
$roles_clauses[] = array(
'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
'value' => '"' . $role . '"',
$role_queries[] = $roles_clauses;
$role__in_clauses = array( 'relation' => 'OR' );
if ( ! empty( $role__in ) ) {
foreach ( $role__in as $role ) {
$role__in_clauses[] = array(
'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
'value' => '"' . $role . '"',
$role_queries[] = $role__in_clauses;
$role__not_in_clauses = array( 'relation' => 'AND' );
if ( ! empty( $role__not_in ) ) {
foreach ( $role__not_in as $role ) {
$role__not_in_clauses[] = array(
'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
'value' => '"' . $role . '"',
$role_queries[] = $role__not_in_clauses;
// If there are no specific roles named, make sure the user is a member of the site.
if ( empty( $role_queries ) ) {
'key' => $wpdb->get_blog_prefix( $blog_id ) . 'capabilities',
// Specify that role queries should be joined with AND.
$role_queries['relation'] = 'AND';
if ( empty( $this->meta_query->queries ) ) {
$this->meta_query->queries = $role_queries;
// Append the cap query to the original queries and reparse the query.
$this->meta_query->queries = array(
array( $this->meta_query->queries, $role_queries ),
$this->meta_query->parse_query_vars( $this->meta_query->queries );
if ( ! empty( $this->meta_query->queries ) ) {
$clauses = $this->meta_query->get_sql( 'user', $wpdb->users, 'ID', $this );
$this->query_from .= $clauses['join'];
$this->query_where .= $clauses['where'];
if ( $this->meta_query->has_or_relation() ) {
$this->query_fields = 'DISTINCT ' . $this->query_fields;
$qv['order'] = isset( $qv['order'] ) ? strtoupper( $qv['order'] ) : '';
$order = $this->parse_order( $qv['order'] );
if ( empty( $qv['orderby'] ) ) {
// Default order is by 'user_login'.
$ordersby = array( 'user_login' => $order );
} elseif ( is_array( $qv['orderby'] ) ) {
$ordersby = $qv['orderby'];
// 'orderby' values may be a comma- or space-separated list.
$ordersby = preg_split( '/[,\s]+/', $qv['orderby'] );
$orderby_array = array();
foreach ( $ordersby as $_key => $_value ) {
// Integer key means this is a flat array of 'orderby' fields.
// Non-integer key means this the key is the field and the value is ASC/DESC.
$parsed = $this->parse_orderby( $_orderby );
if ( 'nicename__in' === $_orderby || 'login__in' === $_orderby ) {
$orderby_array[] = $parsed;
$orderby_array[] = $parsed . ' ' . $this->parse_order( $_order );
// If no valid clauses were found, order by user_login.
if ( empty( $orderby_array ) ) {
$orderby_array[] = "user_login $order";
$this->query_orderby = 'ORDER BY ' . implode( ', ', $orderby_array );
if ( isset( $qv['number'] ) && $qv['number'] > 0 ) {
$this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $qv['offset'], $qv['number'] );
$this->query_limit = $wpdb->prepare( 'LIMIT %d, %d', $qv['number'] * ( $qv['paged'] - 1 ), $qv['number'] );
if ( isset( $qv['search'] ) ) {
$search = trim( $qv['search'] );
$leading_wild = ( ltrim( $search, '*' ) !== $search );
$trailing_wild = ( rtrim( $search, '*' ) !== $search );
if ( $leading_wild && $trailing_wild ) {
} elseif ( $leading_wild ) {
} elseif ( $trailing_wild ) {
$search = trim( $search, '*' );
$search_columns = array();
if ( $qv['search_columns'] ) {
$search_columns = array_intersect( $qv['search_columns'], array( 'ID', 'user_login', 'user_email', 'user_url', 'user_nicename', 'display_name' ) );
if ( ! $search_columns ) {
if ( str_contains( $search, '@' ) ) {
$search_columns = array( 'user_email' );
} elseif ( is_numeric( $search ) ) {
$search_columns = array( 'user_login', 'ID' );
} elseif ( preg_match( '|^https?://|', $search ) && ! ( is_multisite() && wp_is_large_network( 'users' ) ) ) {
$search_columns = array( 'user_url' );
$search_columns = array( 'user_login', 'user_url', 'user_email', 'user_nicename', 'display_name' );
* Filters the columns to search in a WP_User_Query search.
* The default columns depend on the search term, and include 'ID', 'user_login',
* 'user_email', 'user_url', 'user_nicename', and 'display_name'.
* @param string[] $search_columns Array of column names to be searched.
* @param string $search Text being searched.
* @param WP_User_Query $query The current WP_User_Query instance.
$search_columns = apply_filters( 'user_search_columns', $search_columns, $search, $this );
$this->query_where .= $this->get_search_sql( $search, $search_columns, $wild );
if ( ! empty( $include ) ) {
$ids = implode( ',', $include );
$this->query_where .= " AND $wpdb->users.ID IN ($ids)";
} elseif ( ! empty( $qv['exclude'] ) ) {
$ids = implode( ',', wp_parse_id_list( $qv['exclude'] ) );
$this->query_where .= " AND $wpdb->users.ID NOT IN ($ids)";
// Date queries are allowed for the user_registered field.
if ( ! empty( $qv['date_query'] ) && is_array( $qv['date_query'] ) ) {
$date_query = new WP_Date_Query( $qv['date_query'], 'user_registered' );
$this->query_where .= $date_query->get_sql();
* Fires after the WP_User_Query has been parsed, and before
* The passed WP_User_Query object contains SQL parts formed
* from parsing the given query.
* @param WP_User_Query $query Current instance of WP_User_Query (passed by reference).
do_action_ref_array( 'pre_user_query', array( &$this ) );
* Executes the query, with the current variables.
* @global wpdb $wpdb WordPress database abstraction object.
public function query() {
if ( ! did_action( 'plugins_loaded' ) ) {
/* translators: %s: plugins_loaded */
__( 'User queries should not be run before the %s hook.' ),
'<code>plugins_loaded</code>'
$qv =& $this->query_vars;
// Do not cache results if more than 3 fields are requested.
if ( is_array( $qv['fields'] ) && count( $qv['fields'] ) > 3 ) {
$qv['cache_results'] = false;
* Filters the users array before the query takes place.
* Return a non-null value to bypass WordPress' default user queries.
* Filtering functions that require pagination information are encouraged to set
* the `total_users` property of the WP_User_Query object, passed to the filter
* by reference. If WP_User_Query does not perform a database query, it will not
* have enough information to generate these values itself.
* @param array|null $results Return an array of user data to short-circuit WP's user query
* or null to allow WP to run its normal queries.
* @param WP_User_Query $query The WP_User_Query instance (passed by reference).
$this->results = apply_filters_ref_array( 'users_pre_query', array( null, &$this ) );
if ( null === $this->results ) {
// Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841.
"SELECT {$this->query_fields}
$cache_key = $this->generate_cache_key( $qv, $this->request );
$cache_group = 'user-queries';
if ( $qv['cache_results'] ) {
$cache_value = wp_cache_get( $cache_key, $cache_group );
if ( false !== $cache_value ) {
$this->results = $cache_value['user_data'];
$this->total_users = $cache_value['total_users'];
if ( is_array( $qv['fields'] ) ) {
$this->results = $wpdb->get_results( $this->request );
$this->results = $wpdb->get_col( $this->request );
if ( isset( $qv['count_total'] ) && $qv['count_total'] ) {
* Filters SELECT FOUND_ROWS() query for the current WP_User_Query instance.
* @since 5.1.0 Added the `$this` parameter.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $sql The SELECT FOUND_ROWS() query for the current WP_User_Query.
* @param WP_User_Query $query The current WP_User_Query instance.
$found_users_query = apply_filters( 'found_users_query', 'SELECT FOUND_ROWS()', $this );
$this->total_users = (int) $wpdb->get_var( $found_users_query );
if ( $qv['cache_results'] ) {
'user_data' => $this->results,
'total_users' => $this->total_users,
wp_cache_add( $cache_key, $cache_value, $cache_group );
if ( ! $this->results ) {
is_array( $qv['fields'] ) &&
isset( $this->results[0]->ID )
foreach ( $this->results as $result ) {
$result->id = $result->ID;
} elseif ( 'all_with_meta' === $qv['fields'] || 'all' === $qv['fields'] ) {
if ( function_exists( 'cache_users' ) ) {
cache_users( $this->results );
foreach ( $this->results as $userid ) {
if ( 'all_with_meta' === $qv['fields'] ) {
$r[ $userid ] = new WP_User( $userid, '', $qv['blog_id'] );
$r[] = new WP_User( $userid, '', $qv['blog_id'] );
* Retrieves query variable.
* @param string $query_var Query variable key.
public function get( $query_var ) {
if ( isset( $this->query_vars[ $query_var ] ) ) {
return $this->query_vars[ $query_var ];
* @param string $query_var Query variable key.
* @param mixed $value Query variable value.
public function set( $query_var, $value ) {
$this->query_vars[ $query_var ] = $value;
* Used internally to generate an SQL string for searching across multiple columns.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $search Search string.
* @param string[] $columns Array of columns to search.
* @param bool $wild Whether to allow wildcard searches. Default is false for Network Admin, true for single site.
* Single site allows leading and trailing wildcards, Network Admin only trailing.
protected function get_search_sql( $search, $columns, $wild = false ) {
$leading_wild = ( 'leading' === $wild || 'both' === $wild ) ? '%' : '';
$trailing_wild = ( 'trailing' === $wild || 'both' === $wild ) ? '%' : '';
$like = $leading_wild . $wpdb->esc_like( $search ) . $trailing_wild;
foreach ( $columns as $column ) {
if ( 'ID' === $column ) {
$searches[] = $wpdb->prepare( "$column = %s", $search );
$searches[] = $wpdb->prepare( "$column LIKE %s", $like );
return ' AND (' . implode( ' OR ', $searches ) . ')';
* Returns the list of users.
* @return array Array of results.
public function get_results() {
* Returns the total number of users for the current query.
* @return int Number of total users.
public function get_total() {
return $this->total_users;
* Parses and sanitizes 'orderby' keys passed to the user query.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $orderby Alias for the field to order by.
* @return string Value to used in the ORDER clause, if `$orderby` is valid.
protected function parse_orderby( $orderby ) {
$meta_query_clauses = $this->meta_query->get_clauses();
if ( in_array( $orderby, array( 'login', 'nicename', 'email', 'url', 'registered' ), true ) ) {