home *** CD-ROM | disk | FTP | other *** search
/ HTML Examples / WP.iso / wordpress / wp-includes / class-wp-term-query.php < prev    next >
Encoding:
PHP Script  |  2017-10-16  |  32.8 KB  |  970 lines

  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.  */
  18. class 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; only works
  127.      *                                                when the `$fields` parameter is 'object_ids' ), '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              Type of object metadata is for (e.g., comment, post, or user).
  176.      *                                                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 array $taxonomies An array of taxonomies.
  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 array $taxonomies An array of taxonomies.
  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.                 return array();
  383.             }
  384.         }
  385.  
  386.         // 'term_order' is a legal sort order only when joining the relationship table.
  387.         $_orderby = $this->query_vars['orderby'];
  388.         if ( 'term_order' === $_orderby && empty( $this->query_vars['object_ids'] ) ) {
  389.             $_orderby = 'term_id';
  390.         }
  391.         $orderby = $this->parse_orderby( $_orderby );
  392.  
  393.         if ( $orderby ) {
  394.             $orderby = "ORDER BY $orderby";
  395.         }
  396.  
  397.         $order = $this->parse_order( $this->query_vars['order'] );
  398.  
  399.         if ( $taxonomies ) {
  400.             $this->sql_clauses['where']['taxonomy'] = "tt.taxonomy IN ('" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "')";
  401.         }
  402.  
  403.         $exclude      = $args['exclude'];
  404.         $exclude_tree = $args['exclude_tree'];
  405.         $include      = $args['include'];
  406.  
  407.         $inclusions = '';
  408.         if ( ! empty( $include ) ) {
  409.             $exclude = '';
  410.             $exclude_tree = '';
  411.             $inclusions = implode( ',', wp_parse_id_list( $include ) );
  412.         }
  413.  
  414.         if ( ! empty( $inclusions ) ) {
  415.             $this->sql_clauses['where']['inclusions'] = 't.term_id IN ( ' . $inclusions . ' )';
  416.         }
  417.  
  418.         $exclusions = array();
  419.         if ( ! empty( $exclude_tree ) ) {
  420.             $exclude_tree = wp_parse_id_list( $exclude_tree );
  421.             $excluded_children = $exclude_tree;
  422.             foreach ( $exclude_tree as $extrunk ) {
  423.                 $excluded_children = array_merge(
  424.                     $excluded_children,
  425.                     (array) get_terms( reset( $taxonomies ), array(
  426.                         'child_of' => intval( $extrunk ),
  427.                         'fields' => 'ids',
  428.                         'hide_empty' => 0
  429.                     ) )
  430.                 );
  431.             }
  432.             $exclusions = array_merge( $excluded_children, $exclusions );
  433.         }
  434.  
  435.         if ( ! empty( $exclude ) ) {
  436.             $exclusions = array_merge( wp_parse_id_list( $exclude ), $exclusions );
  437.         }
  438.  
  439.         // 'childless' terms are those without an entry in the flattened term hierarchy.
  440.         $childless = (bool) $args['childless'];
  441.         if ( $childless ) {
  442.             foreach ( $taxonomies as $_tax ) {
  443.                 $term_hierarchy = _get_term_hierarchy( $_tax );
  444.                 $exclusions = array_merge( array_keys( $term_hierarchy ), $exclusions );
  445.             }
  446.         }
  447.  
  448.         if ( ! empty( $exclusions ) ) {
  449.             $exclusions = 't.term_id NOT IN (' . implode( ',', array_map( 'intval', $exclusions ) ) . ')';
  450.         } else {
  451.             $exclusions = '';
  452.         }
  453.  
  454.         /**
  455.          * Filters the terms to exclude from the terms query.
  456.          *
  457.          * @since 2.3.0
  458.          *
  459.          * @param string $exclusions `NOT IN` clause of the terms query.
  460.          * @param array  $args       An array of terms query arguments.
  461.          * @param array  $taxonomies An array of taxonomies.
  462.          */
  463.         $exclusions = apply_filters( 'list_terms_exclusions', $exclusions, $args, $taxonomies );
  464.  
  465.         if ( ! empty( $exclusions ) ) {
  466.             // Must do string manipulation here for backward compatibility with filter.
  467.             $this->sql_clauses['where']['exclusions'] = preg_replace( '/^\s*AND\s*/', '', $exclusions );
  468.         }
  469.  
  470.         if (
  471.             ( ! empty( $args['name'] ) ) ||
  472.             ( is_string( $args['name'] ) && 0 !== strlen( $args['name'] ) )
  473.         ) {
  474.             $names = (array) $args['name'];
  475.             foreach ( $names as &$_name ) {
  476.                 // `sanitize_term_field()` returns slashed data.
  477.                 $_name = stripslashes( sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' ) );
  478.             }
  479.  
  480.             $this->sql_clauses['where']['name'] = "t.name IN ('" . implode( "', '", array_map( 'esc_sql', $names ) ) . "')";
  481.         }
  482.  
  483.         if (
  484.             ( ! empty( $args['slug'] ) ) ||
  485.             ( is_string( $args['slug'] ) && 0 !== strlen( $args['slug'] ) )
  486.         ) {
  487.             if ( is_array( $args['slug'] ) ) {
  488.                 $slug = array_map( 'sanitize_title', $args['slug'] );
  489.                 $this->sql_clauses['where']['slug'] = "t.slug IN ('" . implode( "', '", $slug ) . "')";
  490.             } else {
  491.                 $slug = sanitize_title( $args['slug'] );
  492.                 $this->sql_clauses['where']['slug'] = "t.slug = '$slug'";
  493.             }
  494.         }
  495.  
  496.         if ( ! empty( $args['term_taxonomy_id'] ) ) {
  497.             if ( is_array( $args['term_taxonomy_id'] ) ) {
  498.                 $tt_ids = implode( ',', array_map( 'intval', $args['term_taxonomy_id'] ) );
  499.                 $this->sql_clauses['where']['term_taxonomy_id'] = "tt.term_taxonomy_id IN ({$tt_ids})";
  500.             } else {
  501.                 $this->sql_clauses['where']['term_taxonomy_id'] = $wpdb->prepare( "tt.term_taxonomy_id = %d", $args['term_taxonomy_id'] );
  502.             }
  503.         }
  504.  
  505.         if ( ! empty( $args['name__like'] ) ) {
  506.             $this->sql_clauses['where']['name__like'] = $wpdb->prepare( "t.name LIKE %s", '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
  507.         }
  508.  
  509.         if ( ! empty( $args['description__like'] ) ) {
  510.             $this->sql_clauses['where']['description__like'] = $wpdb->prepare( "tt.description LIKE %s", '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
  511.         }
  512.  
  513.         if ( ! empty( $args['object_ids'] ) ) {
  514.             $object_ids = $args['object_ids'];
  515.             if ( ! is_array( $object_ids ) ) {
  516.                 $object_ids = array( $object_ids );
  517.             }
  518.  
  519.             $object_ids = implode( ', ', array_map( 'intval', $object_ids ) );
  520.             $this->sql_clauses['where']['object_ids'] = "tr.object_id IN ($object_ids)";
  521.         }
  522.  
  523.         /*
  524.          * When querying for object relationships, the 'count > 0' check
  525.          * added by 'hide_empty' is superfluous.
  526.          */
  527.         if ( ! empty( $args['object_ids'] ) ) {
  528.             $args['hide_empty'] = false;
  529.         }
  530.  
  531.         if ( '' !== $parent ) {
  532.             $parent = (int) $parent;
  533.             $this->sql_clauses['where']['parent'] = "tt.parent = '$parent'";
  534.         }
  535.  
  536.         $hierarchical = $args['hierarchical'];
  537.         if ( 'count' == $args['fields'] ) {
  538.             $hierarchical = false;
  539.         }
  540.         if ( $args['hide_empty'] && !$hierarchical ) {
  541.             $this->sql_clauses['where']['count'] = 'tt.count > 0';
  542.         }
  543.  
  544.         $number = $args['number'];
  545.         $offset = $args['offset'];
  546.  
  547.         // Don't limit the query results when we have to descend the family tree.
  548.         if ( $number && ! $hierarchical && ! $child_of && '' === $parent ) {
  549.             if ( $offset ) {
  550.                 $limits = 'LIMIT ' . $offset . ',' . $number;
  551.             } else {
  552.                 $limits = 'LIMIT ' . $number;
  553.             }
  554.         } else {
  555.             $limits = '';
  556.         }
  557.  
  558.  
  559.         if ( ! empty( $args['search'] ) ) {
  560.             $this->sql_clauses['where']['search'] = $this->get_search_sql( $args['search'] );
  561.         }
  562.  
  563.         // Meta query support.
  564.         $join = '';
  565.         $distinct = '';
  566.  
  567.         // Reparse meta_query query_vars, in case they were modified in a 'pre_get_terms' callback.
  568.         $this->meta_query->parse_query_vars( $this->query_vars );
  569.         $mq_sql = $this->meta_query->get_sql( 'term', 't', 'term_id' );
  570.         $meta_clauses = $this->meta_query->get_clauses();
  571.  
  572.         if ( ! empty( $meta_clauses ) ) {
  573.             $join .= $mq_sql['join'];
  574.             $this->sql_clauses['where']['meta_query'] = preg_replace( '/^\s*AND\s*/', '', $mq_sql['where'] );
  575.             $distinct .= "DISTINCT";
  576.  
  577.         }
  578.  
  579.         $selects = array();
  580.         switch ( $args['fields'] ) {
  581.             case 'all':
  582.             case 'all_with_object_id' :
  583.             case 'tt_ids' :
  584.             case 'slugs' :
  585.                 $selects = array( 't.*', 'tt.*' );
  586.                 if ( 'all_with_object_id' === $args['fields'] && ! empty( $args['object_ids'] ) ) {
  587.                     $selects[] = 'tr.object_id';
  588.                 }
  589.                 break;
  590.             case 'ids':
  591.             case 'id=>parent':
  592.                 $selects = array( 't.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy' );
  593.                 break;
  594.             case 'names':
  595.                 $selects = array( 't.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy' );
  596.                 break;
  597.             case 'count':
  598.                 $orderby = '';
  599.                 $order = '';
  600.                 $selects = array( 'COUNT(*)' );
  601.                 break;
  602.             case 'id=>name':
  603.                 $selects = array( 't.term_id', 't.name', 'tt.count', 'tt.taxonomy' );
  604.                 break;
  605.             case 'id=>slug':
  606.                 $selects = array( 't.term_id', 't.slug', 'tt.count', 'tt.taxonomy' );
  607.                 break;
  608.         }
  609.  
  610.         $_fields = $args['fields'];
  611.  
  612.         /**
  613.          * Filters the fields to select in the terms query.
  614.          *
  615.          * Field lists modified using this filter will only modify the term fields returned
  616.          * by the function when the `$fields` parameter set to 'count' or 'all'. In all other
  617.          * cases, the term fields in the results array will be determined by the `$fields`
  618.          * parameter alone.
  619.          *
  620.          * Use of this filter can result in unpredictable behavior, and is not recommended.
  621.          *
  622.          * @since 2.8.0
  623.          *
  624.          * @param array $selects    An array of fields to select for the terms query.
  625.          * @param array $args       An array of term query arguments.
  626.          * @param array $taxonomies An array of taxonomies.
  627.          */
  628.         $fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
  629.  
  630.         $join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
  631.  
  632.         if ( ! empty( $this->query_vars['object_ids'] ) ) {
  633.             $join .= " INNER JOIN {$wpdb->term_relationships} AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id";
  634.         }
  635.  
  636.         $where = implode( ' AND ', $this->sql_clauses['where'] );
  637.  
  638.         /**
  639.          * Filters the terms query SQL clauses.
  640.          *
  641.          * @since 3.1.0
  642.          *
  643.          * @param array $pieces     Terms query SQL clauses.
  644.          * @param array $taxonomies An array of taxonomies.
  645.          * @param array $args       An array of terms query arguments.
  646.          */
  647.         $clauses = apply_filters( 'terms_clauses', compact( 'fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits' ), $taxonomies, $args );
  648.  
  649.         $fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
  650.         $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
  651.         $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
  652.         $distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
  653.         $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
  654.         $order = isset( $clauses[ 'order' ] ) ? $clauses[ 'order' ] : '';
  655.         $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
  656.  
  657.         if ( $where ) {
  658.             $where = "WHERE $where";
  659.         }
  660.  
  661.         $this->sql_clauses['select']  = "SELECT $distinct $fields";
  662.         $this->sql_clauses['from']    = "FROM $wpdb->terms AS t $join";
  663.         $this->sql_clauses['orderby'] = $orderby ? "$orderby $order" : '';
  664.         $this->sql_clauses['limits']  = $limits;
  665.  
  666.         $this->request = "{$this->sql_clauses['select']} {$this->sql_clauses['from']} {$where} {$this->sql_clauses['orderby']} {$this->sql_clauses['limits']}";
  667.  
  668.         // $args can be anything. Only use the args defined in defaults to compute the key.
  669.         $key = md5( serialize( wp_array_slice_assoc( $args, array_keys( $this->query_var_defaults ) ) ) . serialize( $taxonomies ) . $this->request );
  670.         $last_changed = wp_cache_get_last_changed( 'terms' );
  671.         $cache_key = "get_terms:$key:$last_changed";
  672.         $cache = wp_cache_get( $cache_key, 'terms' );
  673.         if ( false !== $cache ) {
  674.             if ( 'all' === $_fields ) {
  675.                 $cache = array_map( 'get_term', $cache );
  676.             }
  677.  
  678.             $this->terms = $cache;
  679.             return $this->terms;
  680.         }
  681.  
  682.         if ( 'count' == $_fields ) {
  683.             $count = $wpdb->get_var( $this->request );
  684.             wp_cache_set( $cache_key, $count, 'terms' );
  685.             return $count;
  686.         }
  687.  
  688.         $terms = $wpdb->get_results( $this->request );
  689.         if ( 'all' == $_fields || 'all_with_object_id' === $_fields ) {
  690.             update_term_cache( $terms );
  691.         }
  692.  
  693.         // Prime termmeta cache.
  694.         if ( $args['update_term_meta_cache'] ) {
  695.             $term_ids = wp_list_pluck( $terms, 'term_id' );
  696.             update_termmeta_cache( $term_ids );
  697.         }
  698.  
  699.         if ( empty( $terms ) ) {
  700.             wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
  701.             return array();
  702.         }
  703.  
  704.         if ( $child_of ) {
  705.             foreach ( $taxonomies as $_tax ) {
  706.                 $children = _get_term_hierarchy( $_tax );
  707.                 if ( ! empty( $children ) ) {
  708.                     $terms = _get_term_children( $child_of, $terms, $_tax );
  709.                 }
  710.             }
  711.         }
  712.  
  713.         // Update term counts to include children.
  714.         if ( $args['pad_counts'] && 'all' == $_fields ) {
  715.             foreach ( $taxonomies as $_tax ) {
  716.                 _pad_term_counts( $terms, $_tax );
  717.             }
  718.         }
  719.  
  720.         // Make sure we show empty categories that have children.
  721.         if ( $hierarchical && $args['hide_empty'] && is_array( $terms ) ) {
  722.             foreach ( $terms as $k => $term ) {
  723.                 if ( ! $term->count ) {
  724.                     $children = get_term_children( $term->term_id, $term->taxonomy );
  725.                     if ( is_array( $children ) ) {
  726.                         foreach ( $children as $child_id ) {
  727.                             $child = get_term( $child_id, $term->taxonomy );
  728.                             if ( $child->count ) {
  729.                                 continue 2;
  730.                             }
  731.                         }
  732.                     }
  733.  
  734.                     // It really is empty.
  735.                     unset( $terms[ $k ] );
  736.                 }
  737.             }
  738.         }
  739.  
  740.         /*
  741.          * When querying for terms connected to objects, we may get
  742.          * duplicate results. The duplicates should be preserved if
  743.          * `$fields` is 'all_with_object_id', but should otherwise be
  744.          * removed.
  745.          */
  746.         if ( ! empty( $args['object_ids'] ) && 'all_with_object_id' != $_fields ) {
  747.             $_tt_ids = $_terms = array();
  748.             foreach ( $terms as $term ) {
  749.                 if ( isset( $_tt_ids[ $term->term_id ] ) ) {
  750.                     continue;
  751.                 }
  752.  
  753.                 $_tt_ids[ $term->term_id ] = 1;
  754.                 $_terms[] = $term;
  755.             }
  756.  
  757.             $terms = $_terms;
  758.         }
  759.  
  760.         $_terms = array();
  761.         if ( 'id=>parent' == $_fields ) {
  762.             foreach ( $terms as $term ) {
  763.                 $_terms[ $term->term_id ] = $term->parent;
  764.             }
  765.         } elseif ( 'ids' == $_fields ) {
  766.             foreach ( $terms as $term ) {
  767.                 $_terms[] = (int) $term->term_id;
  768.             }
  769.         } elseif ( 'tt_ids' == $_fields ) {
  770.             foreach ( $terms as $term ) {
  771.                 $_terms[] = (int) $term->term_taxonomy_id;
  772.             }
  773.         } elseif ( 'names' == $_fields ) {
  774.             foreach ( $terms as $term ) {
  775.                 $_terms[] = $term->name;
  776.             }
  777.         } elseif ( 'slugs' == $_fields ) {
  778.             foreach ( $terms as $term ) {
  779.                 $_terms[] = $term->slug;
  780.             }
  781.         } elseif ( 'id=>name' == $_fields ) {
  782.             foreach ( $terms as $term ) {
  783.                 $_terms[ $term->term_id ] = $term->name;
  784.             }
  785.         } elseif ( 'id=>slug' == $_fields ) {
  786.             foreach ( $terms as $term ) {
  787.                 $_terms[ $term->term_id ] = $term->slug;
  788.             }
  789.         }
  790.  
  791.         if ( ! empty( $_terms ) ) {
  792.             $terms = $_terms;
  793.         }
  794.  
  795.         // Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
  796.         if ( $hierarchical && $number && is_array( $terms ) ) {
  797.             if ( $offset >= count( $terms ) ) {
  798.                 $terms = array();
  799.             } else {
  800.                 $terms = array_slice( $terms, $offset, $number, true );
  801.             }
  802.         }
  803.  
  804.         wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
  805.  
  806.         if ( 'all' === $_fields || 'all_with_object_id' === $_fields ) {
  807.             $terms = array_map( 'get_term', $terms );
  808.         }
  809.  
  810.         $this->terms = $terms;
  811.         return $this->terms;
  812.     }
  813.  
  814.     /**
  815.      * Parse and sanitize 'orderby' keys passed to the term query.
  816.      *
  817.      * @since 4.6.0
  818.      *
  819.      * @global wpdb $wpdb WordPress database abstraction object.
  820.      *
  821.      * @param string $orderby_raw Alias for the field to order by.
  822.      * @return string|false Value to used in the ORDER clause. False otherwise.
  823.      */
  824.     protected function parse_orderby( $orderby_raw ) {
  825.         $_orderby = strtolower( $orderby_raw );
  826.         $maybe_orderby_meta = false;
  827.  
  828.         if ( in_array( $_orderby, array( 'term_id', 'name', 'slug', 'term_group' ), true ) ) {
  829.             $orderby = "t.$_orderby";
  830.         } elseif ( in_array( $_orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id', 'description' ), true ) ) {
  831.             $orderby = "tt.$_orderby";
  832.         } elseif ( 'term_order' === $_orderby ) {
  833.             $orderby = 'tr.term_order';
  834.         } elseif ( 'include' == $_orderby && ! empty( $this->query_vars['include'] ) ) {
  835.             $include = implode( ',', wp_parse_id_list( $this->query_vars['include'] ) );
  836.             $orderby = "FIELD( t.term_id, $include )";
  837.         } elseif ( 'slug__in' == $_orderby && ! empty( $this->query_vars['slug'] ) && is_array( $this->query_vars['slug'] ) ) {
  838.             $slugs = implode( "', '", array_map( 'sanitize_title_for_query', $this->query_vars['slug'] ) );
  839.             $orderby = "FIELD( t.slug, '" . $slugs . "')";
  840.         } elseif ( 'none' == $_orderby ) {
  841.             $orderby = '';
  842.         } elseif ( empty( $_orderby ) || 'id' == $_orderby || 'term_id' === $_orderby ) {
  843.             $orderby = 't.term_id';
  844.         } else {
  845.             $orderby = 't.name';
  846.  
  847.             // This may be a value of orderby related to meta.
  848.             $maybe_orderby_meta = true;
  849.         }
  850.  
  851.         /**
  852.          * Filters the ORDERBY clause of the terms query.
  853.          *
  854.          * @since 2.8.0
  855.          *
  856.          * @param string $orderby    `ORDERBY` clause of the terms query.
  857.          * @param array  $args       An array of terms query arguments.
  858.          * @param array  $taxonomies An array of taxonomies.
  859.          */
  860.         $orderby = apply_filters( 'get_terms_orderby', $orderby, $this->query_vars, $this->query_vars['taxonomy'] );
  861.  
  862.         // Run after the 'get_terms_orderby' filter for backward compatibility.
  863.         if ( $maybe_orderby_meta ) {
  864.             $maybe_orderby_meta = $this->parse_orderby_meta( $_orderby );
  865.             if ( $maybe_orderby_meta ) {
  866.                 $orderby = $maybe_orderby_meta;
  867.             }
  868.         }
  869.  
  870.         return $orderby;
  871.     }
  872.  
  873.     /**
  874.      * Generate the ORDER BY clause for an 'orderby' param that is potentially related to a meta query.
  875.      *
  876.      * @since 4.6.0
  877.      *
  878.      * @param string $orderby_raw Raw 'orderby' value passed to WP_Term_Query.
  879.      * @return string ORDER BY clause.
  880.      */
  881.     protected function parse_orderby_meta( $orderby_raw ) {
  882.         $orderby = '';
  883.  
  884.         // Tell the meta query to generate its SQL, so we have access to table aliases.
  885.         $this->meta_query->get_sql( 'term', 't', 'term_id' );
  886.         $meta_clauses = $this->meta_query->get_clauses();
  887.         if ( ! $meta_clauses || ! $orderby_raw ) {
  888.             return $orderby;
  889.         }
  890.  
  891.         $allowed_keys = array();
  892.         $primary_meta_key = null;
  893.         $primary_meta_query = reset( $meta_clauses );
  894.         if ( ! empty( $primary_meta_query['key'] ) ) {
  895.             $primary_meta_key = $primary_meta_query['key'];
  896.             $allowed_keys[] = $primary_meta_key;
  897.         }
  898.         $allowed_keys[] = 'meta_value';
  899.         $allowed_keys[] = 'meta_value_num';
  900.         $allowed_keys   = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
  901.  
  902.         if ( ! in_array( $orderby_raw, $allowed_keys, true ) ) {
  903.             return $orderby;
  904.         }
  905.  
  906.         switch( $orderby_raw ) {
  907.             case $primary_meta_key:
  908.             case 'meta_value':
  909.                 if ( ! empty( $primary_meta_query['type'] ) ) {
  910.                     $orderby = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
  911.                 } else {
  912.                     $orderby = "{$primary_meta_query['alias']}.meta_value";
  913.                 }
  914.                 break;
  915.  
  916.             case 'meta_value_num':
  917.                 $orderby = "{$primary_meta_query['alias']}.meta_value+0";
  918.                 break;
  919.  
  920.             default:
  921.                 if ( array_key_exists( $orderby_raw, $meta_clauses ) ) {
  922.                     // $orderby corresponds to a meta_query clause.
  923.                     $meta_clause = $meta_clauses[ $orderby_raw ];
  924.                     $orderby = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
  925.                 }
  926.                 break;
  927.         }
  928.  
  929.         return $orderby;
  930.     }
  931.  
  932.     /**
  933.      * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
  934.      *
  935.      * @since 4.6.0
  936.      *
  937.      * @param string $order The 'order' query variable.
  938.      * @return string The sanitized 'order' query variable.
  939.      */
  940.     protected function parse_order( $order ) {
  941.         if ( ! is_string( $order ) || empty( $order ) ) {
  942.             return 'DESC';
  943.         }
  944.  
  945.         if ( 'ASC' === strtoupper( $order ) ) {
  946.             return 'ASC';
  947.         } else {
  948.             return 'DESC';
  949.         }
  950.     }
  951.  
  952.     /**
  953.      * Used internally to generate a SQL string related to the 'search' parameter.
  954.      *
  955.      * @since 4.6.0
  956.      *
  957.      * @global wpdb $wpdb WordPress database abstraction object.
  958.      *
  959.      * @param string $string
  960.      * @return string
  961.      */
  962.     protected function get_search_sql( $string ) {
  963.         global $wpdb;
  964.  
  965.         $like = '%' . $wpdb->esc_like( $string ) . '%';
  966.  
  967.         return $wpdb->prepare( '((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
  968.     }
  969. }
  970.