Make WordPress Core

Ticket #47591: terms-pre-query.diff

File terms-pre-query.diff, 30.5 KB (added by tlovett1, 5 years ago)
  • wp-includes/class-wp-term-query.php

     
    356356                 */
    357357                $args = apply_filters( 'get_terms_args', $args, $taxonomies );
    358358
    359                 // Avoid the query if the queried parent/child_of term has no descendants.
    360                 $child_of = $args['child_of'];
    361                 $parent   = $args['parent'];
     359                /**
     360                 * Filters the terms array before the query takes place.
     361                 *
     362                 * Return a non-null value to bypass WordPress's default terms queries. Filtering
     363                 * this value will cause WP_Term_Query to skip all caching and post-processing logic.
     364                 * You will need to handle caching yourself and ensure you respect query parameters such
     365                 * as only returning specific fields.
     366                 *
     367                 * @since ...
     368                 *
     369                 * @param array|null $results Return an array of terms to short-circuit WP's term query
     370                 *                            or null to allow WP to run its normal queries.
     371                 * @param WP_Term_Query $this The WP_Term_Query instance (passed by reference).
     372                 */
     373                $this->terms = apply_filters_ref_array( 'terms_pre_query', array( null, &$this, $args ) );
    362374
    363                 if ( $child_of ) {
    364                         $_parent = $child_of;
    365                 } elseif ( $parent ) {
    366                         $_parent = $parent;
    367                 } else {
    368                         $_parent = false;
    369                 }
     375                if ( null === $this->terms ) {
    370376
    371                 if ( $_parent ) {
    372                         $in_hierarchy = false;
    373                         foreach ( $taxonomies as $_tax ) {
    374                                 $hierarchy = _get_term_hierarchy( $_tax );
     377                        // Avoid the query if the queried parent/child_of term has no descendants.
     378                        $child_of = $args['child_of'];
     379                        $parent   = $args['parent'];
    375380
    376                                 if ( isset( $hierarchy[ $_parent ] ) ) {
    377                                         $in_hierarchy = true;
    378                                 }
     381                        if ( $child_of ) {
     382                                $_parent = $child_of;
     383                        } elseif ( $parent ) {
     384                                $_parent = $parent;
     385                        } else {
     386                                $_parent = false;
    379387                        }
    380388
    381                         if ( ! $in_hierarchy ) {
    382                                 if ( 'count' == $args['fields'] ) {
    383                                         return 0;
    384                                 } else {
    385                                         $this->terms = array();
    386                                         return $this->terms;
     389                        if ( $_parent ) {
     390                                $in_hierarchy = false;
     391                                foreach ( $taxonomies as $_tax ) {
     392                                        $hierarchy = _get_term_hierarchy( $_tax );
     393
     394                                        if ( isset( $hierarchy[ $_parent ] ) ) {
     395                                                $in_hierarchy = true;
     396                                        }
    387397                                }
     398
     399                                if ( ! $in_hierarchy ) {
     400                                        if ( 'count' == $args['fields'] ) {
     401                                                return 0;
     402                                        } else {
     403                                                $this->terms = array();
     404                                                return $this->terms;
     405                                        }
     406                                }
    388407                        }
    389                 }
    390408
    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 );
     409                        // 'term_order' is a legal sort order only when joining the relationship table.
     410                        $_orderby = $this->query_vars['orderby'];
     411                        if ( 'term_order' === $_orderby && empty( $this->query_vars['object_ids'] ) ) {
     412                                $_orderby = 'term_id';
     413                        }
     414                        $orderby = $this->parse_orderby( $_orderby );
    397415
    398                 if ( $orderby ) {
    399                         $orderby = "ORDER BY $orderby";
    400                 }
     416                        if ( $orderby ) {
     417                                $orderby = "ORDER BY $orderby";
     418                        }
    401419
    402                 $order = $this->parse_order( $this->query_vars['order'] );
     420                        $order = $this->parse_order( $this->query_vars['order'] );
    403421
    404                 if ( $taxonomies ) {
    405                         $this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
    406                 }
     422                        if ( $taxonomies ) {
     423                                $this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
     424                        }
    407425
    408                 $exclude      = $args['exclude'];
    409                 $exclude_tree = $args['exclude_tree'];
    410                 $include      = $args['include'];
     426                        $exclude      = $args['exclude'];
     427                        $exclude_tree = $args['exclude_tree'];
     428                        $include      = $args['include'];
    411429
    412                 $inclusions = '';
    413                 if ( ! empty( $include ) ) {
    414                         $exclude      = '';
    415                         $exclude_tree = '';
    416                         $inclusions   = implode( ',', wp_parse_id_list( $include ) );
    417                 }
     430                        $inclusions = '';
     431                        if ( ! empty( $include ) ) {
     432                                $exclude      = '';
     433                                $exclude_tree = '';
     434                                $inclusions   = implode( ',', wp_parse_id_list( $include ) );
     435                        }
    418436
    419                 if ( ! empty( $inclusions ) ) {
    420                         $this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
    421                 }
     437                        if ( ! empty( $inclusions ) ) {
     438                                $this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
     439                        }
    422440
    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,
     441                        $exclusions = array();
     442                        if ( ! empty( $exclude_tree ) ) {
     443                                $exclude_tree      = wp_parse_id_list( $exclude_tree );
     444                                $excluded_children = $exclude_tree;
     445                                foreach ( $exclude_tree as $extrunk ) {
     446                                        $excluded_children = array_merge(
     447                                                $excluded_children,
     448                                                (array) get_terms(
     449                                                        reset( $taxonomies ),
     450                                                        array(
     451                                                                'child_of'   => intval( $extrunk ),
     452                                                                'fields'     => 'ids',
     453                                                                'hide_empty' => 0,
     454                                                        )
    436455                                                )
    437                                         )
    438                                 );
     456                                        );
     457                                }
     458                                $exclusions = array_merge( $excluded_children, $exclusions );
    439459                        }
    440                         $exclusions = array_merge( $excluded_children, $exclusions );
    441                 }
    442460
    443                 if ( ! empty( $exclude ) ) {
    444                         $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
    445                 }
     461                        if ( ! empty( $exclude ) ) {
     462                                $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
     463                        }
    446464
    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 );
     465                        // 'childless' terms are those without an entry in the flattened term hierarchy.
     466                        $childless = (bool) $args['childless'];
     467                        if ( $childless ) {
     468                                foreach ( $taxonomies as $_tax ) {
     469                                        $term_hierarchy = _get_term_hierarchy( $_tax );
     470                                        $exclusions     = array_merge( array_keys( $term_hierarchy ), $exclusions );
     471                                }
    453472                        }
    454                 }
    455473
    456                 if ( ! empty( $exclusions ) ) {
    457                         $exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
    458                 } else {
    459                         $exclusions = '';
    460                 }
     474                        if ( ! empty( $exclusions ) ) {
     475                                $exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
     476                        } else {
     477                                $exclusions = '';
     478                        }
    461479
    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 );
     480                        /**
     481                        * Filters the terms to exclude from the terms query.
     482                        *
     483                        * @since 2.3.0
     484                        *
     485                        * @param string   $exclusions `NOT IN` clause of the terms query.
     486                        * @param array    $args       An array of terms query arguments.
     487                        * @param string[] $taxonomies An array of taxonomy names.
     488                        */
     489                        $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
    472490
    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                 }
     491                        if ( ! empty( $exclusions ) ) {
     492                                // Must do string manipulation here for backward compatibility with filter.
     493                                $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions );
     494                        }
    477495
    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' ) );
     496                        if (
     497                                ( ! empty( $args['name'] ) ) ||
     498                                ( is_string( $args['name'] ) && 0 !== strlen( $args['name'] ) )
     499                        ) {
     500                                $names = (array) $args['name'];
     501                                foreach ( $names as &$_name ) {
     502                                        // `sanitize_term_field()` returns slashed data.
     503                                        $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) );
     504                                }
     505
     506                                $this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
    486507                        }
    487508
    488                         $this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
    489                 }
     509                        if (
     510                                ( ! empty( $args['slug'] ) ) ||
     511                                ( is_string( $args['slug'] ) && 0 !== strlen( $args['slug'] ) )
     512                        ) {
     513                                if ( is_array( $args['slug'] ) ) {
     514                                        $slug                               = array_map( 'sanitize_title', $args['slug'] );
     515                                        $this->sql_clauses['where']['slug'] = "t.slug IN ('" . implode( "', '", $slug ) . "')";
     516                                } else {
     517                                        $slug                               = sanitize_title( $args['slug'] );
     518                                        $this->sql_clauses['where']['slug'] = "t.slug = '$slug'";
     519                                }
     520                        }
    490521
    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'";
     522                        if ( ! empty( $args['term_taxonomy_id'] ) ) {
     523                                if ( is_array( $args['term_taxonomy_id'] ) ) {
     524                                        $tt_ids = implode( ',', array_map( 'intval', $args['term_taxonomy_id'] ) );
     525                                        $this->sql_clauses['where']['term_taxonomy_id'] = "tt.term_taxonomy_id IN ({$tt_ids})";
     526                                } else {
     527                                        $this->sql_clauses['where']['term_taxonomy_id'] = $wpdb->prepare( 'tt.term_taxonomy_id = %d', $args['term_taxonomy_id'] );
     528                                }
    501529                        }
    502                 }
    503530
    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'] );
     531                        if ( ! empty( $args['name__like'] ) ) {
     532                                $this->sql_clauses['where']['name__like'] = $wpdb->prepare( 't.name LIKE %s', '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
    510533                        }
    511                 }
    512534
    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                 }
     535                        if ( ! empty( $args['description__like'] ) ) {
     536                                $this->sql_clauses['where']['description__like'] = $wpdb->prepare( 'tt.description LIKE %s', '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
     537                        }
    516538
    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                 }
     539                        if ( ! empty( $args['object_ids'] ) ) {
     540                                $object_ids = $args['object_ids'];
     541                                if ( ! is_array( $object_ids ) ) {
     542                                        $object_ids = array( $object_ids );
     543                                }
    520544
    521                 if ( ! empty( $args['object_ids'] ) ) {
    522                         $object_ids = $args['object_ids'];
    523                         if ( ! is_array( $object_ids ) ) {
    524                                 $object_ids = array( $object_ids );
     545                                $object_ids                               = implode( ', ', array_map( 'intval', $object_ids ) );
     546                                $this->sql_clauses['where']['object_ids'] = "tr.object_id IN ($object_ids)";
    525547                        }
    526548
    527                         $object_ids                               = implode( ', ', array_map( 'intval', $object_ids ) );
    528                         $this->sql_clauses['where']['object_ids'] = "tr.object_id IN ($object_ids)";
    529                 }
     549                        /*
     550                         * When querying for object relationships, the 'count > 0' check
     551                         * added by 'hide_empty' is superfluous.
     552                         */
     553                        if ( ! empty( $args['object_ids'] ) ) {
     554                                $args['hide_empty'] = false;
     555                        }
    530556
    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                 }
     557                        if ( '' !== $parent ) {
     558                                $parent                               = (int) $parent;
     559                                $this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
     560                        }
    538561
    539                 if ( '' !== $parent ) {
    540                         $parent                               = (int) $parent;
    541                         $this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
    542                 }
     562                        $hierarchical = $args['hierarchical'];
     563                        if ( 'count' == $args['fields'] ) {
     564                                $hierarchical = false;
     565                        }
     566                        if ( $args['hide_empty'] && ! $hierarchical ) {
     567                                $this->sql_clauses['where']['count'] = 'tt.count > 0';
     568                        }
    543569
    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                 }
     570                        $number = $args['number'];
     571                        $offset = $args['offset'];
    551572
    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;
     573                        // Don't limit the query results when we have to descend the family tree.
     574                        if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
     575                                if ( $offset ) {
     576                                        $limits = 'LIMIT ' . $offset . ',' . $number;
     577                                } else {
     578                                        $limits = 'LIMIT ' . $number;
     579                                }
    559580                        } else {
    560                                 $limits = 'LIMIT ' . $number;
     581                                $limits = '';
    561582                        }
    562                 } else {
    563                         $limits = '';
    564                 }
    565583
    566                 if ( ! empty( $args['search'] ) ) {
    567                         $this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
    568                 }
     584                        if ( ! empty( $args['search'] ) ) {
     585                                $this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
     586                        }
    569587
    570                 // Meta query support.
    571                 $join     = '';
    572                 $distinct = '';
     588                        // Meta query support.
     589                        $join     = '';
     590                        $distinct = '';
    573591
    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();
     592                        // Reparse meta_query query_vars, in case they were modified in a 'pre_get_terms' callback.
     593                        $this->meta_query->parse_query_vars( $this->query_vars );
     594                        $mq_sql       = $this->meta_query->get_sql( 'term', 't', 'term_id' );
     595                        $meta_clauses = $this->meta_query->get_clauses();
    578596
    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';
     597                        if ( ! empty( $meta_clauses ) ) {
     598                                $join                                    .= $mq_sql['join'];
     599                                $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] );
     600                                $distinct                                .= 'DISTINCT';
    583601
    584                 }
     602                        }
    585603
    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                 }
     604                        $selects = array();
     605                        switch ( $args['fields'] ) {
     606                                case 'all':
     607                                case 'all_with_object_id':
     608                                case 'tt_ids':
     609                                case 'slugs':
     610                                        $selects = array( 't.*', 'tt.*' );
     611                                        if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
     612                                                $selects[] = 'tr.object_id';
     613                                        }
     614                                        break;
     615                                case 'ids':
     616                                case 'id=>parent':
     617                                        $selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
     618                                        break;
     619                                case 'names':
     620                                        $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
     621                                        break;
     622                                case 'count':
     623                                        $orderby = '';
     624                                        $order   = '';
     625                                        $selects = array( 'COUNT(*)' );
     626                                        break;
     627                                case 'id=>name':
     628                                        $selects = array( 't.term_id', 't.name', 'tt.count', 'tt.taxonomy' );
     629                                        break;
     630                                case 'id=>slug':
     631                                        $selects = array( 't.term_id', 't.slug', 'tt.count', 'tt.taxonomy' );
     632                                        break;
     633                        }
    616634
    617                 $_fields = $args['fields'];
     635                        $_fields = $args['fields'];
    618636
    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 ) );
     637                        /**
     638                        * Filters the fields to select in the terms query.
     639                        *
     640                        * Field lists modified using this filter will only modify the term fields returned
     641                        * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
     642                        * cases, the term fields in the results array will be determined by the `$fields`
     643                        * parameter alone.
     644                        *
     645                        * Use of this filter can result in unpredictable behavior, and is not recommended.
     646                        *
     647                        * @since 2.8.0
     648                        *
     649                        * @param string[] $selects    An array of fields to select for the terms query.
     650                        * @param array    $args       An array of term query arguments.
     651                        * @param string[] $taxonomies An array of taxonomy names.
     652                        */
     653                        $fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
    636654
    637                 $join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
     655                        $join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
    638656
    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                 }
     657                        if ( ! empty( $this->query_vars['object_ids'] ) ) {
     658                                $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
     659                        }
    642660
    643                 $where = implode( ' AND ', $this->sql_clauses['where'] );
     661                        $where = implode( ' AND ', $this->sql_clauses['where'] );
    644662
    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 );
     663                        /**
     664                        * Filters the terms query SQL clauses.
     665                        *
     666                        * @since 3.1.0
     667                        *
     668                        * @param string[] $pieces     Array of query SQL clauses.
     669                        * @param string[] $taxonomies An array of taxonomy names.
     670                        * @param array    $args       An array of term query arguments.
     671                        */
     672                        $clauses = apply_filters( 'terms_clauses', compact( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ), $taxonomies, $args );
    655673
    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'] : '';
     674                        $fields   = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
     675                        $join     = isset( $clauses['join'] ) ? $clauses['join'] : '';
     676                        $where    = isset( $clauses['where'] ) ? $clauses['where'] : '';
     677                        $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
     678                        $orderby  = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
     679                        $order    = isset( $clauses['order'] ) ? $clauses['order'] : '';
     680                        $limits   = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
    663681
    664                 if ( $where ) {
    665                         $where = "WHERE $where";
    666                 }
     682                        if ( $where ) {
     683                                $where = "WHERE $where";
     684                        }
    667685
    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;
     686                        $this->sql_clauses['select']  = "SELECT $distinct $fields";
     687                        $this->sql_clauses['from']    = "FROM $wpdb->terms AS t $join";
     688                        $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : '';
     689                        $this->sql_clauses['limits']  = $limits;
    672690
    673                 $this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
     691                        $this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
    674692
    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 );
     693                        // $args can be anything. Only use the args defined in defaults to compute the key.
     694                        $key          = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
     695                        $last_changed = wp_cache_get_last_changed( 'terms' );
     696                        $cache_key    = "get_terms:$key:$last_changed";
     697                        $cache        = wp_cache_get( $cache_key, 'terms' );
     698                        if ( false !== $cache ) {
     699                                if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
     700                                        $cache = $this->populate_terms( $cache );
     701                                }
     702
     703                                $this->terms = $cache;
     704                                return $this->terms;
    683705                        }
    684706
    685                         $this->terms = $cache;
    686                         return $this->terms;
    687                 }
     707                        if ( 'count' == $_fields ) {
     708                                $count = $wpdb->get_var( $this->request );
     709                                wp_cache_set( $cache_key, $count, 'terms' );
     710                                return $count;
     711                        }
    688712
    689                 if ( 'count' == $_fields ) {
    690                         $count = $wpdb->get_var( $this->request );
    691                         wp_cache_set( $cache_key, $count, 'terms' );
    692                         return $count;
    693                 }
     713                        $terms = $wpdb->get_results( $this->request );
     714                        if ( 'all' == $_fields || 'all_with_object_id' === $_fields ) {
     715                                update_term_cache( $terms );
     716                        }
    694717
    695                 $terms = $wpdb->get_results( $this->request );
    696                 if ( 'all' == $_fields || 'all_with_object_id' === $_fields ) {
    697                         update_term_cache( $terms );
    698                 }
     718                        // Prime termmeta cache.
     719                        if ( $args['update_term_meta_cache'] ) {
     720                                $term_ids = wp_list_pluck( $terms, 'term_id' );
     721                                update_termmeta_cache( $term_ids );
     722                        }
    699723
    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                 }
     724                        if ( empty( $terms ) ) {
     725                                wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
     726                                return array();
     727                        }
    705728
    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 );
     729                        if ( $child_of ) {
     730                                foreach ( $taxonomies as $_tax ) {
     731                                        $children = _get_term_hierarchy( $_tax );
     732                                        if ( ! empty( $children ) ) {
     733                                                $terms = _get_term_children( $child_of, $terms, $_tax );
     734                                        }
    716735                                }
    717736                        }
    718                 }
    719737
    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 );
     738                        // Update term counts to include children.
     739                        if ( $args['pad_counts'] && 'all' == $_fields ) {
     740                                foreach ( $taxonomies as $_tax ) {
     741                                        _pad_term_counts( $terms, $_tax );
     742                                }
    724743                        }
    725                 }
    726744
    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;
     745                        // Make sure we show empty categories that have children.
     746                        if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
     747                                foreach ( $terms as $k => $term ) {
     748                                        if ( ! $term->count ) {
     749                                                $children = get_term_children( $term->term_id, $term->taxonomy );
     750                                                if ( is_array( $children ) ) {
     751                                                        foreach ( $children as $child_id ) {
     752                                                                $child = get_term( $child_id, $term->taxonomy );
     753                                                                if ( $child->count ) {
     754                                                                        continue 2;
     755                                                                }
    737756                                                        }
    738757                                                }
     758
     759                                                // It really is empty.
     760                                                unset( $terms[ $k ] );
    739761                                        }
    740 
    741                                         // It really is empty.
    742                                         unset( $terms[ $k ] );
    743762                                }
    744763                        }
    745                 }
    746764
    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;
     765                        /*
     766                         * When querying for terms connected to objects, we may get
     767                         * duplicate results. The duplicates should be preserved if
     768                         * `$fields` is 'all_with_object_id', but should otherwise be
     769                         * removed.
     770                         */
     771                        if ( ! empty( $args['object_ids'] ) && 'all_with_object_id' != $_fields ) {
     772                                $_tt_ids = $_terms = array();
     773                                foreach ( $terms as $term ) {
     774                                        if ( isset( $_tt_ids[ $term->term_id ] ) ) {
     775                                                continue;
     776                                        }
     777
     778                                        $_tt_ids[ $term->term_id ] = 1;
     779                                        $_terms[]                  = $term;
    758780                                }
    759781
    760                                 $_tt_ids[ $term->term_id ] = 1;
    761                                 $_terms[]                  = $term;
     782                                $terms = $_terms;
    762783                        }
    763784
    764                         $terms = $_terms;
    765                 }
     785                        $_terms = array();
     786                        if ( 'id=>parent' == $_fields ) {
     787                                foreach ( $terms as $term ) {
     788                                        $_terms[ $term->term_id ] = $term->parent;
     789                                }
     790                        } elseif ( 'ids' == $_fields ) {
     791                                foreach ( $terms as $term ) {
     792                                        $_terms[] = (int) $term->term_id;
     793                                }
     794                        } elseif ( 'tt_ids' == $_fields ) {
     795                                foreach ( $terms as $term ) {
     796                                        $_terms[] = (int) $term->term_taxonomy_id;
     797                                }
     798                        } elseif ( 'names' == $_fields ) {
     799                                foreach ( $terms as $term ) {
     800                                        $_terms[] = $term->name;
     801                                }
     802                        } elseif ( 'slugs' == $_fields ) {
     803                                foreach ( $terms as $term ) {
     804                                        $_terms[] = $term->slug;
     805                                }
     806                        } elseif ( 'id=>name' == $_fields ) {
     807                                foreach ( $terms as $term ) {
     808                                        $_terms[ $term->term_id ] = $term->name;
     809                                }
     810                        } elseif ( 'id=>slug' == $_fields ) {
     811                                foreach ( $terms as $term ) {
     812                                        $_terms[ $term->term_id ] = $term->slug;
     813                                }
     814                        }
    766815
    767                 $_terms = array();
    768                 if ( 'id=>parent' == $_fields ) {
    769                         foreach ( $terms as $term ) {
    770                                 $_terms[ $term->term_id ] = $term->parent;
     816                        if ( ! empty( $_terms ) ) {
     817                                $terms = $_terms;
    771818                        }
    772                 } elseif ( 'ids' == $_fields ) {
    773                         foreach ( $terms as $term ) {
    774                                 $_terms[] = (int) $term->term_id;
     819
     820                        // Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
     821                        if ( $hierarchical && $number && is_array( $terms ) ) {
     822                                if ( $offset >= count( $terms ) ) {
     823                                        $terms = array();
     824                                } else {
     825                                        $terms = array_slice( $terms, $offset, $number, true );
     826                                }
    775827                        }
    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                 }
    797828
    798                 if ( ! empty( $_terms ) ) {
    799                         $terms = $_terms;
    800                 }
     829                        wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
    801830
    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 );
     831                        if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
     832                                $terms = $this->populate_terms( $terms );
    808833                        }
    809                 }
    810834
    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 );
     835                        $this->terms = $terms;
    815836                }
    816837
    817                 $this->terms = $terms;
    818838                return $this->terms;
    819839        }
    820840