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

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