PluginProbe
ElasticPress / 4.2.0
ElasticPress v4.2.0
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 / Term / Term.php

Term.php in ElasticPress 4.2.0, at includes/classes/Indexable/Term/Term.php

1,033 lines 24.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Term indexable
4 *
5 * @since 3.1
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Indexable\Term;
10
11 use ElasticPress\Indexable as Indexable;
12 use ElasticPress\Elasticsearch as Elasticsearch;
13 use \WP_Term_Query as WP_Term_Query;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 // @codeCoverageIgnoreStart
17 exit; // Exit if accessed directly.
18 // @codeCoverageIgnoreEnd
19 }
20
21 /**
22 * Term indexable class
23 */
24 class Term extends Indexable {
25
26 /**
27 * Indexable slug
28 *
29 * @var string
30 * @since 3.1
31 */
32 public $slug = 'term';
33
34 /**
35 * Create indexable and initialize dependencies
36 *
37 * @since 3.1
38 */
39 public function __construct() {
40 $this->labels = [
41 'plural' => esc_html__( 'Terms', 'elasticpress' ),
42 'singular' => esc_html__( 'Term', 'elasticpress' ),
43 ];
44
45 $this->sync_manager = new SyncManager( $this->slug );
46 $this->query_integration = new QueryIntegration( $this->slug );
47 }
48
49 /**
50 * Format query vars into ES query
51 *
52 * @param array $query_vars WP_Term_Query args.
53 * @since 3.1
54 * @return array
55 */
56 public function format_args( $query_vars ) {
57 /**
58 * Support `number` query var
59 */
60 if ( ! empty( $query_vars['number'] ) ) {
61 $number = (int) $query_vars['number'];
62 } else {
63 /**
64 * Set the maximum results window size.
65 *
66 * The request will return a HTTP 500 Internal Error if the size of the
67 * request is larger than the [index.max_result_window] parameter in ES.
68 * See the scroll api for a more efficient way to request large data sets.
69 *
70 * @return int The max results window size.
71 *
72 * @since 2.3.0
73 */
74 $number = apply_filters( 'ep_max_results_window', 10000 );
75 }
76
77 $formatted_args = [
78 'from' => 0,
79 'size' => $number,
80 ];
81
82 /**
83 * Support `offset` query var
84 */
85 if ( isset( $query_vars['offset'] ) ) {
86 $formatted_args['from'] = (int) $query_vars['offset'];
87 }
88
89 /**
90 * Support `order` and `orderby` query vars
91 */
92
93 // Set sort order, default is 'ASC'.
94 if ( ! empty( $query_vars['order'] ) ) {
95 $order = $this->parse_order( $query_vars['order'] );
96 } else {
97 $order = 'desc';
98 }
99
100 // Set orderby, default is 'name'.
101 if ( empty( $query_vars['orderby'] ) ) {
102 $query_vars['orderby'] = 'name';
103 }
104
105 // Set sort type.
106 $formatted_args['sort'] = $this->parse_orderby( $query_vars['orderby'], $order, $query_vars );
107
108 $filter = [
109 'bool' => [
110 'must' => [],
111 ],
112 ];
113
114 $use_filters = false;
115
116 /**
117 * Support `taxonomy` query var
118 */
119 $taxonomy = [];
120 if ( ! empty( $query_vars['taxonomy'] ) ) {
121 $taxonomy = (array) $query_vars['taxonomy'];
122 $terms_map_name = 'terms';
123
124 if ( count( $taxonomy ) < 2 ) {
125 $terms_map_name = 'term';
126 $taxonomy = $taxonomy[0];
127 }
128
129 $filter['bool']['must'][] = [
130 $terms_map_name => [
131 'taxonomy.raw' => $taxonomy,
132 ],
133 ];
134
135 $use_filters = true;
136 }
137
138 /**
139 * Support `object_ids` query var
140 */
141 if ( ! empty( $query_vars['object_ids'] ) ) {
142 $filter['bool']['must'][]['bool']['must'][] = [
143 'terms' => [
144 'object_ids.value' => (array) $query_vars['object_ids'],
145 ],
146 ];
147
148 $use_filters = true;
149 }
150
151 /**
152 * Support `get` query var
153 */
154 if ( ! empty( $query_vars['get'] ) && 'all' === $query_vars['get'] ) {
155 $query_vars['childless'] = false;
156 $query_vars['child_of'] = 0;
157 $query_vars['hide_empty'] = false;
158 $query_vars['hierarchical'] = false;
159 $query_vars['pad_counts'] = false;
160 }
161
162 /**
163 * Support `include` query var
164 */
165 if ( ! empty( $query_vars['include'] ) ) {
166 $filter['bool']['must'][]['bool']['must'] = [
167 'terms' => [
168 'term_id' => array_values( (array) $query_vars['include'] ),
169 ],
170 ];
171
172 $use_filters = true;
173 }
174
175 /**
176 * Support `exclude` query var
177 */
178 if ( empty( $query_vars['include'] ) && ! empty( $query_vars['exclude'] ) ) {
179 $filter['bool']['must'][]['bool']['must_not'] = [
180 'terms' => [
181 'term_id' => array_values( (array) $query_vars['exclude'] ),
182 ],
183 ];
184
185 $use_filters = true;
186 }
187
188 /**
189 * Support `exclude_tree` query var
190 */
191 if ( empty( $query_vars['include'] ) && ! empty( $query_vars['exclude_tree'] ) ) {
192 $filter['bool']['must'][]['bool']['must_not'] = [
193 'terms' => [
194 'term_id' => array_values( (array) $query_vars['exclude_tree'] ),
195 ],
196 ];
197
198 $filter['bool']['must'][]['bool']['must_not'] = [
199 'terms' => [
200 'parent' => array_values( (array) $query_vars['exclude_tree'] ),
201 ],
202 ];
203
204 $use_filters = true;
205 }
206
207 /**
208 * Support `name` query var
209 */
210 if ( ! empty( $query_vars['name'] ) ) {
211 $filter['bool']['must'][] = [
212 'terms' => [
213 'name.raw' => (array) $query_vars['name'],
214 ],
215 ];
216
217 $use_filters = true;
218 }
219
220 /**
221 * Support `slug` query var
222 */
223 if ( ! empty( $query_vars['slug'] ) ) {
224 $filter['bool']['must'][] = [
225 'terms' => [
226 'slug.raw' => (array) $query_vars['slug'],
227 ],
228 ];
229
230 $use_filters = true;
231 }
232
233 /**
234 * Support `term_taxonomy_id` query var
235 */
236 if ( ! empty( $query_vars['term_taxonomy_id'] ) ) {
237 $filter['bool']['must'][]['bool']['must'] = [
238 'terms' => [
239 'term_taxonomy_id' => array_values( (array) $query_vars['term_taxonomy_id'] ),
240 ],
241 ];
242
243 $use_filters = true;
244 }
245
246 /**
247 * Support `hierarchical` and `hide_empty` query var
248 *
249 * `hierarchical` needs to work in conjunction with `hide_empty`, as per WP docs:
250 * > `hierarchical`: Whether to include terms that have non-empty descendants (even if $hide_empty is set to true).
251 *
252 * In summary:
253 * - hide_empty AND hierarchical: count > 1 OR hierarchy.children > 1
254 * - hide_empty AND NOT hierarchical: count > 1 (ignore hierarchy.children)
255 * - NOT hide_empty (AND hierarchical): there is no need to limit the query
256 *
257 * @see https://developer.wordpress.org/reference/classes/WP_Term_Query/__construct/
258 */
259 $hide_empty = isset( $query_vars['hide_empty'] ) ? $query_vars['hide_empty'] : '';
260 if ( $hide_empty ) {
261 $hierarchical = isset( $query_vars['hierarchical'] ) ? $query_vars['hierarchical'] : '';
262
263 if ( $hierarchical ) {
264 $filter_clause = [ 'bool' => [ 'should' => [] ] ];
265
266 $filter_clause['bool']['should'][] = [
267 'range' => [
268 'count' => [
269 'gte' => 1,
270 ],
271 ],
272 ];
273
274 $filter_clause['bool']['should'][] = [
275 'range' => [
276 'hierarchy.children.count' => [
277 'gte' => 1,
278 ],
279 ],
280 ];
281
282 $filter['bool']['must'][] = $filter_clause;
283
284 $use_filters = true;
285 } else {
286 $filter['bool']['must'][] = [
287 'range' => [
288 'count' => [
289 'gte' => 1,
290 ],
291 ],
292 ];
293 }
294
295 $use_filters = true;
296 }
297
298 /**
299 * Support `search`, `name__like` and `description__like` query_vars
300 */
301 if ( ! empty( $query_vars['search'] ) || ! empty( $query_vars['name__like'] ) || ! empty( $query_vars['description__like'] ) ) {
302
303 $search = ! empty( $query_vars['search'] ) ? $query_vars['search'] : '';
304 $search_fields = [];
305
306 if ( ! empty( $query_vars['name__like'] ) ) {
307 $search = $query_vars['name__like'];
308 $search_fields[] = 'name';
309 }
310
311 if ( ! empty( $query_vars['description__like'] ) ) {
312 $search = $query_vars['description__like'];
313 $search_fields[] = 'description';
314 }
315
316 /**
317 * Allow for search field specification
318 */
319 if ( ! empty( $query_vars['search_fields'] ) ) {
320 $search_fields = $query_vars['search_fields'];
321 }
322
323 if ( ! empty( $search_fields ) ) {
324 $prepared_search_fields = [];
325
326 if ( ! empty( $search_fields['meta'] ) ) {
327 $metas = (array) $search_fields['meta'];
328
329 foreach ( $metas as $meta ) {
330 $prepared_search_fields[] = 'meta.' . $meta . '.value';
331 }
332
333 unset( $search_fields['meta'] );
334 }
335
336 $prepared_search_fields = array_merge( $search_fields, $prepared_search_fields );
337 } else {
338 $prepared_search_fields = [
339 'name',
340 'slug',
341 'taxonomy',
342 'description',
343 ];
344 }
345
346 /**
347 * Filter fields to search on Term query
348 *
349 * @hook ep_term_search_fields
350 * @param {array} $search_fields Search fields
351 * @param {array} $query_vars Query variables
352 * @since 3.4
353 * @return {array} New search fields
354 */
355 $prepared_search_fields = apply_filters( 'ep_term_search_fields', $prepared_search_fields, $query_vars );
356
357 $query = [
358 'bool' => [
359 'should' => [
360 [
361 'multi_match' => [
362 'query' => $search,
363 'type' => 'phrase',
364 'fields' => $prepared_search_fields,
365 /**
366 * Filter term match phrase boost amount
367 *
368 * @hook ep_term_match_phrase_boost
369 * @param {int} $boos Boost amount for match phrase
370 * @param {array} $query_vars Query variables
371 * @since 3.4
372 * @return {int} New boost amount
373 */
374 'boost' => apply_filters( 'ep_term_match_phrase_boost', 4, $prepared_search_fields, $query_vars ),
375 ],
376 ],
377 [
378 'multi_match' => [
379 'query' => $search,
380 'fields' => $prepared_search_fields,
381 /**
382 * Filter term match boost amount
383 *
384 * @hook ep_term_match_boost
385 * @param {int} $boost Boost amount for match
386 * @param {array} $query_vars Query variables
387 * @since 3.4
388 * @return {int} New boost amount
389 */
390 'boost' => apply_filters( 'ep_term_match_boost', 2, $prepared_search_fields, $query_vars ),
391 'fuzziness' => 0,
392 'operator' => 'and',
393 ],
394 ],
395 [
396 'multi_match' => [
397 'fields' => $prepared_search_fields,
398 'query' => $search,
399 /**
400 * Filter term fuzziness amount
401 *
402 * @hook ep_term_fuzziness_arg
403 * @param {int} $fuzziness Amount of fuziness to factor into search
404 * @param {array} $query_vars Query variables
405 * @since 3.4
406 * @return {int} New boost amount
407 */
408 'fuzziness' => apply_filters( 'ep_term_fuzziness_arg', 1, $prepared_search_fields, $query_vars ),
409 ],
410 ],
411 ],
412 ],
413 ];
414
415 /**
416 * Filter Elasticsearch query used for Terms indexable
417 *
418 * @hook ep_term_formatted_args_query
419 * @param {array} $query Elasticsearch query
420 * @param {array} $query_vars Query variables
421 * @since 3.4
422 * @return {array} New query
423 */
424 $formatted_args['query'] = apply_filters( 'ep_term_formatted_args_query', $query, $query_vars );
425
426 } else {
427 $formatted_args['query']['match_all'] = [
428 'boost' => 1,
429 ];
430 }
431
432 /**
433 * Support `child_of` query var.
434 */
435 if ( ! empty( $query_vars['child_of'] ) && ( is_string( $taxonomy ) || count( $taxonomy ) < 2 ) ) {
436 $filter['bool']['must'][]['bool']['must'][] = [
437 'match_phrase' => [
438 'hierarchy.ancestors.terms' => (int) $query_vars['child_of'],
439 ],
440 ];
441
442 $use_filters = true;
443 }
444
445 /**
446 * Support `parent` query var.
447 */
448 if ( isset( $query_vars['parent'] ) && '' !== $query_vars['parent'] ) {
449 $filter['bool']['must'][]['bool']['must'] = [
450 'term' => [
451 'parent' => (int) $query_vars['parent'],
452 ],
453 ];
454
455 $use_filters = true;
456 }
457
458 /**
459 * Support `childless` query var.
460 */
461 if ( ! empty( $query_vars['childless'] ) ) {
462 $filter['bool']['must'][]['bool']['must'] = [
463 'term' => [
464 'hierarchy.children.terms' => 0,
465 ],
466 ];
467
468 $use_filters = true;
469 }
470
471 $meta_queries = [];
472
473 /**
474 * Support `meta_key`, `meta_value`, and `meta_compare` query args
475 */
476 if ( ! empty( $query_vars['meta_key'] ) ) {
477 $meta_query_array = [
478 'key' => $query_vars['meta_key'],
479 ];
480
481 if ( isset( $query_vars['meta_value'] ) ) {
482 $meta_query_array['value'] = $query_vars['meta_value'];
483 }
484
485 if ( isset( $query_vars['meta_compare'] ) ) {
486 $meta_query_array['compare'] = $query_vars['meta_compare'];
487 }
488
489 $meta_queries[] = $meta_query_array;
490 }
491
492 /**
493 * Support 'meta_query' query var.
494 */
495 if ( ! empty( $query_vars['meta_query'] ) ) {
496 $meta_queries = array_merge( $meta_queries, $query_vars['meta_query'] );
497 }
498
499 if ( ! empty( $meta_queries ) ) {
500 $built_meta_queries = $this->build_meta_query( $meta_queries );
501
502 if ( $built_meta_queries ) {
503 $filter['bool']['must'][] = $built_meta_queries;
504 $use_filters = true;
505 }
506 }
507
508 /**
509 * Support `fields` query var.
510 */
511 if ( isset( $query_vars['fields'] ) ) {
512 switch ( $query_vars['fields'] ) {
513 case 'ids':
514 $formatted_args['_source'] = [
515 'includes' => [
516 'term_id',
517 ],
518 ];
519 break;
520
521 case 'id=>name':
522 $formatted_args['_source'] = [
523 'includes' => [
524 'term_id',
525 'name',
526 ],
527 ];
528 break;
529
530 case 'id=>parent':
531 $formatted_args['_source'] = [
532 'includes' => [
533 'term_id',
534 'parent',
535 ],
536 ];
537 break;
538
539 case 'id=>slug':
540 $formatted_args['_source'] = [
541 'includes' => [
542 'term_id',
543 'slug',
544 ],
545 ];
546 break;
547
548 case 'names':
549 $formatted_args['_source'] = [
550 'includes' => [
551 'name',
552 ],
553 ];
554 break;
555 case 'tt_ids':
556 $formatted_args['_source'] = [
557 'includes' => [
558 'term_taxonomy_id',
559 ],
560 ];
561 break;
562 }
563 }
564
565 if ( $use_filters ) {
566 $formatted_args['post_filter'] = $filter;
567 }
568
569 /**
570 * Filter full Elasticsearch query for Terms indexable
571 *
572 * @hook ep_term_formatted_args
573 * @param {array} $query Elasticsearch query
574 * @param {array} $query_vars Query variables
575 * @since 3.4
576 * @return {array} New query
577 */
578 return apply_filters( 'ep_term_formatted_args', $formatted_args, $query_vars );
579 }
580
581 /**
582 * Generate the mapping array
583 *
584 * @since 3.6.0
585 * @return array
586 */
587 public function generate_mapping() {
588 $es_version = Elasticsearch::factory()->get_elasticsearch_version();
589
590 if ( empty( $es_version ) ) {
591 $es_version = apply_filters( 'ep_fallback_elasticsearch_version', '2.0' );
592 }
593
594 $mapping_file = 'initial.php';
595
596 if ( version_compare( $es_version, '5.0', '<' ) ) {
597 $mapping_file = 'pre-5-0.php';
598 } elseif ( version_compare( $es_version, '7.0', '>=' ) ) {
599 $mapping_file = '7-0.php';
600 }
601
602 /**
603 * Filter mapping file for Terms indexable
604 *
605 * @hook ep_term_mapping_file
606 * @param {string} $file File name
607 * @since 3.4
608 * @return {string} New file name
609 */
610 $mapping = require apply_filters( 'ep_term_mapping_file', __DIR__ . '/../../../mappings/term/' . $mapping_file );
611
612 /**
613 * Filter full Elasticsearch query for Terms indexable
614 *
615 * @hook ep_term_mapping
616 * @param {array} $mapping Elasticsearch mapping
617 * @since 3.4
618 * @return {array} New mapping
619 */
620 $mapping = apply_filters( 'ep_term_mapping', $mapping );
621
622 return $mapping;
623 }
624
625 /**
626 * Prepare a term document for indexing
627 *
628 * @param int $term_id Term ID
629 * @since 3.1
630 * @return bool|array
631 */
632 public function prepare_document( $term_id ) {
633 $term = get_term( $term_id );
634
635 if ( ! $term || ! is_a( $term, 'WP_Term' ) ) {
636 return false;
637 }
638
639 $term_args = [
640 'term_id' => $term->term_id,
641 'ID' => $term->term_id,
642 'name' => $term->name,
643 'slug' => $term->slug,
644 'term_group' => $term->group,
645 'term_taxonomy_id' => $term->term_taxonomy_id,
646 'taxonomy' => $term->taxonomy,
647 'description' => $term->description,
648 'parent' => $term->parent,
649 'count' => $term->count,
650 'meta' => $this->prepare_meta_types( $this->prepare_meta( $term->term_id ) ),
651 'hierarchy' => $this->prepare_term_hierarchy( $term->term_id, $term->taxonomy ),
652 'object_ids' => $this->prepare_object_ids( $term->term_id, $term->taxonomy ),
653 ];
654
655 /**
656 * Filter term fields pre-sync
657 *
658 * @hook ep_term_sync_args
659 * @param {array} $term_args Current term fields
660 * @param {int} $term_id Term ID
661 * @since 3.4
662 * @return {array} New fields
663 */
664 $term_args = apply_filters( 'ep_term_sync_args', $term_args, $term_id );
665
666 return $term_args;
667 }
668
669 /**
670 * Query DB for terms
671 *
672 * @param array $args Query arguments
673 * @since 3.1
674 * @return array
675 */
676 public function query_db( $args ) {
677
678 $defaults = [
679 'number' => $this->get_bulk_items_per_page(),
680 'offset' => 0,
681 'orderby' => 'id',
682 'order' => 'desc',
683 'taxonomy' => $this->get_indexable_taxonomies(),
684 'hide_empty' => false,
685 ];
686
687 if ( isset( $args['per_page'] ) ) {
688 $args['number'] = $args['per_page'];
689 }
690
691 /**
692 * Filter database arguments for term query
693 *
694 * @hook ep_term_query_db_args
695 * @param {array} $args Query arguments based to WP_Term_Query
696 * @since 3.4
697 * @return {array} New arguments
698 */
699 $args = apply_filters( 'ep_term_query_db_args', wp_parse_args( $args, $defaults ) );
700
701 $all_query_args = $args;
702
703 unset( $all_query_args['number'] );
704 unset( $all_query_args['offset'] );
705 unset( $all_query_args['fields'] );
706
707 /**
708 * This just seems so inefficient.
709 *
710 * @todo Better way to do this?
711 */
712
713 /**
714 * Filter database arguments for term count query
715 *
716 * @hook ep_term_all_query_db_args
717 * @param {array} $args Query arguments based to WP_Term_Query
718 * @since 3.4
719 * @return {array} New arguments
720 */
721 $all_query = new WP_Term_Query( apply_filters( 'ep_term_all_query_db_args', $all_query_args, $args ) );
722
723 $total_objects = count( $all_query->terms );
724
725 if ( ! empty( $args['offset'] ) ) {
726 if ( (int) $args['offset'] >= $total_objects ) {
727 $total_objects = 0;
728 }
729 }
730
731 $query = new WP_Term_Query( $args );
732
733 if ( is_array( $query->terms ) ) {
734 array_walk( $query->terms, array( $this, 'remap_terms' ) );
735 }
736
737 return [
738 'objects' => $query->terms,
739 'total_objects' => $total_objects,
740 ];
741 }
742
743 /**
744 * Returns indexable taxonomies for the current site
745 *
746 * @since 3.1
747 * @return mixed|void
748 */
749 public function get_indexable_taxonomies() {
750 $taxonomies = get_taxonomies( [], 'objects' );
751 $public_taxonomies = [];
752
753 foreach ( $taxonomies as $taxonomy ) {
754 if ( $taxonomy->public || $taxonomy->publicly_queryable ) {
755 $public_taxonomies[] = $taxonomy->name;
756 }
757 }
758
759 /**
760 * Filter indexable taxonomies for Terms indexable
761 *
762 * @hook ep_indexable_taxonomies
763 * @param {array} $public_taxonomies Taxonomies
764 * @since 3.4
765 * @return {array} New taxonomies array
766 */
767 return apply_filters( 'ep_indexable_taxonomies', $public_taxonomies );
768 }
769
770 /**
771 * Rebuild our term object to match the fields we need.
772 *
773 * In particular, result of WP_Term_Query does not
774 * include an "id" field, which our index command
775 * expects.
776 *
777 * @param object $value Term object
778 * @since 3.1
779 * @return void Returns by reference
780 */
781 public function remap_terms( &$value ) {
782 $value = (object) array(
783 'ID' => $value->term_id,
784 'term_id' => $value->term_id,
785 'name' => $value->name,
786 'slug' => $value->slug,
787 'term_group' => $value->term_group,
788 'term_taxonomy_id' => $value->term_taxonomy_id,
789 'taxonomy' => $value->taxonomy,
790 'description' => $value->description,
791 'parent' => $value->parent,
792 'count' => $value->count,
793 );
794 }
795
796 /**
797 * Prepare meta to send to ES
798 *
799 * @param int $term_id Term ID
800 * @since 3.1
801 * @return array
802 */
803 public function prepare_meta( $term_id ) {
804 $meta = (array) get_term_meta( $term_id );
805
806 if ( empty( $meta ) ) {
807 return [];
808 }
809
810 $prepared_meta = [];
811
812 /**
813 * Filter index-able private meta
814 *
815 * Allows for specifying private meta keys that may be indexed in the same manner as public meta keys.
816 *
817 * @since 3.4
818 * @hook ep_prepare_term_meta_allowed_protected_keys
819 * @param {array} $allowed_protected_keys Array of index-able private meta keys.
820 * @param {int} $term_id Term ID.
821 * @return {array} New meta keys
822 */
823 $allowed_protected_keys = apply_filters( 'ep_prepare_term_meta_allowed_protected_keys', [], $term_id );
824
825 /**
826 * Filter non-indexed public meta
827 *
828 * Allows for specifying public meta keys that should be excluded from the ElasticPress index.
829 *
830 * @since 3.4
831 * @hook ep_prepare_term_meta_excluded_public_keys
832 * @param {array} $public_keys Array of public meta keys to exclude from index.
833 * @param {int} $term_id Term ID.
834 * @return {array} New keys
835 */
836 $excluded_public_keys = apply_filters(
837 'ep_prepare_term_meta_excluded_public_keys',
838 [
839 'session_tokens',
840 ],
841 $term_id
842 );
843
844 foreach ( $meta as $key => $value ) {
845
846 $allow_index = false;
847
848 if ( is_protected_meta( $key ) ) {
849
850 if ( true === $allowed_protected_keys || in_array( $key, $allowed_protected_keys, true ) ) {
851 $allow_index = true;
852 }
853 } else {
854
855 if ( true !== $excluded_public_keys && ! in_array( $key, $excluded_public_keys, true ) ) {
856 $allow_index = true;
857 }
858 }
859
860 /**
861 * Filter kill switch for any term meta
862 *
863 * @since 3.4
864 * @hook ep_prepare_term_meta_whitelist_key
865 * @param {boolean} $index_key Whether to index key or not
866 * @param {string} $key Key name
867 * @param {int} $term_id Term ID.
868 * @return {boolean} New index value
869 */
870 if ( true === $allow_index || apply_filters( 'ep_prepare_term_meta_whitelist_key', false, $key, $term_id ) ) {
871 $prepared_meta[ $key ] = maybe_unserialize( $value );
872 }
873 }
874
875 return $prepared_meta;
876 }
877
878 /**
879 * Prepare term hierarchy to send to ES
880 *
881 * @param int $term_id Term ID.
882 * @param string $taxonomy Term taxonomy.
883 * @since 3.1
884 * @return array
885 */
886 public function prepare_term_hierarchy( $term_id, $taxonomy ) {
887 $hierarchy = [];
888 $children = get_term_children( $term_id, $taxonomy );
889 $ancestors = get_ancestors( $term_id, $taxonomy, 'taxonomy' );
890
891 if ( ! empty( $children ) && ! is_wp_error( $children ) ) {
892 $hierarchy['children']['terms'] = $children;
893 $children_count = 0;
894
895 foreach ( $children as $child_term_id ) {
896 $child_term = get_term( $child_term_id );
897 $children_count += (int) $child_term->count;
898 }
899
900 $hierarchy['children']['count'] = $children_count;
901 } else {
902 $hierarchy['children']['terms'] = 0;
903 $hierarchy['children']['count'] = 0;
904 }
905
906 if ( ! empty( $ancestors ) ) {
907 $hierarchy['ancestors']['terms'] = $ancestors;
908 } else {
909 $hierarchy['ancestors']['terms'] = 0;
910 }
911
912 return $hierarchy;
913 }
914
915 /**
916 * Prepare object IDs to send to ES
917 *
918 * @param int $term_id Term ID.
919 * @param string $taxonomy Term taxonomy.
920 * @since 3.1
921 * @return array
922 */
923 public function prepare_object_ids( $term_id, $taxonomy ) {
924 $ids = [];
925 $object_ids = get_objects_in_term( [ $term_id ], [ $taxonomy ] );
926
927 if ( ! empty( $object_ids ) && ! is_wp_error( $object_ids ) ) {
928 $ids['value'] = array_map( 'absint', array_values( $object_ids ) );
929 } else {
930 $ids['value'] = 0;
931 }
932
933 return $ids;
934 }
935
936 /**
937 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
938 *
939 * @access protected
940 *
941 * @param string $order The 'order' query variable.
942 * @since 3.1
943 * @return string The sanitized 'order' query variable.
944 */
945 protected function parse_order( $order ) {
946 if ( ! is_string( $order ) || empty( $order ) ) {
947 return 'desc';
948 }
949
950 if ( 'ASC' === strtoupper( $order ) ) {
951 return 'asc';
952 } else {
953 return 'desc';
954 }
955 }
956
957 /**
958 * Convert the alias to a properly-prefixed sort value.
959 *
960 * @access protected
961 *
962 * @param string $orderby Alias or path for the field to order by.
963 * @param string $order Order direction
964 * @param array $args Query args
965 * @since 3.1
966 * @return array
967 */
968 protected function parse_orderby( $orderby, $order, $args ) {
969 $sort = [];
970
971 if ( empty( $orderby ) ) {
972 return $sort;
973 }
974
975 switch ( $orderby ) {
976 case 'name':
977 $es_version = Elasticsearch::factory()->get_elasticsearch_version();
978 $es_field_name = 'name.sortable';
979
980 if ( version_compare( $es_version, '7.0', '<' ) ) {
981 $es_field_name = 'name.raw';
982 }
983
984 break;
985
986 case 'slug':
987 $es_field_name = 'slug.raw';
988 break;
989
990 case 'term_id':
991 case 'id':
992 $es_field_name = 'term_id';
993 break;
994
995 case 'description':
996 $es_field_name = 'description.sortable';
997 break;
998
999 case 'meta_value':
1000 if ( ! empty( $args['meta_key'] ) ) {
1001 $es_field_name = 'meta.' . $args['meta_key'] . '.value';
1002 }
1003
1004 break;
1005
1006 case 'meta_value_num':
1007 if ( ! empty( $args['meta_key'] ) ) {
1008 $es_field_name = 'meta.' . $args['meta_key'] . '.long';
1009 }
1010
1011 break;
1012
1013 case 'parent':
1014 case 'count':
1015 default:
1016 $es_field_name = $orderby;
1017 break;
1018 }
1019
1020 // For `meta_value` and `meta_value_num`, for example, there is a chance this wasn't set.
1021 if ( ! empty( $es_field_name ) ) {
1022 $sort[] = array(
1023 $es_field_name => array(
1024 'order' => $order,
1025 ),
1026 );
1027 }
1028
1029 return $sort;
1030 }
1031
1032 }
1033