PluginProbe
ElasticPress / 5.0.2
ElasticPress v5.0.2
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 5.0.2, at includes/classes/Indexable/Term/Term.php

948 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 \WP_Term_Query;
12 use ElasticPress\Elasticsearch;
13 use ElasticPress\Indexable;
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 = '7-0.php';
543
544 if ( version_compare( $es_version, '7.0', '<' ) ) {
545 $mapping_file = 'initial.php';
546 }
547
548 /**
549 * Filter mapping file for Terms indexable
550 *
551 * @hook ep_term_mapping_file
552 * @param {string} $file File name
553 * @since 3.4
554 * @return {string} New file name
555 */
556 $mapping = require apply_filters( 'ep_term_mapping_file', __DIR__ . '/../../../mappings/term/' . $mapping_file );
557
558 /**
559 * Filter full Elasticsearch query for Terms indexable
560 *
561 * @hook ep_term_mapping
562 * @param {array} $mapping Elasticsearch mapping
563 * @since 3.4
564 * @return {array} New mapping
565 */
566 $mapping = apply_filters( 'ep_term_mapping', $mapping );
567
568 return $mapping;
569 }
570
571 /**
572 * Prepare a term document for indexing
573 *
574 * @param int $term_id Term ID
575 * @since 3.1
576 * @return bool|array
577 */
578 public function prepare_document( $term_id ) {
579 $term = get_term( $term_id );
580
581 if ( ! $term || ! is_a( $term, 'WP_Term' ) ) {
582 return false;
583 }
584
585 $term_args = [
586 'term_id' => $term->term_id,
587 'ID' => $term->term_id,
588 'name' => $term->name,
589 'slug' => $term->slug,
590 'term_group' => $term->group,
591 'term_taxonomy_id' => $term->term_taxonomy_id,
592 'taxonomy' => $term->taxonomy,
593 'description' => $term->description,
594 'parent' => $term->parent,
595 'count' => $term->count,
596 'meta' => $this->prepare_meta_types( $this->prepare_meta( $term->term_id ) ),
597 'hierarchy' => $this->prepare_term_hierarchy( $term->term_id, $term->taxonomy ),
598 'object_ids' => $this->prepare_object_ids( $term->term_id, $term->taxonomy ),
599 ];
600
601 /**
602 * Filter term fields pre-sync
603 *
604 * @hook ep_term_sync_args
605 * @param {array} $term_args Current term fields
606 * @param {int} $term_id Term ID
607 * @since 3.4
608 * @return {array} New fields
609 */
610 $term_args = apply_filters( 'ep_term_sync_args', $term_args, $term_id );
611
612 return $term_args;
613 }
614
615 /**
616 * Query DB for terms
617 *
618 * @param array $args Query arguments
619 * @since 3.1
620 * @return array
621 */
622 public function query_db( $args ) {
623 $defaults = [
624 'number' => $this->get_bulk_items_per_page(),
625 'offset' => 0,
626 'orderby' => 'id',
627 'order' => 'desc',
628 'taxonomy' => $this->get_indexable_taxonomies(),
629 'hide_empty' => false,
630 'hierarchical' => false,
631 'update_term_meta_cache' => false,
632 'cache_results' => 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 * Filter database arguments for term count query
657 *
658 * @hook ep_term_all_query_db_args
659 * @param {array} $args Query arguments based to `wp_count_terms()`
660 * @since 3.4
661 * @return {array} New arguments
662 */
663 $total_objects = wp_count_terms( apply_filters( 'ep_term_all_query_db_args', $all_query_args, $args ) );
664 $total_objects = ! is_wp_error( $total_objects ) ? (int) $total_objects : 0;
665
666 if ( ! empty( $args['offset'] ) ) {
667 if ( (int) $args['offset'] >= $total_objects ) {
668 $total_objects = 0;
669 }
670 }
671
672 $query = new WP_Term_Query( $args );
673
674 if ( is_array( $query->terms ) ) {
675 array_walk( $query->terms, array( $this, 'remap_terms' ) );
676 }
677
678 return [
679 'objects' => $query->terms,
680 'total_objects' => $total_objects,
681 ];
682 }
683
684 /**
685 * Returns indexable taxonomies for the current site
686 *
687 * @since 3.1
688 * @return mixed|void
689 */
690 public function get_indexable_taxonomies() {
691 $taxonomies = get_taxonomies( [], 'objects' );
692 $public_taxonomies = [];
693
694 foreach ( $taxonomies as $taxonomy ) {
695 if ( $taxonomy->public || $taxonomy->publicly_queryable ) {
696 $public_taxonomies[] = $taxonomy->name;
697 }
698 }
699
700 /**
701 * Filter indexable taxonomies for Terms indexable
702 *
703 * @hook ep_indexable_taxonomies
704 * @param {array} $public_taxonomies Taxonomies
705 * @since 3.4
706 * @return {array} New taxonomies array
707 */
708 return apply_filters( 'ep_indexable_taxonomies', $public_taxonomies );
709 }
710
711 /**
712 * Rebuild our term object to match the fields we need.
713 *
714 * In particular, result of WP_Term_Query does not
715 * include an "id" field, which our index command
716 * expects.
717 *
718 * @param object $value Term object
719 * @since 3.1
720 * @return void Returns by reference
721 */
722 public function remap_terms( &$value ) {
723 $value = (object) array(
724 'ID' => $value->term_id,
725 'term_id' => $value->term_id,
726 'name' => $value->name,
727 'slug' => $value->slug,
728 'term_group' => $value->term_group,
729 'term_taxonomy_id' => $value->term_taxonomy_id,
730 'taxonomy' => $value->taxonomy,
731 'description' => $value->description,
732 'parent' => $value->parent,
733 'count' => $value->count,
734 );
735 }
736
737 /**
738 * Prepare meta to send to ES
739 *
740 * @param int $term_id Term ID
741 * @since 3.1
742 * @return array
743 */
744 public function prepare_meta( $term_id ) {
745 $meta = (array) get_term_meta( $term_id );
746
747 if ( empty( $meta ) ) {
748 return [];
749 }
750
751 $prepared_meta = [];
752
753 /**
754 * Filter index-able private meta
755 *
756 * Allows for specifying private meta keys that may be indexed in the same manner as public meta keys.
757 *
758 * @since 3.4
759 * @hook ep_prepare_term_meta_allowed_protected_keys
760 * @param {array} $allowed_protected_keys Array of index-able private meta keys.
761 * @param {int} $term_id Term ID.
762 * @return {array} New meta keys
763 */
764 $allowed_protected_keys = apply_filters( 'ep_prepare_term_meta_allowed_protected_keys', [], $term_id );
765
766 /**
767 * Filter non-indexed public meta
768 *
769 * Allows for specifying public meta keys that should be excluded from the ElasticPress index.
770 *
771 * @since 3.4
772 * @hook ep_prepare_term_meta_excluded_public_keys
773 * @param {array} $public_keys Array of public meta keys to exclude from index.
774 * @param {int} $term_id Term ID.
775 * @return {array} New keys
776 */
777 $excluded_public_keys = apply_filters(
778 'ep_prepare_term_meta_excluded_public_keys',
779 [
780 'session_tokens',
781 ],
782 $term_id
783 );
784
785 foreach ( $meta as $key => $value ) {
786
787 $allow_index = false;
788
789 if ( is_protected_meta( $key ) ) {
790
791 if ( true === $allowed_protected_keys || in_array( $key, $allowed_protected_keys, true ) ) {
792 $allow_index = true;
793 }
794 } else {
795
796 if ( true !== $excluded_public_keys && ! in_array( $key, $excluded_public_keys, true ) ) {
797 $allow_index = true;
798 }
799 }
800
801 /**
802 * Filter kill switch for any term meta
803 *
804 * @since 3.4
805 * @hook ep_prepare_term_meta_whitelist_key
806 * @param {boolean} $index_key Whether to index key or not
807 * @param {string} $key Key name
808 * @param {int} $term_id Term ID.
809 * @return {boolean} New index value
810 */
811 if ( true === $allow_index || apply_filters( 'ep_prepare_term_meta_whitelist_key', false, $key, $term_id ) ) {
812 $prepared_meta[ $key ] = maybe_unserialize( $value );
813 }
814 }
815
816 return $prepared_meta;
817 }
818
819 /**
820 * Prepare term hierarchy to send to ES
821 *
822 * @param int $term_id Term ID.
823 * @param string $taxonomy Term taxonomy.
824 * @since 3.1
825 * @return array
826 */
827 public function prepare_term_hierarchy( $term_id, $taxonomy ) {
828 $hierarchy = [];
829 $children = get_term_children( $term_id, $taxonomy );
830 $ancestors = get_ancestors( $term_id, $taxonomy, 'taxonomy' );
831
832 if ( ! empty( $children ) && ! is_wp_error( $children ) ) {
833 $hierarchy['children']['terms'] = $children;
834 $children_count = 0;
835
836 foreach ( $children as $child_term_id ) {
837 $child_term = get_term( $child_term_id );
838 $children_count += (int) $child_term->count;
839 }
840
841 $hierarchy['children']['count'] = $children_count;
842 } else {
843 $hierarchy['children']['terms'] = 0;
844 $hierarchy['children']['count'] = 0;
845 }
846
847 if ( ! empty( $ancestors ) ) {
848 $hierarchy['ancestors']['terms'] = $ancestors;
849 } else {
850 $hierarchy['ancestors']['terms'] = 0;
851 }
852
853 return $hierarchy;
854 }
855
856 /**
857 * Prepare object IDs to send to ES
858 *
859 * @param int $term_id Term ID.
860 * @param string $taxonomy Term taxonomy.
861 * @since 3.1
862 * @return array
863 */
864 public function prepare_object_ids( $term_id, $taxonomy ) {
865 $ids = [];
866 $object_ids = get_objects_in_term( [ $term_id ], [ $taxonomy ] );
867
868 if ( ! empty( $object_ids ) && ! is_wp_error( $object_ids ) ) {
869 $ids['value'] = array_map( 'absint', array_values( $object_ids ) );
870 } else {
871 $ids['value'] = 0;
872 }
873
874 return $ids;
875 }
876
877 /**
878 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
879 *
880 * @access protected
881 *
882 * @param string $order The 'order' query variable.
883 * @since 3.1
884 * @return string The sanitized 'order' query variable.
885 */
886 protected function parse_order( $order ) {
887 if ( ! is_string( $order ) || empty( $order ) ) {
888 return 'desc';
889 }
890
891 if ( 'ASC' === strtoupper( $order ) ) {
892 return 'asc';
893 } else {
894 return 'desc';
895 }
896 }
897
898 /**
899 * Convert the alias to a properly-prefixed sort value.
900 *
901 * @access protected
902 *
903 * @param string $orderby Alias or path for the field to order by.
904 * @param string $order Order direction
905 * @param array $args Query args
906 * @since 3.1
907 * @return array
908 */
909 protected function parse_orderby( $orderby, $order, $args ) {
910 $sort = [];
911
912 if ( empty( $orderby ) ) {
913 return $sort;
914 }
915
916 $from_to = [
917 'slug' => 'slug.raw',
918 'id' => 'term_id',
919 'description' => 'description.sortable',
920 ];
921
922 if ( in_array( $orderby, [ 'meta_value', 'meta_value_num' ], true ) ) {
923 if ( empty( $args['meta_key'] ) ) {
924 return $sort;
925 } else {
926 $from_to['meta_value'] = 'meta.' . $args['meta_key'] . '.value';
927 $from_to['meta_value_num'] = 'meta.' . $args['meta_key'] . '.long';
928 }
929 }
930
931 if ( 'name' === $orderby ) {
932 $es_version = Elasticsearch::factory()->get_elasticsearch_version();
933 $from_to['name'] = version_compare( (string) $es_version, '7.0', '<' ) ? 'name.raw' : 'name.sortable';
934 }
935
936 $orderby = $from_to[ $orderby ] ?? $orderby;
937
938 $sort[] = array(
939 $orderby => array(
940 'order' => $order,
941 ),
942 );
943
944 return $sort;
945 }
946
947 }
948