[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> class-wp-term-query.php (source)

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


Generated: Sat Jul 4 01:00:03 2020 Cross-referenced by PHPXref 0.7.1