PluginProbe
ElasticPress / 4.4.1
ElasticPress v4.4.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 / Term / Term.php

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

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