t_results( $this->request ); } else { $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 3.2.0 * @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'] ) { $cache_value = array( 'user_data' => $this->results, 'total_users' => $this->total_users, ); wp_cache_add( $cache_key, $cache_value, $cache_group ); } } } if ( ! $this->results ) { return; } if ( 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 ); } $r = array(); foreach ( $this->results as $userid ) { if ( 'all_with_meta' === $qv['fields'] ) { $r[ $userid ] = new WP_User( $userid, '', $qv['blog_id'] ); } else { $r[] = new WP_User( $userid, '', $qv['blog_id'] ); } } $this->results = $r; } } /** * Retrieves query variable. * * @since 3.5.0 * * @param string $query_var Query variable key. * @return mixed */ public function get( $query_var ) { if ( isset( $this->query_vars[ $query_var ] ) ) { return $this->query_vars[ $query_var ]; } return null; } /** * Sets query variable. * * @since 3.5.0 * * @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. * * @since 3.1.0 * * @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. * @return string */ protected function get_search_sql( $search, $columns, $wild = false ) { global $wpdb; $searches = array(); $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 ); } else { $searches[] = $wpdb->prepare( "$column LIKE %s", $like ); } } return ' AND (' . implode( ' OR ', $searches ) . ')'; } /** * Returns the list of users. * * @since 3.1.0 * * @return array Array of results. */ public function get_results() { return $this->results; } /** * Returns the total number of users for the current query. * * @since 3.1.0 * * @return int Number of total users. */ public function get_total() { return $this->total_users; } /** * Parses and sanitizes 'orderby' keys passed to the user query. * * @since 4.2.0 * * @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 ) { global $wpdb; $meta_query_clauses = $this->meta_query->get_clauses(); $_orderby = ''; if ( in_array( $orderby, array( 'login', 'nicename', 'email', 'url', 'registered' ), true ) ) { $_orderby = 'user_' . $orderby; } elseif ( in_array( $orderby, array( 'user_login', 'user_nicename', 'user_email', 'user_url', 'user_registered' ), true ) ) { $_orderby = $orderby; } elseif ( 'name' === $orderby || 'display_name' === $orderby ) { $_orderby = 'display_name'; } elseif ( 'post_count' === $orderby ) { // @todo Avoid the JOIN. $where = get_posts_by_author_sql( 'post' ); $this->query_from .= " LEFT OUTER JOIN ( SELECT post_author, COUNT(*) as post_count FROM $wpdb->posts $where GROUP BY post_author ) p ON ({$wpdb->users}.ID = p.post_author)"; $_orderby = 'post_count'; } elseif ( 'ID' === $orderby || 'id' === $orderby ) { $_orderby = 'ID'; } elseif ( 'meta_value' === $orderby || $this->get( 'meta_key' ) === $orderby ) { $_orderby = "$wpdb->usermeta.meta_value"; } elseif ( 'meta_value_num' === $orderby ) { $_orderby = "$wpdb->usermeta.meta_value+0"; } elseif ( 'include' === $orderby && ! empty( $this->query_vars['include'] ) ) { $include = wp_parse_id_list( $this->query_vars['include'] ); $include_sql = implode( ',', $include ); $_orderby = "FIELD( $wpdb->users.ID, $include_sql )"; } elseif ( 'nicename__in' === $orderby ) { $sanitized_nicename__in = array_map( 'esc_sql', $this->query_vars['nicename__in'] ); $nicename__in = implode( "','", $sanitized_nicename__in ); $_orderby = "FIELD( user_nicename, '$nicename__in' )"; } elseif ( 'login__in' === $orderby ) { $sanitized_login__in = array_map( 'esc_sql', $this->query_vars['login__in'] ); $login__in = implode( "','", $sanitized_login__in ); $_orderby = "FIELD( user_login, '$login__in' )"; } elseif ( isset( $meta_query_clauses[ $orderby ] ) ) { $meta_clause = $meta_query_clauses[ $orderby ]; $_orderby = sprintf( 'CAST(%s.meta_value AS %s)', esc_sql( $meta_clause['alias'] ), esc_sql( $meta_clause['cast'] ) ); } return $_orderby; } /** * Generate cache key. * * @since 6.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $args Query arguments. * @param string $sql SQL statement. * @return string Cache key. */ protected function generate_cache_key( array $args, $sql ) { global $wpdb; // Replace wpdb placeholder in the SQL statement used by the cache key. $sql = $wpdb->remove_placeholder_escape( $sql ); $key = md5( $sql ); $last_changed = wp_cache_get_last_changed( 'users' ); if ( empty( $args['orderby'] ) ) { // Default order is by 'user_login'. $ordersby = array( 'user_login' => '' ); } elseif ( is_array( $args['orderby'] ) ) { $ordersby = $args['orderby']; } else { // 'orderby' values may be a comma- or space-separated list. $ordersby = preg_split( '/[,\s]+/', $args['orderby'] ); } $blog_id = 0; if ( isset( $args['blog_id'] ) ) { $blog_id = absint( $args['blog_id'] ); } if ( $args['has_published_posts'] || in_array( 'post_count', $ordersby, true ) ) { $switch = $blog_id && get_current_blog_id() !== $blog_id; if ( $switch ) { switch_to_blog( $blog_id ); } $last_changed .= wp_cache_get_last_changed( 'posts' ); if ( $switch ) { restore_current_blog(); } } return "get_users:$key:$last_changed"; } /** * Parses an 'order' query variable and casts it to ASC or DESC as necessary. * * @since 4.2.0 * * @param string $order The 'order' query variable. * @return string The sanitized 'order' query variable. */ protected function parse_order( $order ) { if ( ! is_string( $order ) || empty( $order ) ) { return 'DESC'; } if ( 'ASC' === strtoupper( $order ) ) { return 'ASC'; } else { return 'DESC'; } } /** * Makes private properties readable for backward compatibility. * * @since 4.0.0 * @since 6.4.0 Getting a dynamic property is deprecated. * * @param string $name Property to get. * @return mixed Property. */ public function __get( $name ) { if ( in_array( $name, $this->compat_fields, true ) ) { return $this->$name; } wp_trigger_error( __METHOD__, "The property `{$name}` is not declared. Getting a dynamic property is " . 'deprecated since version 6.4.0! Instead, declare the property on the class.', E_USER_DEPRECATED ); return null; } /** * Makes private properties settable for backward compatibility. * * @since 4.0.0 * @since 6.4.0 Setting a dynamic property is deprecated. * * @param string $name Property to check if set. * @param mixed $value Property value. */ public function __set( $name, $value ) { if ( in_array( $name, $this->compat_fields, true ) ) { $this->$name = $value; return; } wp_trigger_error( __METHOD__, "The property `{$name}` is not declared. Setting a dynamic property is " . 'deprecated since version 6.4.0! Instead, declare the property on the class.', E_USER_DEPRECATED ); } /** * Makes private properties checkable for backward compatibility. * * @since 4.0.0 * @since 6.4.0 Checking a dynamic property is deprecated. * * @param string $name Property to check if set. * @return bool Whether the property is set. */ public function __isset( $name ) { if ( in_array( $name, $this->compat_fields, true ) ) { return isset( $this->$name ); } wp_trigger_error( __METHOD__, "The property `{$name}` is not declared. Checking `isset()` on a dynamic property " . 'is deprecated since version 6.4.0! Instead, declare the property on the class.', E_USER_DEPRECATED ); return false; } /** * Makes private properties un-settable for backward compatibility. * * @since 4.0.0 * @since 6.4.0 Unsetting a dynamic property is deprecated. * * @param string $name Property to unset. */ public function __unset( $name ) { if ( in_array( $name, $this->compat_fields, true ) ) { unset( $this->$name ); return; } wp_trigger_error( __METHOD__, "A property `{$name}` is not declared. Unsetting a dynamic property is " . 'deprecated since version 6.4.0! Instead, declare the property on the class.', E_USER_DEPRECATED ); } /** * Makes private/protected methods readable for backward compatibility. * * @since 4.0.0 * * @param string $name Method to call. * @param array $arguments Arguments to pass when calling. * @return mixed Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { if ( 'get_search_sql' === $name ) { return $this->get_search_sql( ...$arguments ); } return false; } } * that the context is a shortcode and not part of the theme's template rendering logic. * * @since 6.3.0 * @access private * * @return string The filtered context value for wp_get_attachment_images when doing shortcodes. */ function _filter_do_shortcode_context() { return 'do_shortcode'; } /** * Retrieves the shortcode regular expression for searching. * * The regular expression combines the shortcode tags in the regular expression * in a regex class. * * The regular expression contains 6 different sub matches to help with parsing. * * 1 - An extra [ to allow for escaping shortcodes with double [[]] * 2 - The shortcode name * 3 - The shortcode argument list * 4 - The self closing / * 5 - The content of a shortcode when it wraps some content. * 6 - An extra ] to allow for escaping shortcodes with double [[]] * * @since 2.5.0 * @since 4.4.0 Added the `$tagnames` parameter. * * @global array $shortcode_tags * * @param array $tagnames Optional. List of shortcodes to find. Defaults to all registered shortcodes. * @return string The shortcode search regular expression */ function get_shortcode_regex( $tagnames = null ) { global $shortcode_tags; if ( empty( $tagnames ) ) { $tagnames = array_keys( $shortcode_tags ); } $tagregexp = implode( '|', array_map( 'preg_quote', $tagnames ) ); /* * WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag(). * Also, see shortcode_unautop() and shortcode.js. */ // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation return '\\[' // Opening bracket. . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]. . "($tagregexp)" // 2: Shortcode name. . '(?![\\w-])' // Not followed by word character or hyphen. . '(' // 3: Unroll the loop: Inside the opening shortcode tag. . '[^\\]\\/]*' // Not a closing bracket or forward slash. . '(?:' . '\\/(?!\\])' // A forward slash not followed by a closing bracket. . '[^\\]\\/]*' // Not a closing bracket or forward slash. . ')*?' . ')' . '(?:' . '(\\/)' // 4: Self closing tag... . '\\]' // ...and closing bracket. . '|' . '\\]' // Closing bracket. . '(?:' . '(' // 5: Unroll the loop: Optionally, anything between the opening and closing shortcode tags. . '[^\\[]*+' // Not an opening bracket. . '(?:' . '\\[(?!\\/\\2\\])' // An opening bracket not followed by the closing shortcode tag. . '[^\\[]*+' // Not an opening bracket. . ')*+' . ')' . '\\[\\/\\2\\]' // Closing shortcode tag. . ')?' . ')' . '(\\]?)'; // 6: Optional second closing bracket for escaping shortcodes: [[tag]]. // phpcs:enable } /** * Regular Expression callable for do_shortcode() for calling shortcode hook. * * @see get_shortcode_regex() for details of the match array contents. * * @since 2.5.0 * @access private * * @global array $shortcode_tags * * @param array $m { * Regular expression match array. * * @type string $0 Entire matched shortcode text. * @type string $1 Optional second opening bracket for escaping shortcodes. * @type string $2 Shortcode name. * @type string $3 Shortcode arguments list. * @type string $4 Optional self closing slash. * @type string $5 Content of a shortcode when it wraps some content. * @type string $6 Optional second closing bracket for escaping shortcodes. * } * @return string Shortcode output. */ function do_shortcode_tag( $m ) { global $shortcode_tags; // Allow [[foo]] syntax for escaping a tag. if ( '[' === $m[1] && ']' === $m[6] ) { return substr( $m[0], 1, -1 ); } $tag = $m[2]; $attr = shortcode_parse_atts( $m[3] ); if ( ! is_callable( $shortcode_tags[ $tag ] ) ) { _doing_it_wrong( __FUNCTION__, /* translators: %s: Shortcode tag. */ sprintf( __( 'Attempting to parse a shortcode without a valid callback: %s' ), $tag ), '4.3.0' ); return $m[0]; } /** * Filters whether to call a shortcode callback. * * Returning a non-false value from filter will short-circuit the * shortcode generation process, returning that value instead. * * @since 4.7.0 * @since 6.5.0 The `$attr` parameter is always an array. * * @param false|string $output Short-circuit return value. Either false or the value to replace the shortcode with. * @param string $tag Shortcode name. * @param array $attr Shortcode attributes array, can be empty if the original arguments string cannot be parsed. * @param array $m Regular expression match array. */ $return = apply_filters( 'pre_do_shortcode_tag', false, $tag, $attr, $m ); if ( false !== $return ) { return $return; } $content = isset( $m[5] ) ? $m[5] : null; $output = $m[1] . call_user_func( $shortcode_tags[ $tag ], $attr, $content, $tag ) . $m[6]; /** * Filters the output created by a shortcode callback. * * @since 4.7.0 * @since 6.5.0 The `$attr` parameter is always an array. * * @param string $output Shortcode output. * @param string $tag Shortcode name. * @param array $attr Shortcode attributes array, can be empty if the original arguments string cannot be parsed. * @param array $m Regular expression match array. */ return apply_filters( 'do_shortcode_tag', $output, $tag, $attr, $m ); } /** * Searches only inside HTML elements for shortcodes and process them. * * Any [ or ] characters remaining inside elements will be HTML encoded * to prevent interference with shortcodes that are outside the elements. * Assumes $content processed by KSES already. Users with unfiltered_html * capability may get unexpected output if angle braces are nested in tags. * * @since 4.2.3 * * @param string $content Content to search for shortcodes. * @param bool $ignore_html When true, all square braces inside elements will be encoded. * @param array $tagnames List of shortcodes to find. * @return string Content with shortcodes filtered out. */ function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) { // Normalize entities in unfiltered HTML before adding placeholders. $trans = array( '[' => '[', ']' => ']', ); $content = strtr( $content, $trans ); $trans = array( '[' => '[', ']' => ']', ); $pattern = get_shortcode_regex( $tagnames ); $textarr = wp_html_split( $content ); foreach ( $textarr as &$element ) { if ( '' === $element || '<' !== $element[0] ) { continue; } $noopen = ! str_contains( $element, '[' ); $noclose = ! str_contains( $element, ']' ); if ( $noopen || $noclose ) { // This element does not contain shortcodes. if ( $noopen xor $noclose ) { // Need to encode stray '[' or ']' chars. $element = strtr( $element, $trans ); } continue; } if ( $ignore_html || str_starts_with( $element, '