WordPress Version: 4.5
/**
* Retrieve the terms in a given taxonomy or list of taxonomies.
*
* You can fully inject any customizations to the query before it is sent, as
* well as control the output with a filter.
*
* The {@see 'get_terms'} filter will be called when the cache has the term and will
* pass the found term along with the array of $taxonomies and array of $args.
* This filter is also called before the array of terms is passed and will pass
* the array of terms, along with the $taxonomies and $args.
*
* The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
* the $args.
*
* The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
* along with the $args array.
*
* Prior to 4.5.0, the first parameter of `get_terms()` was a taxonomy or list of taxonomies:
*
* $terms = get_terms( 'post_tag', array(
* 'hide_empty' => false,
* ) );
*
* Since 4.5.0, taxonomies should be passed via the 'taxonomy' argument in the `$args` array:
*
* $terms = get_terms( array(
* 'taxonomy' => 'post_tag',
* 'hide_empty' => false,
* ) );
*
* @since 2.3.0
* @since 4.2.0 Introduced 'name' and 'childless' parameters.
* @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
* Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
* a list of WP_Term objects.
* @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
* Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata.
*
* @internal The `$deprecated` parameter is parsed for backward compatibility only.
*
* @global wpdb $wpdb WordPress database abstraction object.
* @global array $wp_filter
*
* @param array|string $args {
* Optional. Array or string of arguments to get terms.
*
* @type string|array $taxonomy Taxonomy name, or array of taxonomies, to which results should
* be limited.
* @type string $orderby Field(s) to order terms by. Accepts term fields ('name', 'slug',
* 'term_group', 'term_id', 'id', 'description'), 'count' for term
* taxonomy count, 'include' to match the 'order' of the $include param,
* 'meta_value', 'meta_value_num', the value of `$meta_key`, the array
* keys of `$meta_query`, or 'none' to omit the ORDER BY clause.
* Defaults to 'name'.
* @type string $order Whether to order terms in ascending or descending order.
* Accepts 'ASC' (ascending) or 'DESC' (descending).
* Default 'ASC'.
* @type bool|int $hide_empty Whether to hide terms not assigned to any posts. Accepts
* 1|true or 0|false. Default 1|true.
* @type array|string $include Array or comma/space-separated string of term ids to include.
* Default empty array.
* @type array|string $exclude Array or comma/space-separated string of term ids to exclude.
* If $include is non-empty, $exclude is ignored.
* Default empty array.
* @type array|string $exclude_tree Array or comma/space-separated string of term ids to exclude
* along with all of their descendant terms. If $include is
* non-empty, $exclude_tree is ignored. Default empty array.
* @type int|string $number Maximum number of terms to return. Accepts ''|0 (all) or any
* positive number. Default ''|0 (all).
* @type int $offset The number by which to offset the terms query. Default empty.
* @type string $fields Term fields to query for. Accepts 'all' (returns an array of complete
* term objects), 'ids' (returns an array of ids), 'id=>parent' (returns
* an associative array with ids as keys, parent term IDs as values),
* 'names' (returns an array of term names), 'count' (returns the number
* of matching terms), 'id=>name' (returns an associative array with ids
* as keys, term names as values), or 'id=>slug' (returns an associative
* array with ids as keys, term slugs as values). Default 'all'.
* @type string|array $name Optional. Name or array of names to return term(s) for. Default empty.
* @type string|array $slug Optional. Slug or array of slugs to return term(s) for. Default empty.
* @type bool $hierarchical Whether to include terms that have non-empty descendants (even
* if $hide_empty is set to true). Default true.
* @type string $search Search criteria to match terms. Will be SQL-formatted with
* wildcards before and after. Default empty.
* @type string $name__like Retrieve terms with criteria by which a term is LIKE $name__like.
* Default empty.
* @type string $description__like Retrieve terms where the description is LIKE $description__like.
* Default empty.
* @type bool $pad_counts Whether to pad the quantity of a term's children in the quantity
* of each term's "count" object variable. Default false.
* @type string $get Whether to return terms regardless of ancestry or whether the terms
* are empty. Accepts 'all' or empty (disabled). Default empty.
* @type int $child_of Term ID to retrieve child terms of. If multiple taxonomies
* are passed, $child_of is ignored. Default 0.
* @type int|string $parent Parent term ID to retrieve direct-child terms of. Default empty.
* @type bool $childless True to limit results to terms that have no children. This parameter
* has no effect on non-hierarchical taxonomies. Default false.
* @type string $cache_domain Unique cache key to be produced when this query is stored in an
* object cache. Default is 'core'.
* @type bool $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
* @type array $meta_query Meta query clauses to limit retrieved terms by.
* See `WP_Meta_Query`. Default empty.
* @type string $meta_key Limit terms to those matching a specific metadata key. Can be used in
* conjunction with `$meta_value`.
* @type string $meta_value Limit terms to those matching a specific metadata value. Usually used
* in conjunction with `$meta_key`.
* }
* @param array $deprecated Argument array, when using the legacy function parameter format. If present, this
* parameter will be interpreted as `$args`, and the first function parameter will
* be parsed as a taxonomy or array of taxonomies.
* @return array|int|WP_Error List of WP_Term instances and their children. Will return WP_Error, if any of $taxonomies
* do not exist.
*/
function get_terms($args = array(), $deprecated = '')
{
global $wpdb;
$defaults = array('taxonomy' => null, 'orderby' => 'name', 'order' => 'ASC', 'hide_empty' => true, 'include' => array(), 'exclude' => array(), 'exclude_tree' => array(), 'number' => '', 'offset' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'hierarchical' => true, 'search' => '', 'name__like' => '', 'description__like' => '', 'pad_counts' => false, 'get' => '', 'child_of' => 0, 'parent' => '', 'childless' => false, 'cache_domain' => 'core', 'update_term_meta_cache' => true, 'meta_query' => '');
/*
* Legacy argument format ($taxonomy, $args) takes precedence.
*
* We detect legacy argument format by checking if
* (a) a second non-empty parameter is passed, or
* (b) the first parameter shares no keys with the default array (ie, it's a list of taxonomies)
*/
$key_intersect = array_intersect_key($defaults, (array) $args);
$do_legacy_args = $deprecated || empty($key_intersect);
$taxonomies = null;
if ($do_legacy_args) {
$taxonomies = (array) $args;
$args = $deprecated;
} elseif (isset($args['taxonomy']) && null !== $args['taxonomy']) {
$taxonomies = (array) $args['taxonomy'];
unset($args['taxonomy']);
}
$empty_array = array();
if ($taxonomies) {
foreach ($taxonomies as $taxonomy) {
if (!taxonomy_exists($taxonomy)) {
return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
}
}
}
/**
* Filter the terms query default arguments.
*
* Use 'get_terms_args' to filter the passed arguments.
*
* @since 4.4.0
*
* @param array $defaults An array of default get_terms() arguments.
* @param array $taxonomies An array of taxonomies.
*/
$args = wp_parse_args($args, apply_filters('get_terms_defaults', $defaults, $taxonomies));
$args['number'] = absint($args['number']);
$args['offset'] = absint($args['offset']);
// Save queries by not crawling the tree in the case of multiple taxes or a flat tax.
$has_hierarchical_tax = false;
if ($taxonomies) {
foreach ($taxonomies as $_tax) {
if (is_taxonomy_hierarchical($_tax)) {
$has_hierarchical_tax = true;
}
}
}
if (!$has_hierarchical_tax) {
$args['hierarchical'] = false;
$args['pad_counts'] = false;
}
// 'parent' overrides 'child_of'.
if (0 < intval($args['parent'])) {
$args['child_of'] = false;
}
if ('all' == $args['get']) {
$args['childless'] = false;
$args['child_of'] = 0;
$args['hide_empty'] = 0;
$args['hierarchical'] = false;
$args['pad_counts'] = false;
}
/**
* Filter the terms query arguments.
*
* @since 3.1.0
*
* @param array $args An array of get_terms() arguments.
* @param array $taxonomies An array of taxonomies.
*/
$args = apply_filters('get_terms_args', $args, $taxonomies);
// Avoid the query if the queried parent/child_of term has no descendants.
$child_of = $args['child_of'];
$parent = $args['parent'];
if ($child_of) {
$_parent = $child_of;
} elseif ($parent) {
$_parent = $parent;
} else {
$_parent = false;
}
if ($_parent) {
$in_hierarchy = false;
foreach ($taxonomies as $_tax) {
$hierarchy = _get_term_hierarchy($_tax);
if (isset($hierarchy[$_parent])) {
$in_hierarchy = true;
}
}
if (!$in_hierarchy) {
return $empty_array;
}
}
$_orderby = strtolower($args['orderby']);
if ('count' == $_orderby) {
$orderby = 'tt.count';
} elseif ('name' == $_orderby) {
$orderby = 't.name';
} elseif ('slug' == $_orderby) {
$orderby = 't.slug';
} elseif ('include' == $_orderby && !empty($args['include'])) {
$include = implode(',', array_map('absint', $args['include']));
$orderby = "FIELD( t.term_id, {$include} )";
} elseif ('term_group' == $_orderby) {
$orderby = 't.term_group';
} elseif ('description' == $_orderby) {
$orderby = 'tt.description';
} elseif ('none' == $_orderby) {
$orderby = '';
} elseif (empty($_orderby) || 'id' == $_orderby || 'term_id' === $_orderby) {
$orderby = 't.term_id';
} else {
$orderby = 't.name';
}
/**
* Filter the ORDERBY clause of the terms query.
*
* @since 2.8.0
*
* @param string $orderby `ORDERBY` clause of the terms query.
* @param array $args An array of terms query arguments.
* @param array $taxonomies An array of taxonomies.
*/
$orderby = apply_filters('get_terms_orderby', $orderby, $args, $taxonomies);
$order = strtoupper($args['order']);
if (!empty($orderby)) {
$orderby = "ORDER BY {$orderby}";
} else {
$order = '';
}
if ('' !== $order && !in_array($order, array('ASC', 'DESC'))) {
$order = 'ASC';
}
$where_conditions = array();
if ($taxonomies) {
$where_conditions[] = "tt.taxonomy IN ('" . implode("', '", array_map('esc_sql', $taxonomies)) . "')";
}
$exclude = $args['exclude'];
$exclude_tree = $args['exclude_tree'];
$include = $args['include'];
$inclusions = '';
if (!empty($include)) {
$exclude = '';
$exclude_tree = '';
$inclusions = implode(',', wp_parse_id_list($include));
}
if (!empty($inclusions)) {
$where_conditions[] = 't.term_id IN ( ' . $inclusions . ' )';
}
$exclusions = array();
if (!empty($exclude_tree)) {
$exclude_tree = wp_parse_id_list($exclude_tree);
$excluded_children = $exclude_tree;
foreach ($exclude_tree as $extrunk) {
$excluded_children = array_merge($excluded_children, (array) get_terms($taxonomies[0], array('child_of' => intval($extrunk), 'fields' => 'ids', 'hide_empty' => 0)));
}
$exclusions = array_merge($excluded_children, $exclusions);
}
if (!empty($exclude)) {
$exclusions = array_merge(wp_parse_id_list($exclude), $exclusions);
}
// 'childless' terms are those without an entry in the flattened term hierarchy.
$childless = (bool) $args['childless'];
if ($childless) {
foreach ($taxonomies as $_tax) {
$term_hierarchy = _get_term_hierarchy($_tax);
$exclusions = array_merge(array_keys($term_hierarchy), $exclusions);
}
}
if (!empty($exclusions)) {
$exclusions = 't.term_id NOT IN (' . implode(',', array_map('intval', $exclusions)) . ')';
} else {
$exclusions = '';
}
/**
* Filter the terms to exclude from the terms query.
*
* @since 2.3.0
*
* @param string $exclusions `NOT IN` clause of the terms query.
* @param array $args An array of terms query arguments.
* @param array $taxonomies An array of taxonomies.
*/
$exclusions = apply_filters('list_terms_exclusions', $exclusions, $args, $taxonomies);
if (!empty($exclusions)) {
// Must do string manipulation here for backward compatibility with filter.
$where_conditions[] = preg_replace('/^\s*AND\s*/', '', $exclusions);
}
if (!empty($args['name'])) {
$names = (array) $args['name'];
foreach ($names as &$_name) {
// `sanitize_term_field()` returns slashed data.
$_name = stripslashes(sanitize_term_field('name', $_name, 0, reset($taxonomies), 'db'));
}
$where_conditions[] = "t.name IN ('" . implode("', '", array_map('esc_sql', $names)) . "')";
}
if (!empty($args['slug'])) {
if (is_array($args['slug'])) {
$slug = array_map('sanitize_title', $args['slug']);
$where_conditions[] = "t.slug IN ('" . implode("', '", $slug) . "')";
} else {
$slug = sanitize_title($args['slug']);
$where_conditions[] = "t.slug = '{$slug}'";
}
}
if (!empty($args['name__like'])) {
$where_conditions[] = $wpdb->prepare("t.name LIKE %s", '%' . $wpdb->esc_like($args['name__like']) . '%');
}
if (!empty($args['description__like'])) {
$where_conditions[] = $wpdb->prepare("tt.description LIKE %s", '%' . $wpdb->esc_like($args['description__like']) . '%');
}
if ('' !== $parent) {
$parent = (int) $parent;
$where_conditions[] = "tt.parent = '{$parent}'";
}
$hierarchical = $args['hierarchical'];
if ('count' == $args['fields']) {
$hierarchical = false;
}
if ($args['hide_empty'] && !$hierarchical) {
$where_conditions[] = 'tt.count > 0';
}
$number = $args['number'];
$offset = $args['offset'];
// Don't limit the query results when we have to descend the family tree.
if ($number && !$hierarchical && !$child_of && '' === $parent) {
if ($offset) {
$limits = 'LIMIT ' . $offset . ',' . $number;
} else {
$limits = 'LIMIT ' . $number;
}
} else {
$limits = '';
}
if (!empty($args['search'])) {
$like = '%' . $wpdb->esc_like($args['search']) . '%';
$where_conditions[] = $wpdb->prepare('((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like);
}
// Meta query support.
$join = '';
$distinct = '';
$mquery = new WP_Meta_Query();
$mquery->parse_query_vars($args);
$mq_sql = $mquery->get_sql('term', 't', 'term_id');
$meta_clauses = $mquery->get_clauses();
if (!empty($meta_clauses)) {
$join .= $mq_sql['join'];
$where_conditions[] = preg_replace('/^\s*AND\s*/', '', $mq_sql['where']);
$distinct .= "DISTINCT";
// 'orderby' support.
$allowed_keys = array();
$primary_meta_key = null;
$primary_meta_query = reset($meta_clauses);
if (!empty($primary_meta_query['key'])) {
$primary_meta_key = $primary_meta_query['key'];
$allowed_keys[] = $primary_meta_key;
}
$allowed_keys[] = 'meta_value';
$allowed_keys[] = 'meta_value_num';
$allowed_keys = array_merge($allowed_keys, array_keys($meta_clauses));
if (!empty($args['orderby']) && in_array($args['orderby'], $allowed_keys)) {
switch ($args['orderby']) {
case $primary_meta_key:
case 'meta_value':
if (!empty($primary_meta_query['type'])) {
$orderby = "ORDER BY CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
} else {
$orderby = "ORDER BY {$primary_meta_query['alias']}.meta_value";
}
break;
case 'meta_value_num':
$orderby = "ORDER BY {$primary_meta_query['alias']}.meta_value+0";
break;
default:
if (array_key_exists($args['orderby'], $meta_clauses)) {
// $orderby corresponds to a meta_query clause.
$meta_clause = $meta_clauses[$args['orderby']];
$orderby = "ORDER BY CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
}
break;
}
}
}
$selects = array();
switch ($args['fields']) {
case 'all':
$selects = array('t.*', 'tt.*');
break;
case 'ids':
case 'id=>parent':
$selects = array('t.term_id', 'tt.parent', 'tt.count', 'tt.taxonomy');
break;
case 'names':
$selects = array('t.term_id', 'tt.parent', 'tt.count', 't.name', 'tt.taxonomy');
break;
case 'count':
$orderby = '';
$order = '';
$selects = array('COUNT(*)');
break;
case 'id=>name':
$selects = array('t.term_id', 't.name', 'tt.count', 'tt.taxonomy');
break;
case 'id=>slug':
$selects = array('t.term_id', 't.slug', 'tt.count', 'tt.taxonomy');
break;
}
$_fields = $args['fields'];
/**
* Filter the fields to select in the terms query.
*
* Field lists modified using this filter will only modify the term fields returned
* by the function when the `$fields` parameter set to 'count' or 'all'. In all other
* cases, the term fields in the results array will be determined by the `$fields`
* parameter alone.
*
* Use of this filter can result in unpredictable behavior, and is not recommended.
*
* @since 2.8.0
*
* @param array $selects An array of fields to select for the terms query.
* @param array $args An array of term query arguments.
* @param array $taxonomies An array of taxonomies.
*/
$fields = implode(', ', apply_filters('get_terms_fields', $selects, $args, $taxonomies));
$join .= " INNER JOIN {$wpdb->term_taxonomy} AS tt ON t.term_id = tt.term_id";
$where = implode(' AND ', $where_conditions);
$pieces = array('fields', 'join', 'where', 'distinct', 'orderby', 'order', 'limits');
/**
* Filter the terms query SQL clauses.
*
* @since 3.1.0
*
* @param array $pieces Terms query SQL clauses.
* @param array $taxonomies An array of taxonomies.
* @param array $args An array of terms query arguments.
*/
$clauses = apply_filters('terms_clauses', compact($pieces), $taxonomies, $args);
$fields = isset($clauses['fields']) ? $clauses['fields'] : '';
$join = isset($clauses['join']) ? $clauses['join'] : '';
$where = isset($clauses['where']) ? $clauses['where'] : '';
$distinct = isset($clauses['distinct']) ? $clauses['distinct'] : '';
$orderby = isset($clauses['orderby']) ? $clauses['orderby'] : '';
$order = isset($clauses['order']) ? $clauses['order'] : '';
$limits = isset($clauses['limits']) ? $clauses['limits'] : '';
if ($where) {
$where = "WHERE {$where}";
}
$query = "SELECT {$distinct} {$fields} FROM {$wpdb->terms} AS t {$join} {$where} {$orderby} {$order} {$limits}";
// $args can be anything. Only use the args defined in defaults to compute the key.
$key = md5(serialize(wp_array_slice_assoc($args, array_keys($defaults))) . serialize($taxonomies) . $query);
$last_changed = wp_cache_get('last_changed', 'terms');
if (!$last_changed) {
$last_changed = microtime();
wp_cache_set('last_changed', $last_changed, 'terms');
}
$cache_key = "get_terms:{$key}:{$last_changed}";
$cache = wp_cache_get($cache_key, 'terms');
if (false !== $cache) {
if ('all' === $_fields) {
$cache = array_map('get_term', $cache);
}
/**
* Filter the given taxonomy's terms cache.
*
* @since 2.3.0
*
* @param array $cache Cached array of terms for the given taxonomy.
* @param array $taxonomies An array of taxonomies.
* @param array $args An array of get_terms() arguments.
*/
return apply_filters('get_terms', $cache, $taxonomies, $args);
}
if ('count' == $_fields) {
return $wpdb->get_var($query);
}
$terms = $wpdb->get_results($query);
if ('all' == $_fields) {
update_term_cache($terms);
}
// Prime termmeta cache.
if ($args['update_term_meta_cache']) {
$term_ids = wp_list_pluck($terms, 'term_id');
update_termmeta_cache($term_ids);
}
if (empty($terms)) {
wp_cache_add($cache_key, array(), 'terms', DAY_IN_SECONDS);
/** This filter is documented in wp-includes/taxonomy.php */
return apply_filters('get_terms', array(), $taxonomies, $args);
}
if ($child_of) {
foreach ($taxonomies as $_tax) {
$children = _get_term_hierarchy($_tax);
if (!empty($children)) {
$terms = _get_term_children($child_of, $terms, $_tax);
}
}
}
// Update term counts to include children.
if ($args['pad_counts'] && 'all' == $_fields) {
foreach ($taxonomies as $_tax) {
_pad_term_counts($terms, $_tax);
}
}
// Make sure we show empty categories that have children.
if ($hierarchical && $args['hide_empty'] && is_array($terms)) {
foreach ($terms as $k => $term) {
if (!$term->count) {
$children = get_term_children($term->term_id, $term->taxonomy);
if (is_array($children)) {
foreach ($children as $child_id) {
$child = get_term($child_id, $term->taxonomy);
if ($child->count) {
continue 2;
}
}
}
// It really is empty.
unset($terms[$k]);
}
}
}
$_terms = array();
if ('id=>parent' == $_fields) {
foreach ($terms as $term) {
$_terms[$term->term_id] = $term->parent;
}
} elseif ('ids' == $_fields) {
foreach ($terms as $term) {
$_terms[] = $term->term_id;
}
} elseif ('names' == $_fields) {
foreach ($terms as $term) {
$_terms[] = $term->name;
}
} elseif ('id=>name' == $_fields) {
foreach ($terms as $term) {
$_terms[$term->term_id] = $term->name;
}
} elseif ('id=>slug' == $_fields) {
foreach ($terms as $term) {
$_terms[$term->term_id] = $term->slug;
}
}
if (!empty($_terms)) {
$terms = $_terms;
}
// Hierarchical queries are not limited, so 'offset' and 'number' must be handled now.
if ($hierarchical && $number && is_array($terms)) {
if ($offset >= count($terms)) {
$terms = array();
} else {
$terms = array_slice($terms, $offset, $number, true);
}
}
wp_cache_add($cache_key, $terms, 'terms', DAY_IN_SECONDS);
if ('all' === $_fields) {
$terms = array_map('get_term', $terms);
}
/** This filter is documented in wp-includes/taxonomy.php */
return apply_filters('get_terms', $terms, $taxonomies, $args);
}