PluginProbe
ElasticPress / 4.7.0
ElasticPress v4.7.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.7.0, at includes/classes/Indexable/Term/Term.php

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