Make WordPress Core

source: tags/5.2/src/wp-includes/class-wp-term-query.php

Last change on this file was 43571, checked in by pento, 5 years ago

Coding Standards: Upgrade WPCS to 1.0.0

WPCS 1.0.0 includes a bunch of new auto-fixers, which drops the number of coding standards issues across WordPress significantly. Prior to running the auto-fixers, there were 15,312 issues detected. With this commit, we now drop to 4,769 issues.

This change includes three notable additions:

  • Multiline function calls must now put each parameter on a new line.
  • Auto-formatting files is now part of the grunt precommit script.
  • Auto-fixable coding standards issues will now cause Travis failures.

Fixes #44600.

File size: 33.8 KB
Line 
1<?php
2
3/**
4 * Taxonomy API: WP_Term_Query class.
5 *
6 * @package WordPress
7 * @subpackage Taxonomy
8 * @since 4.6.0
9 */
10
11/**
12 * Class used for querying terms.
13 *
14 * @since 4.6.0
15 *
16 * @see WP_Term_Query::__construct() for accepted arguments.
17 */
18class WP_Term_Query {
19
20        /**
21         * SQL string used to perform database query.
22         *
23         * @since 4.6.0
24         * @var string
25         */
26        public $request;
27
28        /**
29         * Metadata query container.
30         *
31         * @since 4.6.0
32         * @var object WP_Meta_Query
33         */
34        public $meta_query = false;
35
36        /**
37         * Metadata query clauses.
38         *
39         * @since 4.6.0
40         * @var array
41         */
42        protected $meta_query_clauses;
43
44        /**
45         * SQL query clauses.
46         *
47         * @since 4.6.0
48         * @var array
49         */
50        protected $sql_clauses = array(
51                'select'  => '',
52                'from'    => '',
53                'where'   => array(),
54                'orderby' => '',
55                'limits'  => '',
56        );
57
58        /**
59         * Query vars set by the user.
60         *
61         * @since 4.6.0
62         * @var array
63         */
64        public $query_vars;
65
66        /**
67         * Default values for query vars.
68         *
69         * @since 4.6.0
70         * @var array
71         */
72        public $query_var_defaults;
73
74        /**
75         * List of terms located by the query.
76         *
77         * @since 4.6.0
78         * @var array
79         */
80        public $terms;
81
82        /**
83         * Constructor.
84         *
85         * Sets up the term query, based on the query vars passed.
86         *
87         * @since 4.6.0
88         * @since 4.6.0 Introduced 'term_taxonomy_id' parameter.
89         * @since 4.7.0 Introduced 'object_ids' parameter.
90         * @since 4.9.0 Added 'slug__in' support for 'orderby'.
91         *
92         * @param string|array $query {
93         *     Optional. Array or query string of term query parameters. Default empty.
94         *
95         *     @type string|array $taxonomy               Taxonomy name, or array of taxonomies, to which results should
96         *                                                be limited.
97         *     @type int|array    $object_ids             Optional. Object ID, or array of object IDs. Results will be
98         *                                                limited to terms associated with these objects.
99         *     @type string       $orderby                Field(s) to order terms by. Accepts term fields ('name',
100         *                                                'slug', 'term_group', 'term_id', 'id', 'description', 'parent'),
101         *                                                'count' for term taxonomy count, 'include' to match the
102         *                                                'order' of the $include param, 'slug__in' to match the
103         *                                                'order' of the $slug param, 'meta_value', 'meta_value_num',
104         *                                                the value of `$meta_key`, the array keys of `$meta_query`, or
105         *                                                'none' to omit the ORDER BY clause. Defaults to 'name'.
106         *     @type string       $order                  Whether to order terms in ascending or descending order.
107         *                                                Accepts 'ASC' (ascending) or 'DESC' (descending).
108         *                                                Default 'ASC'.
109         *     @type bool|int     $hide_empty             Whether to hide terms not assigned to any posts. Accepts
110         *                                                1|true or 0|false. Default 1|true.
111         *     @type array|string $include                Array or comma/space-separated string of term ids to include.
112         *                                                Default empty array.
113         *     @type array|string $exclude                Array or comma/space-separated string of term ids to exclude.
114         *                                                If $include is non-empty, $exclude is ignored.
115         *                                                Default empty array.
116         *     @type array|string $exclude_tree           Array or comma/space-separated string of term ids to exclude
117         *                                                along with all of their descendant terms. If $include is
118         *                                                non-empty, $exclude_tree is ignored. Default empty array.
119         *     @type int|string   $number                 Maximum number of terms to return. Accepts ''|0 (all) or any
120         *                                                positive number. Default ''|0 (all). Note that $number may
121         *                                                not return accurate results when coupled with $object_ids.
122         *                                                See #41796 for details.
123         *     @type int          $offset                 The number by which to offset the terms query. Default empty.
124         *     @type string       $fields                 Term fields to query for. Accepts 'all' (returns an array of
125         *                                                complete term objects), 'all_with_object_id' (returns an
126         *                                                array of term objects with the 'object_id' param; works only
127         *                                                when the `$object_ids` parameter is populated), 'ids'
128         *                                                (returns an array of ids), 'tt_ids' (returns an array of
129         *                                                term taxonomy ids), 'id=>parent' (returns an associative
130         *                                                array with ids as keys, parent term IDs as values), 'names'
131         *                                                (returns an array of term names), 'count' (returns the number
132         *                                                of matching terms), 'id=>name' (returns an associative array
133         *                                                with ids as keys, term names as values), or 'id=>slug'
134         *                                                (returns an associative array with ids as keys, term slugs
135         *                                                as values). Default 'all'.
136         *     @type bool         $count                  Whether to return a term count (true) or array of term objects
137         *                                                (false). Will take precedence over `$fields` if true.
138         *                                                Default false.
139         *     @type string|array $name                   Optional. Name or array of names to return term(s) for.
140         *                                                Default empty.
141         *     @type string|array $slug                   Optional. Slug or array of slugs to return term(s) for.
142         *                                                Default empty.
143         *     @type int|array    $term_taxonomy_id       Optional. Term taxonomy ID, or array of term taxonomy IDs,
144         *                                                to match when querying terms.
145         *     @type bool         $hierarchical           Whether to include terms that have non-empty descendants (even
146         *                                                if $hide_empty is set to true). Default true.
147         *     @type string       $search                 Search criteria to match terms. Will be SQL-formatted with
148         *                                                wildcards before and after. Default empty.
149         *     @type string       $name__like             Retrieve terms with criteria by which a term is LIKE
150         *                                                `$name__like`. Default empty.
151         *     @type string       $description__like      Retrieve terms where the description is LIKE
152         *                                                `$description__like`. Default empty.
153         *     @type bool         $pad_counts             Whether to pad the quantity of a term's children in the
154         *                                                quantity of each term's "count" object variable.
155         *                                                Default false.
156         *     @type string       $get                    Whether to return terms regardless of ancestry or whether the
157         *                                                terms are empty. Accepts 'all' or empty (disabled).
158         *                                                Default empty.
159         *     @type int          $child_of               Term ID to retrieve child terms of. If multiple taxonomies
160         *                                                are passed, $child_of is ignored. Default 0.
161         *     @type int|string   $parent                 Parent term ID to retrieve direct-child terms of.
162         *                                                Default empty.
163         *     @type bool         $childless              True to limit results to terms that have no children.
164         *                                                This parameter has no effect on non-hierarchical taxonomies.
165         *                                                Default false.
166         *     @type string       $cache_domain           Unique cache key to be produced when this query is stored in
167         *                                                an object cache. Default is 'core'.
168         *     @type bool         $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
169         *     @type array        $meta_query             Optional. Meta query clauses to limit retrieved terms by.
170         *                                                See `WP_Meta_Query`. Default empty.
171         *     @type string       $meta_key               Limit terms to those matching a specific metadata key.
172         *                                                Can be used in conjunction with `$meta_value`. Default empty.
173         *     @type string       $meta_value             Limit terms to those matching a specific metadata value.
174         *                                                Usually used in conjunction with `$meta_key`. Default empty.
175         *     @type string       $meta_type              MySQL data type that the `$meta_value` will be CAST to for
176         *                                                comparisons. Default empty.
177         *     @type string       $meta_compare           Comparison operator to test the 'meta_value'. Default empty.
178         * }
179         */
180        public function __construct( $query = '' ) {
181                $this->query_var_defaults = array(
182                        'taxonomy'               => null,
183                        'object_ids'             => null,
184                        'orderby'                => 'name',
185                        'order'                  => 'ASC',
186                        'hide_empty'             => true,
187                        'include'                => array(),
188                        'exclude'                => array(),
189                        'exclude_tree'           => array(),
190                        'number'                 => '',
191                        'offset'                 => '',
192                        'fields'                 => 'all',
193                        'count'                  => false,
194                        'name'                   => '',
195                        'slug'                   => '',
196                        'term_taxonomy_id'       => '',
197                        'hierarchical'           => true,
198                        'search'                 => '',
199                        'name__like'             => '',
200                        'description__like'      => '',
201                        'pad_counts'             => false,
202                        'get'                    => '',
203                        'child_of'               => 0,
204                        'parent'                 => '',
205                        'childless'              => false,
206                        'cache_domain'           => 'core',
207                        'update_term_meta_cache' => true,
208                        'meta_query'             => '',
209                        'meta_key'               => '',
210                        'meta_value'             => '',
211                        'meta_type'              => '',
212                        'meta_compare'           => '',
213                );
214
215                if ( ! empty( $query ) ) {
216                        $this->query( $query );
217                }
218        }
219
220        /**
221         * Parse arguments passed to the term query with default query parameters.
222         *
223         * @since 4.6.0
224         *
225         * @param string|array $query WP_Term_Query arguments. See WP_Term_Query::__construct()
226         */
227        public function parse_query( $query = '' ) {
228                if ( empty( $query ) ) {
229                        $query = $this->query_vars;
230                }
231
232                $taxonomies = isset( $query['taxonomy'] ) ? (array) $query['taxonomy'] : null;
233
234                /**
235                 * Filters the terms query default arguments.
236                 *
237                 * Use {@see 'get_terms_args'} to filter the passed arguments.
238                 *
239                 * @since 4.4.0
240                 *
241                 * @param array    $defaults   An array of default get_terms() arguments.
242                 * @param string[] $taxonomies An array of taxonomy names.
243                 */
244                $this->query_var_defaults = apply_filters( 'get_terms_defaults', $this->query_var_defaults, $taxonomies );
245
246                $query = wp_parse_args( $query, $this->query_var_defaults );
247
248                $query['number'] = absint( $query['number'] );
249                $query['offset'] = absint( $query['offset'] );
250
251                // 'parent' overrides 'child_of'.
252                if ( 0 < intval( $query['parent'] ) ) {
253                        $query['child_of'] = false;
254                }
255
256                if ( 'all' == $query['get'] ) {
257                        $query['childless']    = false;
258                        $query['child_of']     = 0;
259                        $query['hide_empty']   = 0;
260                        $query['hierarchical'] = false;
261                        $query['pad_counts']   = false;
262                }
263
264                $query['taxonomy'] = $taxonomies;
265
266                $this->query_vars = $query;
267
268                /**
269                 * Fires after term query vars have been parsed.
270                 *
271                 * @since 4.6.0
272                 *
273                 * @param WP_Term_Query $this Current instance of WP_Term_Query.
274                 */
275                do_action( 'parse_term_query', $this );
276        }
277
278        /**
279         * Sets up the query for retrieving terms.
280         *
281         * @since 4.6.0
282         *
283         * @param string|array $query Array or URL query string of parameters.
284         * @return array|int List of terms, or number of terms when 'count' is passed as a query var.
285         */
286        public function query( $query ) {
287                $this->query_vars = wp_parse_args( $query );
288                return $this->get_terms();
289        }
290
291        /**
292         * Get terms, based on query_vars.
293         *
294         * @since 4.6.0
295         *
296         * @global wpdb $wpdb WordPress database abstraction object.
297         *
298         * @return array List of terms.
299         */
300        public function get_terms() {
301                global $wpdb;
302
303                $this->parse_query( $this->query_vars );
304                $args = &$this->query_vars;
305
306                // Set up meta_query so it's available to 'pre_get_terms'.
307                $this->meta_query = new WP_Meta_Query();
308                $this->meta_query->parse_query_vars( $args );
309
310                /**
311                 * Fires before terms are retrieved.
312                 *
313                 * @since 4.6.0
314                 *
315                 * @param WP_Term_Query $this Current instance of WP_Term_Query.
316                 */
317                do_action( 'pre_get_terms', $this );
318
319                $taxonomies = (array) $args['taxonomy'];
320
321                // Save queries by not crawling the tree in the case of multiple taxes or a flat tax.
322                $has_hierarchical_tax = false;
323                if ( $taxonomies ) {
324                        foreach ( $taxonomies as $_tax ) {
325                                if ( is_taxonomy_hierarchical( $_tax ) ) {
326                                        $has_hierarchical_tax = true;
327                                }
328                        }
329                }
330
331                if ( ! $has_hierarchical_tax ) {
332                        $args['hierarchical'] = false;
333                        $args['pad_counts']   = false;
334                }
335
336                // 'parent' overrides 'child_of'.
337                if ( 0 < intval( $args['parent'] ) ) {
338                        $args['child_of'] = false;
339                }
340
341                if ( 'all' == $args['get'] ) {
342                        $args['childless']    = false;
343                        $args['child_of']     = 0;
344                        $args['hide_empty']   = 0;
345                        $args['hierarchical'] = false;
346                        $args['pad_counts']   = false;
347                }
348
349                /**
350                 * Filters the terms query arguments.
351                 *
352                 * @since 3.1.0
353                 *
354                 * @param array    $args       An array of get_terms() arguments.
355                 * @param string[] $taxonomies An array of taxonomy names.
356                 */
357                $args = apply_filters( 'get_terms_args', $args, $taxonomies );
358
359                // Avoid the query if the queried parent/child_of term has no descendants.
360                $child_of = $args['child_of'];
361                $parent   = $args['parent'];
362
363                if ( $child_of ) {
364                        $_parent = $child_of;
365                } elseif ( $parent ) {
366                        $_parent = $parent;
367                } else {
368                        $_parent = false;
369                }
370
371                if ( $_parent ) {
372                        $in_hierarchy = false;
373                        foreach ( $taxonomies as $_tax ) {
374                                $hierarchy = _get_term_hierarchy( $_tax );
375
376                                if ( isset( $hierarchy[ $_parent ] ) ) {
377                                        $in_hierarchy = true;
378                                }
379                        }
380
381                        if ( ! $in_hierarchy ) {
382                                if ( 'count' == $args['fields'] ) {
383                                        return 0;
384                                } else {
385                                        $this->terms = array();
386                                        return $this->terms;
387                                }
388                        }
389                }
390
391                // 'term_order' is a legal sort order only when joining the relationship table.
392                $_orderby = $this->query_vars['orderby'];
393                if ( 'term_order' === $_orderby && empty( $this->query_vars['object_ids'] ) ) {
394                        $_orderby = 'term_id';
395                }
396                $orderby = $this->parse_orderby( $_orderby );
397
398                if ( $orderby ) {
399                        $orderby = "ORDER BY $orderby";
400                }
401
402                $order = $this->parse_order( $this->query_vars['order'] );
403
404                if ( $taxonomies ) {
405                        $this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
406                }
407
408                $exclude      = $args['exclude'];
409                $exclude_tree = $args['exclude_tree'];
410                $include      = $args['include'];
411
412                $inclusions = '';
413                if ( ! empty( $include ) ) {
414                        $exclude      = '';
415                        $exclude_tree = '';
416                        $inclusions   = implode( ',', wp_parse_id_list( $include ) );
417                }
418
419                if ( ! empty( $inclusions ) ) {
420                        $this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
421                }
422
423                $exclusions = array();
424                if ( ! empty( $exclude_tree ) ) {
425                        $exclude_tree      = wp_parse_id_list( $exclude_tree );
426                        $excluded_children = $exclude_tree;
427                        foreach ( $exclude_tree as $extrunk ) {
428                                $excluded_children = array_merge(
429                                        $excluded_children,
430                                        (array) get_terms(
431                                                reset( $taxonomies ),
432                                                array(
433                                                        'child_of'   => intval( $extrunk ),
434                                                        'fields'     => 'ids',
435                                                        'hide_empty' => 0,
436                                                )
437                                        )
438                                );
439                        }
440                        $exclusions = array_merge( $excluded_children, $exclusions );
441                }
442
443                if ( ! empty( $exclude ) ) {
444                        $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
445                }
446
447                // 'childless' terms are those without an entry in the flattened term hierarchy.
448                $childless = (bool) $args['childless'];
449                if ( $childless ) {
450                        foreach ( $taxonomies as $_tax ) {
451                                $term_hierarchy = _get_term_hierarchy( $_tax );
452                                $exclusions     = array_merge( array_keys( $term_hierarchy ), $exclusions );
453                        }
454                }
455
456                if ( ! empty( $exclusions ) ) {
457                        $exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
458                } else {
459                        $exclusions = '';
460                }
461
462                /**
463                 * Filters the terms to exclude from the terms query.
464                 *
465                 * @since 2.3.0
466                 *
467                 * @param string   $exclusions `NOT IN` clause of the terms query.
468                 * @param array    $args       An array of terms query arguments.
469                 * @param string[] $taxonomies An array of taxonomy names.
470                 */
471                $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
472
473                if ( ! empty( $exclusions ) ) {
474                        // Must do string manipulation here for backward compatibility with filter.
475                        $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions );
476                }
477
478                if (
479                        ( ! empty( $args['name'] ) ) ||
480                        ( is_string( $args['name'] ) && 0 !== strlen( $args['name'] ) )
481                ) {
482                        $names = (array) $args['name'];
483                        foreach ( $names as &$_name ) {
484                                // `sanitize_term_field()` returns slashed data.
485                                $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) );
486                        }
487
488                        $this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
489                }
490
491                if (
492                        ( ! empty( $args['slug'] ) ) ||
493                        ( is_string( $args['slug'] ) && 0 !== strlen( $args['slug'] ) )
494                ) {
495                        if ( is_array( $args['slug'] ) ) {
496                                $slug                               = array_map( 'sanitize_title', $args['slug'] );
497                                $this->sql_clauses['where']['slug'] = "t.slug IN ('" . implode( "', '", $slug ) . "')";
498                        } else {
499                                $slug                               = sanitize_title( $args['slug'] );
500                                $this->sql_clauses['where']['slug'] = "t.slug = '$slug'";
501                        }
502                }
503
504                if ( ! empty( $args['term_taxonomy_id'] ) ) {
505                        if ( is_array( $args['term_taxonomy_id'] ) ) {
506                                $tt_ids = implode( ',', array_map( 'intval', $args['term_taxonomy_id'] ) );
507                                $this->sql_clauses['where']['term_taxonomy_id'] = "tt.term_taxonomy_id IN ({$tt_ids})";
508                        } else {
509                                $this->sql_clauses['where']['term_taxonomy_id'] = $wpdb->prepare( 'tt.term_taxonomy_id = %d', $args['term_taxonomy_id'] );
510                        }
511                }
512
513                if ( ! empty( $args['name__like'] ) ) {
514                        $this->sql_clauses['where']['name__like'] = $wpdb->prepare( 't.name LIKE %s', '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
515                }
516
517                if ( ! empty( $args['description__like'] ) ) {
518                        $this->sql_clauses['where']['description__like'] = $wpdb->prepare( 'tt.description LIKE %s', '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
519                }
520
521                if ( ! empty( $args['object_ids'] ) ) {
522                        $object_ids = $args['object_ids'];
523                        if ( ! is_array( $object_ids ) ) {
524                                $object_ids = array( $object_ids );
525                        }
526
527                        $object_ids                               = implode( ', ', array_map( 'intval', $object_ids ) );
528                        $this->sql_clauses['where']['object_ids'] = "tr.object_id IN ($object_ids)";
529                }
530
531                /*
532                 * When querying for object relationships, the 'count > 0' check
533                 * added by 'hide_empty' is superfluous.
534                 */
535                if ( ! empty( $args['object_ids'] ) ) {
536                        $args['hide_empty'] = false;
537                }
538
539                if ( '' !== $parent ) {
540                        $parent                               = (int) $parent;
541                        $this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
542                }
543
544                $hierarchical = $args['hierarchical'];
545                if ( 'count' == $args['fields'] ) {
546                        $hierarchical = false;
547                }
548                if ( $args['hide_empty'] && ! $hierarchical ) {
549                        $this->sql_clauses['where']['count'] = 'tt.count > 0';
550                }
551
552                $number = $args['number'];
553                $offset = $args['offset'];
554
555                // Don't limit the query results when we have to descend the family tree.
556                if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
557                        if ( $offset ) {
558                                $limits = 'LIMIT ' . $offset . ',' . $number;
559                        } else {
560                                $limits = 'LIMIT ' . $number;
561                        }
562                } else {
563                        $limits = '';
564                }
565
566                if ( ! empty( $args['search'] ) ) {
567                        $this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
568                }
569
570                // Meta query support.
571                $join     = '';
572                $distinct = '';
573
574                // Reparse meta_query query_vars, in case they were modified in a 'pre_get_terms' callback.
575                $this->meta_query->parse_query_vars( $this->query_vars );
576                $mq_sql       = $this->meta_query->get_sql( 'term', 't', 'term_id' );
577                $meta_clauses = $this->meta_query->get_clauses();
578
579                if ( ! empty( $meta_clauses ) ) {
580                        $join                                    .= $mq_sql['join'];
581                        $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] );
582                        $distinct                                .= 'DISTINCT';
583
584                }
585
586                $selects = array();
587                switch ( $args['fields'] ) {
588                        case 'all':
589                        case 'all_with_object_id':
590                        case 'tt_ids':
591                        case 'slugs':
592                                $selects = array( 't.*', 'tt.*' );
593                                if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
594                                        $selects[] = 'tr.object_id';
595                                }
596                                break;
597                        case 'ids':
598                        case 'id=>parent':
599                                $selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
600                                break;
601                        case 'names':
602                                $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
603                                break;
604                        case 'count':
605                                $orderby = '';
606                                $order   = '';
607                                $selects = array( 'COUNT(*)' );
608                                break;
609                        case 'id=>name':
610                                $selects = array( 't.term_id', 't.name', 'tt.count', 'tt.taxonomy' );
611                                break;
612                        case 'id=>slug':
613                                $selects = array( 't.term_id', 't.slug', 'tt.count', 'tt.taxonomy' );
614                                break;
615                }
616
617                $_fields = $args['fields'];
618
619                /**
620                 * Filters the fields to select in the terms query.
621                 *
622                 * Field lists modified using this filter will only modify the term fields returned
623                 * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
624                 * cases, the term fields in the results array will be determined by the `$fields`
625                 * parameter alone.
626                 *
627                 * Use of this filter can result in unpredictable behavior, and is not recommended.
628                 *
629                 * @since 2.8.0
630                 *
631                 * @param string[] $selects    An array of fields to select for the terms query.
632                 * @param array    $args       An array of term query arguments.
633                 * @param string[] $taxonomies An array of taxonomy names.
634                 */
635                $fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
636
637                $join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
638
639                if ( ! empty( $this->query_vars['object_ids'] ) ) {
640                        $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
641                }
642
643                $where = implode( ' AND ', $this->sql_clauses['where'] );
644
645                /**
646                 * Filters the terms query SQL clauses.
647                 *
648                 * @since 3.1.0
649                 *
650                 * @param string[] $pieces     Array of query SQL clauses.
651                 * @param string[] $taxonomies An array of taxonomy names.
652                 * @param array    $args       An array of term query arguments.
653                 */
654                $clauses = apply_filters( 'terms_clauses', compact( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ), $taxonomies, $args );
655
656                $fields   = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
657                $join     = isset( $clauses['join'] ) ? $clauses['join'] : '';
658                $where    = isset( $clauses['where'] ) ? $clauses['where'] : '';
659                $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
660                $orderby  = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
661                $order    = isset( $clauses['order'] ) ? $clauses['order'] : '';
662                $limits   = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
663
664                if ( $where ) {
665                        $where = "WHERE $where";
666                }
667
668                $this->sql_clauses['select']  = "SELECT $distinct $fields";
669                $this->sql_clauses['from']    = "FROM $wpdb->terms AS t $join";
670                $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : '';
671                $this->sql_clauses['limits']  = $limits;
672
673                $this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
674
675                // $args can be anything. Only use the args defined in defaults to compute the key.
676                $key          = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
677                $last_changed = wp_cache_get_last_changed( 'terms' );
678                $cache_key    = "get_terms:$key:$last_changed";
679                $cache        = wp_cache_get( $cache_key, 'terms' );
680                if ( false !== $cache ) {
681                        if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
682                                $cache = $this->populate_terms( $cache );
683                        }
684
685                        $this->terms = $cache;
686                        return $this->terms;
687                }
688
689                if ( 'count' == $_fields ) {
690                        $count = $wpdb->get_var( $this->request );
691                        wp_cache_set( $cache_key, $count, 'terms' );
692                        return $count;
693                }
694
695                $terms = $wpdb->get_results( $this->request );
696                if ( 'all' == $_fields || 'all_with_object_id' === $_fields ) {
697                        update_term_cache( $terms );
698                }
699
700                // Prime termmeta cache.
701                if ( $args['update_term_meta_cache'] ) {
702                        $term_ids = wp_list_pluck( $terms, 'term_id' );
703                        update_termmeta_cache( $term_ids );
704                }
705
706                if ( empty( $terms ) ) {
707                        wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
708                        return array();
709                }
710
711                if ( $child_of ) {
712                        foreach ( $taxonomies as $_tax ) {
713                                $children = _get_term_hierarchy( $_tax );
714                                if ( ! empty( $children ) ) {
715                                        $terms = _get_term_children( $child_of, $terms, $_tax );
716                                }
717                        }
718                }
719
720                // Update term counts to include children.
721                if ( $args['pad_counts'] && 'all' == $_fields ) {
722                        foreach ( $taxonomies as $_tax ) {
723                                _pad_term_counts( $terms, $_tax );
724                        }
725                }
726
727                // Make sure we show empty categories that have children.
728                if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
729                        foreach ( $terms as $k => $term ) {
730                                if ( ! $term->count ) {
731                                        $children = get_term_children( $term->term_id, $term->taxonomy );
732                                        if ( is_array( $children ) ) {
733                                                foreach ( $children as $child_id ) {
734                                                        $child = get_term( $child_id, $term->taxonomy );
735                                                        if ( $child->count ) {
736                                                                continue 2;
737                                                        }
738                                                }
739                                        }
740
741                                        // It really is empty.
742                                        unset( $terms[ $k ] );
743                                }
744                        }
745                }
746
747                /*
748                 * When querying for terms connected to objects, we may get
749                 * duplicate results. The duplicates should be preserved if
750                 * `$fields` is 'all_with_object_id', but should otherwise be
751                 * removed.
752                 */
753                if ( ! empty( $args['object_ids'] ) && 'all_with_object_id' != $_fields ) {
754                        $_tt_ids = $_terms = array();
755                        foreach ( $terms as $term ) {
756                                if ( isset( $_tt_ids[ $term->term_id ] ) ) {
757                                        continue;
758                                }
759
760                                $_tt_ids[ $term->term_id ] = 1;
761                                $_terms[]                  = $term;
762                        }
763
764                        $terms = $_terms;
765                }
766
767                $_terms = array();
768                if ( 'id=>parent' == $_fields ) {
769                        foreach ( $terms as $term ) {
770                                $_terms[ $term->term_id ] = $term->parent;
771                        }
772                } elseif ( 'ids' == $_fields ) {
773                        foreach ( $terms as $term ) {
774                                $_terms[] = (int) $term->term_id;
775                        }
776                } elseif ( 'tt_ids' == $_fields ) {
777                        foreach ( $terms as $term ) {
778                                $_terms[] = (int) $term->term_taxonomy_id;
779                        }
780                } elseif ( 'names' == $_fields ) {
781                        foreach ( $terms as $term ) {
782                                $_terms[] = $term->name;
783                        }
784                } elseif ( 'slugs' == $_fields ) {
785                        foreach ( $terms as $term ) {
786                                $_terms[] = $term->slug;
787                        }
788                } elseif ( 'id=>name' == $_fields ) {
789                        foreach ( $terms as $term ) {
790                                $_terms[ $term->term_id ] = $term->name;
791                        }
792                } elseif ( 'id=>slug' == $_fields ) {
793                        foreach ( $terms as $term ) {
794                                $_terms[ $term->term_id ] = $term->slug;
795                        }
796                }
797
798                if ( ! empty( $_terms ) ) {
799                        $terms = $_terms;
800                }
801
802                // Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
803                if ( $hierarchical && $number && is_array( $terms ) ) {
804                        if ( $offset >= count( $terms ) ) {
805                                $terms = array();
806                        } else {
807                                $terms = array_slice( $terms, $offset, $number, true );
808                        }
809                }
810
811                wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
812
813                if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
814                        $terms = $this->populate_terms( $terms );
815                }
816
817                $this->terms = $terms;
818                return $this->terms;
819        }
820
821        /**
822         * Parse and sanitize 'orderby' keys passed to the term query.
823         *
824         * @since 4.6.0
825         *
826         * @global wpdb $wpdb WordPress database abstraction object.
827         *
828         * @param string $orderby_raw Alias for the field to order by.
829         * @return string|false Value to used in the ORDER clause. False otherwise.
830         */
831        protected function parse_orderby( $orderby_raw ) {
832                $_orderby           = strtolower( $orderby_raw );
833                $maybe_orderby_meta = false;
834
835                if ( in_array( $_orderby, array( 'term_id', 'name', 'slug', 'term_group' ), true ) ) {
836                        $orderby = "t.$_orderby";
837                } elseif ( in_array( $_orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id', 'description' ), true ) ) {
838                        $orderby = "tt.$_orderby";
839                } elseif ( 'term_order' === $_orderby ) {
840                        $orderby = 'tr.term_order';
841                } elseif ( 'include' == $_orderby && ! empty( $this->query_vars['include'] ) ) {
842                        $include = implode( ',', wp_parse_id_list( $this->query_vars['include'] ) );
843                        $orderby = "FIELD( t.term_id, $include )";
844                } elseif ( 'slug__in' == $_orderby && ! empty( $this->query_vars['slug'] ) && is_array( $this->query_vars['slug'] ) ) {
845                        $slugs   = implode( "', '", array_map( 'sanitize_title_for_query', $this->query_vars['slug'] ) );
846                        $orderby = "FIELD( t.slug, '" . $slugs . "')";
847                } elseif ( 'none' == $_orderby ) {
848                        $orderby = '';
849                } elseif ( empty( $_orderby ) || 'id' == $_orderby || 'term_id' === $_orderby ) {
850                        $orderby = 't.term_id';
851                } else {
852                        $orderby = 't.name';
853
854                        // This may be a value of orderby related to meta.
855                        $maybe_orderby_meta = true;
856                }
857
858                /**
859                 * Filters the ORDERBY clause of the terms query.
860                 *
861                 * @since 2.8.0
862                 *
863                 * @param string   $orderby    `ORDERBY` clause of the terms query.
864                 * @param array    $args       An array of term query arguments.
865                 * @param string[] $taxonomies An array of taxonomy names.
866                 */
867                $orderby = apply_filters( 'get_terms_orderby', $orderby, $this->query_vars, $this->query_vars['taxonomy'] );
868
869                // Run after the 'get_terms_orderby' filter for backward compatibility.
870                if ( $maybe_orderby_meta ) {
871                        $maybe_orderby_meta = $this->parse_orderby_meta( $_orderby );
872                        if ( $maybe_orderby_meta ) {
873                                $orderby = $maybe_orderby_meta;
874                        }
875                }
876
877                return $orderby;
878        }
879
880        /**
881         * Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
882         *
883         * @since 4.6.0
884         *
885         * @param string $orderby_raw Raw 'orderby' value passed to WP_Term_Query.
886         * @return string ORDER BY clause.
887         */
888        protected function parse_orderby_meta( $orderby_raw ) {
889                $orderby = '';
890
891                // Tell the meta query to generate its SQL, so we have access to table aliases.
892                $this->meta_query->get_sql( 'term', 't', 'term_id' );
893                $meta_clauses = $this->meta_query->get_clauses();
894                if ( ! $meta_clauses || ! $orderby_raw ) {
895                        return $orderby;
896                }
897
898                $allowed_keys       = array();
899                $primary_meta_key   = null;
900                $primary_meta_query = reset( $meta_clauses );
901                if ( ! empty( $primary_meta_query['key'] ) ) {
902                        $primary_meta_key = $primary_meta_query['key'];
903                        $allowed_keys[]   = $primary_meta_key;
904                }
905                $allowed_keys[] = 'meta_value';
906                $allowed_keys[] = 'meta_value_num';
907                $allowed_keys   = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
908
909                if ( ! in_array( $orderby_raw, $allowed_keys, true ) ) {
910                        return $orderby;
911                }
912
913                switch ( $orderby_raw ) {
914                        case $primary_meta_key:
915                        case 'meta_value':
916                                if ( ! empty( $primary_meta_query['type'] ) ) {
917                                        $orderby = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
918                                } else {
919                                        $orderby = "{$primary_meta_query['alias']}.meta_value";
920                                }
921                                break;
922
923                        case 'meta_value_num':
924                                $orderby = "{$primary_meta_query['alias']}.meta_value+0";
925                                break;
926
927                        default:
928                                if ( array_key_exists( $orderby_raw, $meta_clauses ) ) {
929                                        // $orderby corresponds to a meta_query clause.
930                                        $meta_clause = $meta_clauses[ $orderby_raw ];
931                                        $orderby     = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
932                                }
933                                break;
934                }
935
936                return $orderby;
937        }
938
939        /**
940         * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
941         *
942         * @since 4.6.0
943         *
944         * @param string $order The 'order' query variable.
945         * @return string The sanitized 'order' query variable.
946         */
947        protected function parse_order( $order ) {
948                if ( ! is_string( $order ) || empty( $order ) ) {
949                        return 'DESC';
950                }
951
952                if ( 'ASC' === strtoupper( $order ) ) {
953                        return 'ASC';
954                } else {
955                        return 'DESC';
956                }
957        }
958
959        /**
960         * Used internally to generate a SQL string related to the 'search' parameter.
961         *
962         * @since 4.6.0
963         *
964         * @global wpdb $wpdb WordPress database abstraction object.
965         *
966         * @param string $string
967         * @return string
968         */
969        protected function get_search_sql( $string ) {
970                global $wpdb;
971
972                $like = '%' . $wpdb->esc_like( $string ) . '%';
973
974                return $wpdb->prepare( '((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
975        }
976
977        /**
978         * Creates an array of term objects from an array of term IDs.
979         *
980         * Also discards invalid term objects.
981         *
982         * @since 4.9.8
983         *
984         * @param array $term_ids Term IDs.
985         * @return array
986         */
987        protected function populate_terms( $term_ids ) {
988                $terms = array();
989
990                if ( ! is_array( $term_ids ) ) {
991                        return $terms;
992                }
993
994                foreach ( $term_ids as $key => $term_id ) {
995                        $term = get_term( $term_id );
996                        if ( $term instanceof WP_Term ) {
997                                $terms[ $key ] = $term;
998                        }
999                }
1000
1001                return $terms;
1002        }
1003}
Note: See TracBrowser for help on using the repository browser.