Source: includes/classes/Indexable/User/QueryIntegration.php

<?php
/**
 * Integrate with WP_User_Query
 *
 * @since  1.0
 * @package elasticpress
 */

namespace ElasticPress\Indexable\User;

use ElasticPress\Indexables as Indexables;
use \WP_User_Query as WP_User_Query;
use ElasticPress\Utils as Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Query integration class
 */
class QueryIntegration {

	/**
	 * Checks to see if we should be integrating and if so, sets up the appropriate actions and filters.
	 *
	 * @param string $indexable_slug Indexable slug. Optional.
	 *
	 * @since 0.9
	 * @since 3.6.0 Added $indexable_slug
	 */
	public function __construct( $indexable_slug = 'user' ) {
		/**
		 * Filter whether to enable query integration during indexing
		 *
		 * @since 4.5.2
		 * @hook ep_enable_query_integration_during_indexing
		 *
		 * @param {bool} $enable To allow query integration during indexing
		 * @param {string} $indexable_slug Indexable slug
		 * @return {bool} New value
		 */
		$allow_query_integration_during_indexing = apply_filters( 'ep_enable_query_integration_during_indexing', false, $indexable_slug );

		// Ensure that we are currently allowing ElasticPress to override the normal WP_Query
		// Indexable->is_full_reindexing() is not available at this point yet, so using the IndexHelper version of it.
		if ( \ElasticPress\IndexHelper::factory()->is_full_reindexing( $indexable_slug ) && ! $allow_query_integration_during_indexing ) {
			return;
		}

		add_filter( 'users_pre_query', [ $this, 'maybe_filter_query' ], 10, 2 );

		// Add header
		add_action( 'pre_get_users', array( $this, 'action_pre_get_users' ), 5 );
	}

	/**
	 * If WP_User_Query meets certain conditions, query results from ES
	 *
	 * @param  array         $results Users array.
	 * @param  WP_User_Query $query   Current query.
	 * @since  3.0
	 * @return array
	 */
	public function maybe_filter_query( $results, WP_User_Query $query ) {
		$user_indexable = Indexables::factory()->get( 'user' );

		/**
		 * Filter to skip user query integration
		 *
		 * @hook ep_skip_user_query_integration
		 * @param {bool} $skip True meanas skip query
		 * @param  {WP_User_Query} $query User query
		 * @since  3.0
		 * @return {boolean} New value
		 */
		if ( ! $user_indexable->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_user_query_integration', false, $query ) ) {
			return $results;
		}

		/**
		 * Filter cached user query users
		 *
		 * @hook ep_wp_query_search_cached_users
		 * @param {array} $users Array of users
		 * @param  {WP_User_Query} $query User query
		 * @since  3.0
		 * @return {array} New users
		 */
		$new_users = apply_filters( 'ep_wp_query_search_cached_users', null, $query );

		if ( null === $new_users ) {
			$formatted_args = $user_indexable->format_args( $query->query_vars, $query );

			$ep_query = $user_indexable->query_es( $formatted_args, $query->query_vars, null, $query );

			if ( false === $ep_query ) {
				return $results;
			}

			/**
			 * WP_User_Query does not let us set this property:
			 *
			 * $query->elasticsearch_success = true;
			 */
			$query->query_vars['elasticsearch_success'] = true;

			$fields    = $query->get( 'fields' );
			$new_users = [];

			if ( in_array( $fields, [ 'all', 'all_with_meta' ], true ) ) {
				foreach ( $ep_query['documents'] as $document ) {
					$new_users[] = $document['ID'];
				}
			} elseif ( is_array( $fields ) ) {
				// WP_User_Query returns a stdClass.
				foreach ( $ep_query['documents'] as $document ) {

					$user                = new \stdClass();
					$user->elasticsearch = true; // Super useful for debugging.

					foreach ( $fields as $field ) {
						if ( 'id' === $field ) {
							$field = 'ID';
						}
						$user->$field = $document[ $field ];
					}

					$new_users[] = $user;
				}
			} elseif ( is_string( $fields ) && ! empty( $fields ) ) {
				foreach ( $ep_query['documents'] as $document ) {
					$new_users[] = $document[ $fields ];
				}
			} else {
				$new_users = $this->format_hits_as_users( $ep_query['documents'] );
			}
		}

		$query->total_users = is_array( $ep_query['found_documents'] ) ? $ep_query['found_documents']['value'] : $ep_query['found_documents']; // 7.0+ have this as an array rather than int;

		return $new_users;
	}

	/**
	 * Format the ES hits/results as WP_User objects.
	 *
	 * @param array $users The users that should be formatted.
	 * @since  3.0
	 * @return array
	 */
	protected function format_hits_as_users( $users ) {
		$new_users = [];

		foreach ( $users as $user_array ) {
			$user = new \stdClass();

			/**
			 * Filter arguments inserted into user object after search
			 *
			 * @hook ep_search_user_return_args
			 * @param {array} $args Array of arguments
			 * @since  3.0
			 * @return {array} New arguments
			 */
			$user_return_args = apply_filters(
				'ep_search_user_return_args',
				[
					'ID',
					'user_login',
					'user_nicename',
					'user_email',
					'user_url',
					'user_registered',
					'user_status',
					'display_name',
					'spam',
					'deleted',
					'terms',
					'meta',
				]
			);

			foreach ( $user_return_args as $key ) {
				if ( isset( $user_array[ $key ] ) ) {
					$user->$key = $user_array[ $key ];
				}
			}

			$user->elasticsearch = true; // Super useful for debugging.

			$new_users[] = $user;
		}

		return $new_users;
	}

	/**
	 * Disables cache_results, adds header.
	 *
	 * @param WP_User_Query $query User query
	 * @since 3.0
	 */
	public function action_pre_get_users( $query ) {
		/**
		 * Filter to skip user query integration
		 *
		 * @hook ep_skip_user_query_integration
		 * @param {bool} $skip True meanas skip query
		 * @param  {WP_User_Query} $query User query
		 * @since  3.0
		 * @return {boolean} New value
		 */
		if ( ! Indexables::factory()->get( 'user' )->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_user_query_integration', false, $query ) ) {
			return;
		}

		if ( ! headers_sent() ) {
			/**
			 * Manually setting a header as $wp_query isn't yet initialized
			 * when we call: add_filter('wp_headers', 'filter_wp_headers');
			 */
			header( 'X-ElasticPress-Search: true' );
		}
	}
}