PluginProbe
ElasticPress / 4.6.1
ElasticPress v4.6.1
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Indexable / User / User.php

User.php in ElasticPress 4.6.1, at includes/classes/Indexable/User/User.php

915 lines 22.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * User indexable
4 *
5 * @since 3.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Indexable\User;
10
11 use ElasticPress\Indexable as Indexable;
12 use ElasticPress\Elasticsearch as Elasticsearch;
13 use \WP_User_Query as WP_User_Query;
14 use ElasticPress\Utils as Utils;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly.
18 }
19
20 /**
21 * User indexable class
22 */
23 class User extends Indexable {
24
25 /**
26 * We only need one user index
27 *
28 * @var boolean
29 * @since 3.0
30 */
31 public $global = true;
32
33 /**
34 * Indexable slug
35 *
36 * @var string
37 * @since 3.0
38 */
39 public $slug = 'user';
40
41 /**
42 * Create indexable and setup dependencies
43 *
44 * @since 3.0
45 */
46 public function __construct() {
47 $this->labels = [
48 'plural' => esc_html__( 'Users', 'elasticpress' ),
49 'singular' => esc_html__( 'User', 'elasticpress' ),
50 ];
51 }
52
53 /**
54 * Instantiate the indexable SyncManager and QueryIntegration, the main responsibles for the WP integration.
55 *
56 * @since 4.5.0
57 * @return void
58 */
59 public function setup() {
60 $this->sync_manager = new SyncManager( $this->slug );
61 $this->query_integration = new QueryIntegration( $this->slug );
62 }
63
64 /**
65 * Format query vars into ES query
66 *
67 * @param array $query_vars WP_User_Query args.
68 * @param WP_User_Query $query User query object
69 * @since 3.0
70 * @return array
71 */
72 public function format_args( $query_vars, $query ) {
73 global $wpdb;
74
75 /**
76 * Handle `number` query var
77 */
78 if ( ! empty( $query_vars['number'] ) ) {
79 $number = (int) $query_vars['number'];
80
81 // ES have a maximum size allowed so we have to convert "-1" to a maximum size.
82 if ( -1 === $number ) {
83 /**
84 * Set the maximum results window size.
85 *
86 * The request will return a HTTP 500 Internal Error if the size of the
87 * request is larger than the [index.max_result_window] parameter in ES.
88 * See the scroll api for a more efficient way to request large data sets.
89 *
90 * @return int The max results window size.
91 *
92 * @since 2.3.0
93 */
94
95 /**
96 * Filter max result size if set to -1
97 *
98 * @hook ep_max_results_window
99 * @param {int} $window Max result window
100 * @return {int} New window
101 */
102 $number = apply_filters( 'ep_max_results_window', 10000 );
103 }
104 } else {
105 /**
106 * Filter max result size if set to -1
107 *
108 * @hook ep_max_results_window
109 * @param {int} $window Max result window
110 * @return {int} New window
111 */
112 $number = apply_filters( 'ep_max_results_window', 10000 );
113 }
114
115 $formatted_args = [
116 'from' => 0,
117 'size' => $number,
118 ];
119
120 $filter = [
121 'bool' => [
122 'must' => [],
123 ],
124 ];
125
126 $use_filters = false;
127
128 /**
129 * Support `blog_id` query arg
130 */
131 $blog_id = false;
132 if ( isset( $query_vars['blog_id'] ) ) {
133 $blog_id = (int) $query_vars['blog_id'];
134 }
135
136 /**
137 * Support `role` query arg
138 */
139 if ( ! empty( $blog_id ) ) {
140 // If a blog id is set, we will apply at least one filter for roles.
141 $use_filters = true;
142
143 // If there are no specific roles named, make sure the user is a member of the site.
144 if ( empty( $query_vars['role'] ) && empty( $query_vars['role__in'] ) && empty( $query_vars['role__not_in'] ) ) {
145 $filter['bool']['must'][] = array(
146 'exists' => array(
147 'field' => 'capabilities.' . $blog_id . '.roles',
148 ),
149 );
150 /**
151 * EP versions prior to 4.1.0 set non-existent roles as `0`.
152 */
153 $filter['bool']['must_not'][] = array(
154 'term' => array(
155 'capabilities.' . $blog_id . '.roles' => 0,
156 ),
157 );
158 } elseif ( ! empty( $query_vars['role'] ) ) {
159 $roles = (array) $query_vars['role'];
160
161 foreach ( $roles as $role ) {
162 $filter['bool']['must'][] = array(
163 'terms' => array(
164 'capabilities.' . $blog_id . '.roles' => [
165 strtolower( $role ),
166 ],
167 ),
168 );
169 }
170 } else {
171 if ( ! empty( $query_vars['role__in'] ) ) {
172 $roles_in = (array) $query_vars['role__in'];
173
174 $roles_in = array_map( 'strtolower', $roles_in );
175
176 $filter['bool']['must'][] = array(
177 'terms' => array(
178 'capabilities.' . $blog_id . '.roles' => $roles_in,
179 ),
180 );
181 }
182
183 if ( ! empty( $query_vars['role__not_in'] ) ) {
184 $roles_not_in = (array) $query_vars['role__not_in'];
185
186 foreach ( $roles_not_in as $role ) {
187 $filter['bool']['must_not'][] = array(
188 'terms' => array(
189 'capabilities.' . $blog_id . '.roles' => [
190 strtolower( $role ),
191 ],
192 ),
193 );
194 }
195 }
196 }
197 }
198
199 $meta_queries = [];
200
201 /**
202 * Support `meta_key`, `meta_value`, and `meta_compare`
203 */
204 if ( ! empty( $query_vars['meta_key'] ) ) {
205 $meta_query_array = [
206 'key' => $query_vars['meta_key'],
207 ];
208
209 if ( isset( $query_vars['meta_value'] ) ) {
210 $meta_query_array['value'] = $query_vars['meta_value'];
211 }
212
213 if ( isset( $query_vars['meta_compare'] ) ) {
214 $meta_query_array['compare'] = $query_vars['meta_compare'];
215 }
216
217 $meta_queries[] = $meta_query_array;
218 }
219
220 /**
221 * 'meta_query' arg support.
222 */
223 if ( ! empty( $query_vars['meta_query'] ) ) {
224 $meta_queries = array_merge( $meta_queries, $query_vars['meta_query'] );
225 }
226
227 if ( ! empty( $meta_queries ) ) {
228 $filter['bool']['must'][] = $this->build_meta_query( $meta_queries );
229
230 $use_filters = true;
231 }
232
233 /**
234 * Support `fields` query var.
235 */
236 if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] && 'all_with_meta' !== $query_vars['fields'] ) {
237 $fields = (array) $query_vars['fields'];
238 $id_position = array_search( 'id', $fields, true );
239 if ( false !== $id_position ) {
240 $fields[ $id_position ] = 'ID';
241 }
242 $formatted_args['_source'] = [
243 'includes' => $fields,
244 ];
245 }
246
247 /**
248 * Support `nicename` query var
249 */
250 if ( ! empty( $query_vars['nicename'] ) ) {
251 $filter['bool']['must'][] = array(
252 'terms' => array(
253 'user_nicename' => [
254 $query_vars['nicename'],
255 ],
256 ),
257 );
258
259 $use_filters = true;
260 }
261
262 /**
263 * Support `nicename` query var
264 */
265 if ( ! empty( $query_vars['nicename__not_in'] ) ) {
266 $filter['bool']['must'][] = [
267 'bool' => [
268 'must_not' => [
269 [
270 'terms' => [
271 'user_nicename' => (array) $query_vars['nicename__not_in'],
272 ],
273 ],
274 ],
275 ],
276 ];
277
278 $use_filters = true;
279 }
280
281 /**
282 * Support `nicename__in` query var
283 */
284 if ( ! empty( $query_vars['nicename__in'] ) ) {
285 $filter['bool']['must'][] = array(
286 'terms' => array(
287 'user_nicename' => (array) $query_vars['nicename__in'],
288 ),
289 );
290
291 $use_filters = true;
292 }
293
294 /**
295 * Support `login` query var
296 */
297 if ( ! empty( $query_vars['login'] ) ) {
298 $filter['bool']['must'][] = array(
299 'terms' => array(
300 'user_login' => [
301 $query_vars['login'],
302 ],
303 ),
304 );
305
306 $use_filters = true;
307 }
308
309 /**
310 * Support `login__in` query var
311 */
312 if ( ! empty( $query_vars['login__in'] ) ) {
313 $filter['bool']['must'][] = array(
314 'terms' => array(
315 'user_login' => (array) $query_vars['login__in'],
316 ),
317 );
318
319 $use_filters = true;
320 }
321
322 /**
323 * Support `login__not_in` query var
324 */
325 if ( ! empty( $query_vars['login__not_in'] ) ) {
326 $filter['bool']['must'][] = [
327 'bool' => [
328 'must_not' => [
329 [
330 'terms' => [
331 'user_login' => (array) $query_vars['login__not_in'],
332 ],
333 ],
334 ],
335 ],
336 ];
337
338 $use_filters = true;
339 }
340
341 /**
342 * Handle `offset` and `paged` query vars. Paged takes priority if both are set.
343 */
344 if ( isset( $query_vars['offset'] ) ) {
345 $formatted_args['from'] = (int) $query_vars['offset'];
346 }
347
348 if ( isset( $query_vars['paged'] ) && $query_vars['paged'] > 1 ) {
349 $formatted_args['from'] = $number * ( $query_vars['paged'] - 1 );
350 }
351
352 /**
353 * Support `include` parameter
354 */
355 if ( ! empty( $query_vars['include'] ) ) {
356 $filter['bool']['must'][] = [
357 'bool' => [
358 'must' => [
359 'terms' => [
360 'ID' => array_values( (array) $query_vars['include'] ),
361 ],
362 ],
363 ],
364 ];
365
366 $use_filters = true;
367 }
368
369 /**
370 * Support `exclude` parameter
371 */
372 if ( ! empty( $query_vars['exclude'] ) ) {
373 $filter['bool']['must'][] = [
374 'bool' => [
375 'must_not' => [
376 'terms' => [
377 'ID' => array_values( (array) $query_vars['exclude'] ),
378 ],
379 ],
380 ],
381 ];
382
383 $use_filters = true;
384 }
385
386 /**
387 * Need to support a few more params
388 *
389 * @todo Support the following parameters:
390 *
391 * $who
392 * $has_published_posts
393 */
394
395 /**
396 * Handle `search` query_var
397 */
398 if ( ! empty( $query_vars['search'] ) ) {
399
400 /**
401 * Remove *'s from beginning and end of user search string'
402 *
403 * @hook ep_user_search_remove_wildcards
404 * @param {boolean} $remove True to remove
405 * @param {array} $query Current query
406 * @param {array} $query_vars Query variables
407 * @since 3.4
408 * @return {boolean}
409 */
410 if ( apply_filters( 'ep_user_search_remove_wildcards', true, $query, $query_vars ) ) {
411 $query_vars['search'] = trim( $query_vars['search'], '*' );
412 }
413
414 $search_fields = ( ! empty( $query_vars['search_columns'] ) ) ? $query_vars['search_columns'] : [];
415
416 if ( ! empty( $query_vars['search_fields'] ) ) {
417 $search_fields = array_merge( $search_fields, $query_vars['search_fields'] );
418 }
419
420 /**
421 * Handle `search_fields` query var and `search_columns`. search_columns is a bit too
422 * simplistic for our needs since we want to be able to search meta too. We just merge
423 * search columns into search_fields. search_fields overwrites search_columns.
424 */
425 if ( ! empty( $search_fields ) ) {
426 $prepared_search_fields = [];
427
428 // WP_User_Query uses shortened column names so we need to expand those.
429 if ( ! empty( $search_fields['login'] ) ) {
430 $prepared_search_fields['user_login'] = $search_fields['login'];
431
432 unset( $search_fields['login'] );
433 }
434
435 if ( ! empty( $search_fields['url'] ) ) {
436 $prepared_search_fields['user_url'] = $search_fields['url'];
437
438 unset( $search_fields['url'] );
439 }
440
441 if ( ! empty( $search_fields['nicename'] ) ) {
442 $prepared_search_fields['user_nicename'] = $search_fields['nicename'];
443
444 unset( $search_fields['nicename'] );
445 }
446
447 if ( ! empty( $search_fields['email'] ) ) {
448 $prepared_search_fields['user_email'] = $search_fields['email'];
449
450 unset( $search_fields['email'] );
451 }
452
453 if ( ! empty( $search_fields['meta'] ) ) {
454 $metas = (array) $search_fields['meta'];
455
456 foreach ( $metas as $meta ) {
457 $prepared_search_fields[] = 'meta.' . $meta . '.value';
458 }
459
460 unset( $search_fields['meta'] );
461 }
462
463 $prepared_search_fields = array_merge( $search_fields, $prepared_search_fields );
464 } else {
465 $prepared_search_fields = [
466 'user_login',
467 'user_nicename',
468 'display_name',
469 'user_url',
470 'user_email',
471 'meta.first_name',
472 'meta.last_name',
473 'meta.nickname',
474 ];
475 }
476
477 /**
478 * Filter search fields in user query
479 *
480 * @hook ep_user_search_fields
481 * @param {array} $prepared_search_fields Prepared search fields
482 * @param {array} $query_vars Query variables
483 * @since 3.0
484 * @return {array} Search fields
485 */
486 $prepared_search_fields = apply_filters( 'ep_user_search_fields', $prepared_search_fields, $query_vars );
487
488 $search_algorithm = $this->get_search_algorithm( $query_vars['search'], $prepared_search_fields, $query_vars );
489 $formatted_args['query'] = $search_algorithm->get_query( 'user', $query_vars['search'], $prepared_search_fields, $query_vars );
490 } else {
491 $formatted_args['query']['match_all'] = [
492 'boost' => 1,
493 ];
494 }
495
496 if ( $use_filters ) {
497 $formatted_args['post_filter'] = $filter;
498 }
499
500 /**
501 * Handle order and orderby
502 */
503 if ( ! empty( $query_vars['order'] ) ) {
504 $order = trim( strtolower( $query_vars['order'] ) );
505 } else {
506 $order = 'desc';
507 }
508
509 if ( empty( $query_vars['orderby'] ) && ( ! isset( $query_vars['search'] ) || '' === $query_vars['search'] ) ) {
510 $query_vars['orderby'] = 'user_login';
511 }
512
513 // Set sort type.
514 if ( ! empty( $query_vars['orderby'] ) ) {
515 $formatted_args['sort'] = $this->parse_orderby( $query_vars['orderby'], $order, $query_vars );
516 } else {
517 // Default sort is to use the score (based on relevance).
518 $formatted_args['sort'] = array(
519 array(
520 '_score' => array(
521 'order' => $order,
522 ),
523 ),
524 );
525 }
526
527 /**
528 * Filter formatted Elasticsearch user query (entire query)
529 *
530 * @hook ep_user_formatted_args_query
531 * @param {array} $formatted_args Formatted Elasticsearch query
532 * @param {array} $query_vars Query variables
533 * @param {array} $query Query part
534 * @since 3.0
535 * @return {array} New query
536 */
537 return apply_filters( 'ep_user_formatted_args', $formatted_args, $query_vars, $query );
538 }
539
540 /**
541 * Convert the alias to a properly-prefixed sort value.
542 *
543 * @since 3.0
544 * @param string $orderby Orderby query var
545 * @param string $default_order Order direction
546 * @param array $query_vars Query vars
547 * @return array
548 */
549 public function parse_orderby( $orderby, $default_order, $query_vars ) {
550 /**
551 * More params to support
552 *
553 * @todo Need to support:
554 *
555 * include
556 * login__in
557 * nicename__in
558 * post_count
559 */
560
561 if ( ! is_array( $orderby ) ) {
562 $orderby = explode( ' ', $orderby );
563 }
564
565 $from_to = [
566 'relevance' => '_score',
567 'user_login' => 'user_login.raw',
568 'login' => 'user_login.raw',
569 'id' => 'ID',
570 'display_name' => 'display_name.sortable',
571 'name' => 'display_name.sortable',
572 'nicename' => 'user_nicename.raw',
573 'user_nicename' => 'user_nicename.raw',
574 'user_email' => 'user_email.raw',
575 'email' => 'user_email.raw',
576 'user_url' => 'user_url.raw',
577 'url' => 'user_url.raw',
578 'registered' => 'user_registered',
579 ];
580
581 $sort = [];
582
583 if ( empty( $orderby ) ) {
584 return $sort;
585 }
586
587 $unsupported_clauses = [ 'rand', 'include', 'login__in', 'nicename__in', 'post_count' ];
588
589 foreach ( $orderby as $key => $value ) {
590 if ( is_string( $key ) ) {
591 $orderby_clause = $key;
592 $order = $value;
593 } else {
594 $orderby_clause = $value;
595 $order = $default_order;
596 }
597
598 if ( empty( $orderby_clause ) || in_array( $orderby_clause, $unsupported_clauses, true ) ) {
599 continue;
600 }
601
602 if ( in_array( $orderby_clause, [ 'meta_value', 'meta_value_num' ], true ) ) {
603 if ( empty( $args['meta_key'] ) ) {
604 continue;
605 } else {
606 $from_to['meta_value'] = 'meta.' . $args['meta_key'] . '.raw';
607 $from_to['meta_value_num'] = 'meta.' . $args['meta_key'] . '.long';
608 }
609 }
610
611 $orderby_clause = $from_to[ $orderby_clause ] ?? $orderby_clause;
612
613 $sort[] = array(
614 $orderby_clause => array(
615 'order' => $order,
616 ),
617 );
618 }
619
620 return $sort;
621 }
622
623 /**
624 * Query DB for users
625 *
626 * @param array $args Query arguments
627 * @since 3.0
628 * @return array
629 */
630 public function query_db( $args ) {
631 global $wpdb;
632
633 $defaults = [
634 'number' => 350,
635 'offset' => 0,
636 'orderby' => 'ID',
637 'order' => 'desc',
638 ];
639
640 if ( isset( $args['per_page'] ) ) {
641 $args['number'] = $args['per_page'];
642 }
643
644 /**
645 * Filter query database arguments for user indexable
646 *
647 * @hook ep_user_query_db_args
648 * @param {array} $args Database query arguments
649 * @since 3.0
650 * @return {array} New arguments
651 */
652 $args = apply_filters( 'ep_user_query_db_args', wp_parse_args( $args, $defaults ) );
653
654 $args['order'] = trim( strtolower( $args['order'] ) );
655
656 if ( ! in_array( $args['order'], [ 'asc', 'desc' ], true ) ) {
657 $args['order'] = 'desc';
658 }
659
660 $orderby_args = sanitize_sql_orderby( "{$args['orderby']} {$args['order']}" );
661 $orderby = $orderby_args ? sprintf( 'ORDER BY %s', $orderby_args ) : '';
662
663 /**
664 * WP_User_Query doesn't let us get users across all blogs easily. This is the best
665 * way to do that.
666 */
667 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery
668 $objects = $wpdb->get_results(
669 $wpdb->prepare(
670 "SELECT SQL_CALC_FOUND_ROWS ID FROM {$wpdb->users} {$orderby} LIMIT %d, %d",
671 (int) $args['offset'],
672 (int) $args['number']
673 )
674 );
675
676 return [
677 'objects' => $objects,
678 'total_objects' => ( 0 === count( $objects ) ) ? 0 : (int) $wpdb->get_var( 'SELECT FOUND_ROWS()' ),
679 ];
680 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery
681 }
682
683 /**
684 * Generate the mapping array
685 *
686 * @since 3.6.0
687 * @return array
688 */
689 public function generate_mapping() {
690 $es_version = Elasticsearch::factory()->get_elasticsearch_version();
691 if ( empty( $es_version ) ) {
692 /**
693 * Filter fallback Elasticsearch version
694 *
695 * @hook ep_fallback_elasticsearch_version
696 * @param {string} $version Fall back Elasticsearch version
697 * @return {string} New version
698 */
699 $es_version = apply_filters( 'ep_fallback_elasticsearch_version', '2.0' );
700 }
701
702 $mapping_file = 'initial.php';
703
704 if ( version_compare( $es_version, '5.0', '<' ) ) {
705 $mapping_file = 'pre-5-0.php';
706 } elseif ( version_compare( $es_version, '7.0', '>=' ) ) {
707 $mapping_file = '7-0.php';
708 }
709
710 /**
711 * Filter user indexable mapping file
712 *
713 * @hook ep_user_mapping_file
714 * @param {string} $file Path to file
715 * @since 3.0
716 * @return {string} New file path
717 */
718 $mapping = require apply_filters( 'ep_user_mapping_file', __DIR__ . '/../../../mappings/user/' . $mapping_file );
719
720 /**
721 * Filter user indexable mapping
722 *
723 * @hook ep_user_mapping
724 * @param {array} $mapping Mapping
725 * @since 3.0
726 * @return {array} New mapping
727 */
728 $mapping = apply_filters( 'ep_user_mapping', $mapping );
729
730 return $mapping;
731 }
732
733 /**
734 * Prepare a user document for indexing
735 *
736 * @param int $user_id User id
737 * @since 3.0
738 * @return array
739 */
740 public function prepare_document( $user_id ) {
741 $user = get_user_by( 'ID', $user_id );
742
743 if ( empty( $user ) ) {
744 return false;
745 }
746
747 $user_args = [
748 'ID' => $user_id,
749 'user_login' => $user->user_login,
750 'user_email' => $user->user_email,
751 'user_nicename' => $user->user_nicename,
752 'spam' => $user->spam,
753 'deleted' => $user->spam,
754 'user_status' => $user->user_status,
755 'display_name' => $user->display_name,
756 'user_registered' => $user->user_registered,
757 'user_url' => $user->user_url,
758 'capabilities' => $this->prepare_capabilities( $user_id ),
759 'meta' => $this->prepare_meta_types( $this->prepare_meta( $user_id ) ),
760 ];
761
762 /**
763 * Filter prepared user document before index
764 *
765 * @hook ep_user_sync_args
766 * @param {array} $user_args Document
767 * @param {int} $user_id User ID
768 * @since 3.0
769 * @return {array} New document
770 */
771 $user_args = apply_filters( 'ep_user_sync_args', $user_args, $user_id );
772
773 return $user_args;
774 }
775
776 /**
777 * Prepare capabilities for indexing
778 *
779 * @param int $user_id User ID
780 * @since 3.0
781 * @return array
782 */
783 public function prepare_capabilities( $user_id ) {
784 global $wpdb;
785
786 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
787 $sites = Utils\get_sites();
788 } else {
789 $sites = [
790 [
791 'blog_id' => (int) get_current_blog_id(),
792 ],
793 ];
794 }
795
796 $prepared_roles = [];
797
798 foreach ( $sites as $site ) {
799 $roles = (array) get_user_meta( $user_id, $wpdb->get_blog_prefix( $site['blog_id'] ) . 'capabilities', true );
800
801 if ( ! empty( $roles ) ) {
802 $prepared_roles[ (int) $site['blog_id'] ] = [
803 'roles' => array_keys( (array) $roles ),
804 ];
805 }
806 }
807
808 return $prepared_roles;
809 }
810
811 /**
812 * Prepare meta to send to ES
813 *
814 * @param int $user_id User id
815 * @since 3.0
816 * @return array
817 */
818 public function prepare_meta( $user_id ) {
819 /**
820 * Filter pre-prepare meta for a user
821 *
822 * @hook ep_prepare_user_meta_data
823 * @param {array} $meta Meta data
824 * @param {int} $user_id User ID
825 * @return {array} New meta
826 */
827 $meta = apply_filters( 'ep_prepare_user_meta_data', (array) get_user_meta( $user_id ), $user_id );
828
829 if ( empty( $meta ) ) {
830 /**
831 * Filter final list of prepared user meta.
832 *
833 * @hook ep_prepared_user_meta
834 * @param {array} $prepared_meta Prepared meta
835 * @param {integer} $user_id User ID
836 * @since 3.4
837 * @return {array} Prepared meta
838 */
839 return apply_filters( 'ep_prepared_user_meta', [], $user_id );
840 }
841
842 $prepared_meta = [];
843
844 /**
845 * Filter indexable private meta for users
846 *
847 * @hook ep_prepare_user_meta_allowed_protected_keys
848 * @param {array} $meta Meta keys
849 * @param {int} $user_id User ID
850 * @since 3.0
851 * @return {array} New meta array
852 */
853 $allowed_protected_keys = apply_filters( 'ep_prepare_user_meta_allowed_protected_keys', [], $user_id );
854
855 /**
856 * Filter out excluded indexable public meta keys for users
857 *
858 * @hook ep_prepare_user_meta_excluded_public_keys
859 * @param {array} $meta Meta keys
860 * @param {int} $user_id User ID
861 * @since 3.0
862 * @return {array} New meta array
863 */
864 $excluded_public_keys = apply_filters(
865 'ep_prepare_user_meta_excluded_public_keys',
866 [
867 'session_tokens',
868 ],
869 $user_id
870 );
871
872 foreach ( $meta as $key => $value ) {
873
874 $allow_index = false;
875
876 if ( is_protected_meta( $key ) ) {
877
878 if ( true === $allowed_protected_keys || in_array( $key, $allowed_protected_keys, true ) ) {
879 $allow_index = true;
880 }
881 } else {
882
883 if ( true !== $excluded_public_keys && ! in_array( $key, $excluded_public_keys, true ) ) {
884 $allow_index = true;
885 }
886 }
887
888 /**
889 * Filter whether to whitelist a specific user meta key
890 *
891 * @hookep_prepare_user_meta_whitelist_key
892 * @param {bool} $index True to force index
893 * @param {string} $key User meta key
894 * @param {int} $user_id User ID
895 * @since 3.0
896 * @return {bool} New index value
897 */
898 if ( true === $allow_index || apply_filters( 'ep_prepare_user_meta_whitelist_key', false, $key, $user_id ) ) {
899 $prepared_meta[ $key ] = maybe_unserialize( $value );
900 }
901 }
902
903 /**
904 * Filter final list of prepared user meta.
905 *
906 * @hook ep_prepared_user_meta
907 * @param {array} $prepared_meta Prepared meta
908 * @param {integer} $user_id User ID
909 * @since 3.4
910 * @return {array} Prepared meta
911 */
912 return apply_filters( 'ep_prepared_user_meta', $prepared_meta, $user_id );
913 }
914 }
915