+ if ( !is_object($term) )
+ return '';
+
+ if ( !isset($term->$field) )
+ return '';
+
+ return sanitize_term_field($field, $term->$field, $term->term_id, $taxonomy, $context);
+}
+
+/**
+ * Sanitizes Term for editing.
+ *
+ * Return value is sanitize_term() and usage is for sanitizing the term for
+ * editing. Function is for contextual and simplicity.
+ *
+ * @since 2.3.0
+ *
+ * @param int|object $id Term ID or object.
+ * @param string $taxonomy Taxonomy name.
+ * @return string|int|null|WP_Error Will return empty string if $term is not an object.
+ */
+function get_term_to_edit( $id, $taxonomy ) {
+ $term = get_term( $id, $taxonomy );
+
+ if ( is_wp_error($term) )
+ return $term;
+
+ if ( !is_object($term) )
+ return '';
+
+ return sanitize_term($term, $taxonomy, 'edit');
+}
+
+/**
+ * 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.
+ *
+ * @since 2.3.0
+ * @since 4.2.0 Introduced 'name' and 'childless' parameters.
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ * @global array $wp_filter
+ *
+ * @param string|array $taxonomies Taxonomy name or list of Taxonomy names.
+ * @param array|string $args {
+ * Optional. Array or string of arguments to get terms.
+ *
+ * @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,
+ * or 'none' to skip ORDER BY. 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
+ * term objects), 'ids' or 'names' (returns an array of integers
+ * or strings, respectively. 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'.
+ * }
+ * @return array|int|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies
+ * do not exist.
+ */
+function get_terms( $taxonomies, $args = '' ) {
+ global $wpdb;
+ $empty_array = array();
+
+ $single_taxonomy = ! is_array( $taxonomies ) || 1 === count( $taxonomies );
+ if ( ! is_array( $taxonomies ) ) {
+ $taxonomies = array( $taxonomies );
+ }
+
+ foreach ( $taxonomies as $taxonomy ) {
+ if ( ! taxonomy_exists($taxonomy) ) {
+ return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
+ }
+ }
+
+ $defaults = array('orderby' => 'name', 'order' => 'ASC',
+ 'hide_empty' => true, 'exclude' => array(), 'exclude_tree' => array(), 'include' => array(),
+ 'number' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'parent' => '', 'childless' => false,
+ 'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', 'description__like' => '',
+ 'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core' );
+ $args = wp_parse_args( $args, $defaults );
+ $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;
+ 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_term() 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;
+ }
+ }
+
+ // $args can be whatever, only use the args defined in defaults to compute the key.
+ $filter_key = ( has_filter('list_terms_exclusions') ) ? serialize($GLOBALS['wp_filter']['list_terms_exclusions']) : '';
+ $key = md5( serialize( wp_array_slice_assoc( $args, array_keys( $defaults ) ) ) . serialize( $taxonomies ) . $filter_key );
+ $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 ) {
+
+ /**
+ * 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 );
+ }
+
+ $_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 ) {
+ $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 = "tt.taxonomy IN ('" . implode("', '", $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 ) ) {
+ $inclusions = ' AND t.term_id IN ( ' . $inclusions . ' )';
+ $where .= $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 = ' AND 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 ) ) {
+ $where .= $exclusions;
+ }
+
+ if ( ! empty( $args['name'] ) ) {
+ $names = (array) $args['name'];
+ foreach ( $names as &$_name ) {
+ $_name = sanitize_term_field( 'name', $_name, 0, reset( $taxonomies ), 'db' );
+ }
+
+ $where .= " AND 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 .= " AND t.slug IN ('" . implode( "', '", $slug ) . "')";
+ } else {
+ $slug = sanitize_title( $args['slug'] );
+ $where .= " AND t.slug = '$slug'";
+ }
+ }
+
+ if ( ! empty( $args['name__like'] ) ) {
+ $where .= $wpdb->prepare( " AND t.name LIKE %s", '%' . $wpdb->esc_like( $args['name__like'] ) . '%' );
+ }
+
+ if ( ! empty( $args['description__like'] ) ) {
+ $where .= $wpdb->prepare( " AND tt.description LIKE %s", '%' . $wpdb->esc_like( $args['description__like'] ) . '%' );
+ }
+
+ if ( '' !== $parent ) {
+ $parent = (int) $parent;
+ $where .= " AND tt.parent = '$parent'";
+ }
+
+ $hierarchical = $args['hierarchical'];
+ if ( 'count' == $args['fields'] ) {
+ $hierarchical = false;
+ }
+ if ( $args['hide_empty'] && !$hierarchical ) {
+ $where .= ' AND 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 .= $wpdb->prepare( ' AND ((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
+ }
+
+ $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";
+
+ $pieces = array( 'fields', 'join', 'where', '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' ] : '';
+ $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
+ $order = isset( $clauses[ 'order' ] ) ? $clauses[ 'order' ] : '';
+ $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
+
+ $query = "SELECT $fields FROM $wpdb->terms AS t $join WHERE $where $orderby $order $limits";
+
+ if ( 'count' == $_fields ) {
+ return $wpdb->get_var( $query );
+ }
+
+ $terms = $wpdb->get_results($query);
+ if ( 'all' == $_fields ) {
+ update_term_cache( $terms );
+ }
+
+ 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;
+ }
+
+ if ( $number && is_array( $terms ) && count( $terms ) > $number ) {
+ $terms = array_slice( $terms, $offset, $number );
+ }
+
+ wp_cache_add( $cache_key, $terms, 'terms', DAY_IN_SECONDS );
+
+ /** This filter is documented in wp-includes/taxonomy */
+ return apply_filters( 'get_terms', $terms, $taxonomies, $args );
+}
+
+/**
+ * Check if Term exists.
+ *
+ * Formerly is_term(), introduced in 2.3.0.
+ *
+ * @since 3.0.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param int|string $term The term to check
+ * @param string $taxonomy The taxonomy name to use
+ * @param int $parent Optional. ID of parent term under which to confine the exists search.
+ * @return mixed Returns null if the term does not exist. Returns the term ID
+ * if no taxonomy is specified and the term ID exists. Returns
+ * an array of the term ID and the term taxonomy ID the taxonomy
+ * is specified and the pairing exists.
+ */
+function term_exists( $term, $taxonomy = '', $parent = null ) {
+ global $wpdb;
+
+ $select = "SELECT term_id FROM $wpdb->terms as t WHERE ";
+ $tax_select = "SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE ";
+
+ if ( is_int($term) ) {
+ if ( 0 == $term )
+ return 0;
+ $where = 't.term_id = %d';
+ if ( !empty($taxonomy) )
+ return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . " AND tt.taxonomy = %s", $term, $taxonomy ), ARRAY_A );
+ else
+ return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
+ }
+
+ $term = trim( wp_unslash( $term ) );
+ $slug = sanitize_title( $term );
+
+ $where = 't.slug = %s';
+ $else_where = 't.name = %s';
+ $where_fields = array($slug);
+ $else_where_fields = array($term);
+ $orderby = 'ORDER BY t.term_id ASC';
+ $limit = 'LIMIT 1';
+ if ( !empty($taxonomy) ) {
+ if ( is_numeric( $parent ) ) {
+ $parent = (int) $parent;
+ $where_fields[] = $parent;
+ $else_where_fields[] = $parent;
+ $where .= ' AND tt.parent = %d';
+ $else_where .= ' AND tt.parent = %d';
+ }
+
+ $where_fields[] = $taxonomy;
+ $else_where_fields[] = $taxonomy;
+
+ if ( $result = $wpdb->get_row( $wpdb->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $where AND tt.taxonomy = %s $orderby $limit", $where_fields), ARRAY_A) )
+ return $result;
+
+ return $wpdb->get_row( $wpdb->prepare("SELECT tt.term_id, tt.term_taxonomy_id FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy as tt ON tt.term_id = t.term_id WHERE $else_where AND tt.taxonomy = %s $orderby $limit", $else_where_fields), ARRAY_A);
+ }
+
+ if ( $result = $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields) ) )
+ return $result;
+
+ return $wpdb->get_var( $wpdb->prepare("SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields) );
+}
+
+/**
+ * Check if a term is an ancestor of another term.
+ *
+ * You can use either an id or the term object for both parameters.
+ *
+ * @since 3.4.0
+ *
+ * @param int|object $term1 ID or object to check if this is the parent term.
+ * @param int|object $term2 The child term.
+ * @param string $taxonomy Taxonomy name that $term1 and `$term2` belong to.
+ * @return bool Whether `$term2` is a child of `$term1`.
+ */
+function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
+ if ( ! isset( $term1->term_id ) )
+ $term1 = get_term( $term1, $taxonomy );
+ if ( ! isset( $term2->parent ) )
+ $term2 = get_term( $term2, $taxonomy );
+
+ if ( empty( $term1->term_id ) || empty( $term2->parent ) )
+ return false;
+ if ( $term2->parent == $term1->term_id )
+ return true;
+
+ return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
+}
+
+/**
+ * Sanitize Term all fields.
+ *
+ * Relies on sanitize_term_field() to sanitize the term. The difference is that
+ * this function will sanitize <strong>all</strong> fields. The context is based
+ * on sanitize_term_field().
+ *
+ * The $term is expected to be either an array or an object.
+ *
+ * @since 2.3.0
+ *
+ * @param array|object $term The term to check.
+ * @param string $taxonomy The taxonomy name to use.
+ * @param string $context Optional. Context in which to sanitize the term. Accepts 'edit', 'db',
+ * 'display', 'attribute', or 'js'. Default 'display'.
+ * @return array|object Term with all fields sanitized.
+ */
+function sanitize_term($term, $taxonomy, $context = 'display') {
+ $fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
+
+ $do_object = is_object( $term );
+
+ $term_id = $do_object ? $term->term_id : (isset($term['term_id']) ? $term['term_id'] : 0);
+
+ foreach ( (array) $fields as $field ) {
+ if ( $do_object ) {
+ if ( isset($term->$field) )
+ $term->$field = sanitize_term_field($field, $term->$field, $term_id, $taxonomy, $context);
+ } else {
+ if ( isset($term[$field]) )
+ $term[$field] = sanitize_term_field($field, $term[$field], $term_id, $taxonomy, $context);
+ }
+ }
+
+ if ( $do_object )
+ $term->filter = $context;
+ else
+ $term['filter'] = $context;
+
+ return $term;
+}
+
+/**
+ * Cleanse the field value in the term based on the context.
+ *
+ * Passing a term field value through the function should be assumed to have
+ * cleansed the value for whatever context the term field is going to be used.
+ *
+ * If no context or an unsupported context is given, then default filters will
+ * be applied.
+ *
+ * There are enough filters for each context to support a custom filtering
+ * without creating your own filter function. Simply create a function that
+ * hooks into the filter you need.
+ *
+ * @since 2.3.0
+ *
+ * @param string $field Term field to sanitize.
+ * @param string $value Search for this term value.
+ * @param int $term_id Term ID.
+ * @param string $taxonomy Taxonomy Name.
+ * @param string $context Context in which to sanitize the term field. Accepts 'edit', 'db', 'display',
+ * 'attribute', or 'js'.
+ * @return mixed Sanitized field.
+ */
+function sanitize_term_field($field, $value, $term_id, $taxonomy, $context) {
+ $int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
+ if ( in_array( $field, $int_fields ) ) {
+ $value = (int) $value;
+ if ( $value < 0 )
+ $value = 0;
+ }
+
+ if ( 'raw' == $context )
+ return $value;
+
+ if ( 'edit' == $context ) {
+
+ /**
+ * Filter a term field to edit before it is sanitized.
+ *
+ * The dynamic portion of the filter name, `$field`, refers to the term field.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the term field.
+ * @param int $term_id Term ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ $value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
+
+ /**
+ * Filter the taxonomy field to edit before it is sanitized.
+ *
+ * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
+ * to the taxonomy slug and taxonomy field, respectively.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the taxonomy field to edit.
+ * @param int $term_id Term ID.
+ */
+ $value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
+
+ if ( 'description' == $field )
+ $value = esc_html($value); // textarea_escaped
+ else
+ $value = esc_attr($value);
+ } elseif ( 'db' == $context ) {
+
+ /**
+ * Filter a term field value before it is sanitized.
+ *
+ * The dynamic portion of the filter name, `$field`, refers to the term field.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the term field.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ $value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
+
+ /**
+ * Filter a taxonomy field before it is sanitized.
+ *
+ * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
+ * to the taxonomy slug and field name, respectively.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the taxonomy field.
+ */
+ $value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
+
+ // Back compat filters
+ if ( 'slug' == $field ) {
+ /**
+ * Filter the category nicename before it is sanitized.
+ *
+ * Use the pre_{$taxonomy}_{$field} hook instead.
+ *
+ * @since 2.0.3
+ *
+ * @param string $value The category nicename.
+ */
+ $value = apply_filters( 'pre_category_nicename', $value );
+ }
+
+ } elseif ( 'rss' == $context ) {
+
+ /**
+ * Filter the term field for use in RSS.
+ *
+ * The dynamic portion of the filter name, `$field`, refers to the term field.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the term field.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ $value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
+
+ /**
+ * Filter the taxonomy field for use in RSS.
+ *
+ * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
+ * to the taxonomy slug and field name, respectively.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the taxonomy field.
+ */
+ $value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
+ } else {
+ // Use display filters by default.
+
+ /**
+ * Filter the term field sanitized for display.
+ *
+ * The dynamic portion of the filter name, `$field`, refers to the term field name.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the term field.
+ * @param int $term_id Term ID.
+ * @param string $taxonomy Taxonomy slug.
+ * @param string $context Context to retrieve the term field value.
+ */
+ $value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
+
+ /**
+ * Filter the taxonomy field sanitized for display.
+ *
+ * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
+ * to the taxonomy slug and taxonomy field, respectively.
+ *
+ * @since 2.3.0
+ *
+ * @param mixed $value Value of the taxonomy field.
+ * @param int $term_id Term ID.
+ * @param string $context Context to retrieve the taxonomy field value.
+ */
+ $value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
+ }
+
+ if ( 'attribute' == $context ) {
+ $value = esc_attr($value);
+ } elseif ( 'js' == $context ) {
+ $value = esc_js($value);
+ }
+ return $value;
+}
+
+/**
+ * Count how many terms are in Taxonomy.
+ *
+ * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
+ *
+ * @todo Document $args as a hash notation.
+ *
+ * @since 2.3.0
+ *
+ * @param string $taxonomy Taxonomy name
+ * @param array|string $args Overwrite defaults. See get_terms()
+ * @return array|int|WP_Error How many terms are in $taxonomy. WP_Error if $taxonomy does not exist.
+ */
+function wp_count_terms( $taxonomy, $args = array() ) {
+ $defaults = array('hide_empty' => false);
+ $args = wp_parse_args($args, $defaults);
+
+ // backwards compatibility
+ if ( isset($args['ignore_empty']) ) {
+ $args['hide_empty'] = $args['ignore_empty'];
+ unset($args['ignore_empty']);
+ }
+
+ $args['fields'] = 'count';
+
+ return get_terms($taxonomy, $args);
+}
+
+/**
+ * Will unlink the object from the taxonomy or taxonomies.
+ *
+ * Will remove all relationships between the object and any terms in
+ * a particular taxonomy or taxonomies. Does not remove the term or
+ * taxonomy itself.
+ *
+ * @since 2.3.0
+ *
+ * @param int $object_id The term Object Id that refers to the term.
+ * @param string|array $taxonomies List of Taxonomy Names or single Taxonomy name.
+ */
+function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
+ $object_id = (int) $object_id;
+
+ if ( !is_array($taxonomies) )
+ $taxonomies = array($taxonomies);
+
+ foreach ( (array) $taxonomies as $taxonomy ) {
+ $term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
+ $term_ids = array_map( 'intval', $term_ids );
+ wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
+ }
+}
+
+/**
+ * Removes a term from the database.
+ *
+ * If the term is a parent of other terms, then the children will be updated to
+ * that term's parent.
+ *
+ * The `$args` 'default' will only override the terms found, if there is only one
+ * term found. Any other and the found terms are used.
+ *
+ * The $args 'force_default' will force the term supplied as default to be
+ * assigned even if the object was not going to be termless
+ *
+ * @todo Document $args as a hash notation.
+ *
+ * @since 2.3.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param int $term Term ID.
+ * @param string $taxonomy Taxonomy Name.
+ * @param array|string $args Optional. Change 'default' term id and override found term ids.
+ * @return bool|int|WP_Error Returns false if not term; true if completes delete action.
+ */
+function wp_delete_term( $term, $taxonomy, $args = array() ) {
+ global $wpdb;
+
+ $term = (int) $term;
+
+ if ( ! $ids = term_exists($term, $taxonomy) )
+ return false;
+ if ( is_wp_error( $ids ) )
+ return $ids;
+
+ $tt_id = $ids['term_taxonomy_id'];
+
+ $defaults = array();
+
+ if ( 'category' == $taxonomy ) {
+ $defaults['default'] = get_option( 'default_category' );
+ if ( $defaults['default'] == $term )
+ return 0; // Don't delete the default category
+ }
+
+ $args = wp_parse_args($args, $defaults);
+
+ if ( isset( $args['default'] ) ) {
+ $default = (int) $args['default'];
+ if ( ! term_exists( $default, $taxonomy ) ) {
+ unset( $default );
+ }
+ }
+
+ if ( isset( $args['force_default'] ) ) {
+ $force_default = $args['force_default'];
+ }
+
+ /**
+ * Fires when deleting a term, before any modifications are made to posts or terms.
+ *
+ * @since 4.1.0
+ *
+ * @param int $term Term ID.
+ * @param string $taxonomy Taxonomy Name.
+ */
+ do_action( 'pre_delete_term', $term, $taxonomy );
+
+ // Update children to point to new parent
+ if ( is_taxonomy_hierarchical($taxonomy) ) {
+ $term_obj = get_term($term, $taxonomy);
+ if ( is_wp_error( $term_obj ) )
+ return $term_obj;
+ $parent = $term_obj->parent;
+
+ $edit_ids = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int)$term_obj->term_id );
+ $edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
+
+ /**
+ * Fires immediately before a term to delete's children are reassigned a parent.
+ *
+ * @since 2.9.0
+ *
+ * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
+ */
+ do_action( 'edit_term_taxonomies', $edit_tt_ids );
+
+ $wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id) + compact( 'taxonomy' ) );
+
+ // Clean the cache for all child terms.
+ $edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
+ clean_term_cache( $edit_term_ids, $taxonomy );
+
+ /**
+ * Fires immediately after a term to delete's children are reassigned a parent.
+ *
+ * @since 2.9.0
+ *
+ * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
+ */
+ do_action( 'edited_term_taxonomies', $edit_tt_ids );
+ }
+
+ $objects = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
+
+ foreach ( (array) $objects as $object ) {
+ $terms = wp_get_object_terms($object, $taxonomy, array('fields' => 'ids', 'orderby' => 'none'));
+ if ( 1 == count($terms) && isset($default) ) {
+ $terms = array($default);
+ } else {
+ $terms = array_diff($terms, array($term));
+ if (isset($default) && isset($force_default) && $force_default)
+ $terms = array_merge($terms, array($default));
+ }
+ $terms = array_map('intval', $terms);
+ wp_set_object_terms($object, $terms, $taxonomy);
+ }
+
+ // Clean the relationship caches for all object types using this term.
+ $tax_object = get_taxonomy( $taxonomy );
+ foreach ( $tax_object->object_type as $object_type )
+ clean_object_term_cache( $objects, $object_type );
+
+ // Get the object before deletion so we can pass to actions below
+ $deleted_term = get_term( $term, $taxonomy );
+
+ /**
+ * Fires immediately before a term taxonomy ID is deleted.
+ *
+ * @since 2.9.0
+ *
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( 'delete_term_taxonomy', $tt_id );
+ $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
+
+ /**
+ * Fires immediately after a term taxonomy ID is deleted.
+ *
+ * @since 2.9.0
+ *
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( 'deleted_term_taxonomy', $tt_id );
+
+ // Delete the term if no taxonomies use it.
+ if ( !$wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term) ) )
+ $wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
+
+ clean_term_cache($term, $taxonomy);
+
+ /**
+ * Fires after a term is deleted from the database and the cache is cleaned.
+ *
+ * @since 2.5.0
+ *
+ * @param int $term Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
+ * by the parent function. WP_Error otherwise.
+ */
+ do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term );
+
+ /**
+ * Fires after a term in a specific taxonomy is deleted.
+ *
+ * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
+ * taxonomy the term belonged to.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ * @param mixed $deleted_term Copy of the already-deleted term, in the form specified
+ * by the parent function. WP_Error otherwise.
+ */
+ do_action( "delete_$taxonomy", $term, $tt_id, $deleted_term );
+
+ return true;
+}
+
+/**
+ * Deletes one existing category.
+ *
+ * @since 2.0.0
+ *
+ * @param int $cat_ID
+ * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
+ * Zero on attempted deletion of default Category; WP_Error object is also a possibility.
+ */
+function wp_delete_category( $cat_ID ) {
+ return wp_delete_term( $cat_ID, 'category' );
+}
+
+/**
+ * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
+ *
+ * @since 2.3.0
+ * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
+ * Introduced `$parent` argument.
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param int|array $object_ids The ID(s) of the object(s) to retrieve.
+ * @param string|array $taxonomies The taxonomies to retrieve terms from.
+ * @param array|string $args {
+ * Array of arguments.
+ * @type string $orderby Field by which results should be sorted. Accepts 'name', 'count', 'slug', 'term_group',
+ * 'term_order', 'taxonomy', 'parent', or 'term_taxonomy_id'. Default 'name'.
+ * @type string $order Sort order. Accepts 'ASC' or 'DESC'. Default 'ASC'.
+ * @type string $fields Fields to return for matched terms. Accepts 'all', 'ids', 'names', and
+ * 'all_with_object_id'. Note that 'all' or 'all_with_object_id' will result in an array of
+ * term objects being returned, 'ids' will return an array of integers, and 'names' an array
+ * of strings.
+ * @type int $parent Optional. Limit results to the direct children of a given term ID.
+ * }
+ * @return array|WP_Error The requested term data or empty array if no terms found.
+ * WP_Error if any of the $taxonomies don't exist.
+ */
+function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
+ global $wpdb;
+
+ if ( empty( $object_ids ) || empty( $taxonomies ) )
+ return array();
+
+ if ( !is_array($taxonomies) )
+ $taxonomies = array($taxonomies);
+
+ foreach ( $taxonomies as $taxonomy ) {
+ if ( ! taxonomy_exists($taxonomy) )
+ return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
+ }
+
+ if ( !is_array($object_ids) )
+ $object_ids = array($object_ids);
+ $object_ids = array_map('intval', $object_ids);
+
+ $defaults = array(
+ 'orderby' => 'name',
+ 'order' => 'ASC',
+ 'fields' => 'all',
+ 'parent' => '',
+ );
+ $args = wp_parse_args( $args, $defaults );
+
+ $terms = array();
+ if ( count($taxonomies) > 1 ) {
+ foreach ( $taxonomies as $index => $taxonomy ) {
+ $t = get_taxonomy($taxonomy);
+ if ( isset($t->args) && is_array($t->args) && $args != array_merge($args, $t->args) ) {
+ unset($taxonomies[$index]);
+ $terms = array_merge($terms, wp_get_object_terms($object_ids, $taxonomy, array_merge($args, $t->args)));
+ }
+ }
+ } else {
+ $t = get_taxonomy($taxonomies[0]);
+ if ( isset($t->args) && is_array($t->args) )
+ $args = array_merge($args, $t->args);
+ }
+
+ $orderby = $args['orderby'];
+ $order = $args['order'];
+ $fields = $args['fields'];
+
+ if ( in_array( $orderby, array( 'term_id', 'name', 'slug', 'term_group' ) ) ) {
+ $orderby = "t.$orderby";
+ } elseif ( in_array( $orderby, array( 'count', 'parent', 'taxonomy', 'term_taxonomy_id' ) ) ) {
+ $orderby = "tt.$orderby";
+ } elseif ( 'term_order' === $orderby ) {
+ $orderby = 'tr.term_order';
+ } elseif ( 'none' === $orderby ) {
+ $orderby = '';
+ $order = '';
+ } else {
+ $orderby = 't.term_id';
+ }
+
+ // tt_ids queries can only be none or tr.term_taxonomy_id
+ if ( ('tt_ids' == $fields) && !empty($orderby) )
+ $orderby = 'tr.term_taxonomy_id';
+
+ if ( !empty($orderby) )
+ $orderby = "ORDER BY $orderby";
+
+ $order = strtoupper( $order );
+ if ( '' !== $order && ! in_array( $order, array( 'ASC', 'DESC' ) ) )
+ $order = 'ASC';
+
+ $taxonomy_array = $taxonomies;
+ $object_id_array = $object_ids;
+ $taxonomies = "'" . implode("', '", $taxonomies) . "'";
+ $object_ids = implode(', ', $object_ids);
+
+ $select_this = '';
+ if ( 'all' == $fields ) {
+ $select_this = 't.*, tt.*';
+ } elseif ( 'ids' == $fields ) {
+ $select_this = 't.term_id';
+ } elseif ( 'names' == $fields ) {
+ $select_this = 't.name';
+ } elseif ( 'slugs' == $fields ) {
+ $select_this = 't.slug';
+ } elseif ( 'all_with_object_id' == $fields ) {
+ $select_this = 't.*, tt.*, tr.object_id';
+ }
+
+ $where = array(
+ "tt.taxonomy IN ($taxonomies)",
+ "tr.object_id IN ($object_ids)",
+ );
+
+ if ( '' !== $args['parent'] ) {
+ $where[] = $wpdb->prepare( 'tt.parent = %d', $args['parent'] );
+ }
+
+ $where = implode( ' AND ', $where );
+
+ $query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE $where $orderby $order";
+
+ $objects = false;
+ if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
+ $_terms = $wpdb->get_results( $query );
+ foreach ( $_terms as $key => $term ) {
+ $_terms[$key] = sanitize_term( $term, $taxonomy, 'raw' );
+ }
+ $terms = array_merge( $terms, $_terms );
+ update_term_cache( $terms );
+ $objects = true;
+ } elseif ( 'ids' == $fields || 'names' == $fields || 'slugs' == $fields ) {
+ $_terms = $wpdb->get_col( $query );
+ $_field = ( 'ids' == $fields ) ? 'term_id' : 'name';
+ foreach ( $_terms as $key => $term ) {
+ $_terms[$key] = sanitize_term_field( $_field, $term, $term, $taxonomy, 'raw' );
+ }
+ $terms = array_merge( $terms, $_terms );
+ } elseif ( 'tt_ids' == $fields ) {
+ $terms = $wpdb->get_col("SELECT tr.term_taxonomy_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tr.object_id IN ($object_ids) AND tt.taxonomy IN ($taxonomies) $orderby $order");
+ foreach ( $terms as $key => $tt_id ) {
+ $terms[$key] = sanitize_term_field( 'term_taxonomy_id', $tt_id, 0, $taxonomy, 'raw' ); // 0 should be the term id, however is not needed when using raw context.
+ }
+ }
+
+ if ( ! $terms ) {
+ $terms = array();
+ } elseif ( $objects && 'all_with_object_id' !== $fields ) {
+ $_tt_ids = array();
+ $_terms = array();
+ foreach ( $terms as $term ) {
+ if ( in_array( $term->term_taxonomy_id, $_tt_ids ) ) {
+ continue;
+ }
+
+ $_tt_ids[] = $term->term_taxonomy_id;
+ $_terms[] = $term;
+ }
+ $terms = $_terms;
+ } elseif ( ! $objects ) {
+ $terms = array_values( array_unique( $terms ) );
+ }
+
+ /**
+ * Filter the terms for a given object or objects.
+ *
+ * @since 4.2.0
+ *
+ * @param array $terms An array of terms for the given object or objects.
+ * @param array $object_id_array Array of object IDs for which `$terms` were retrieved.
+ * @param array $taxonomy_array Array of taxonomies from which `$terms` were retrieved.
+ * @param array $args An array of arguments for retrieving terms for the given
+ * object(s). See wp_get_object_terms() for details.
+ */
+ $terms = apply_filters( 'get_object_terms', $terms, $object_id_array, $taxonomy_array, $args );
+
+ /**
+ * Filter the terms for a given object or objects.
+ *
+ * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
+ * {@see 'get_object_terms'} filter is recommended as an alternative.
+ *
+ * @since 2.8.0
+ *
+ * @param array $terms An array of terms for the given object or objects.
+ * @param int|array $object_ids Object ID or array of IDs.
+ * @param string $taxonomies SQL-formatted (comma-separated and quoted) list of taxonomy names.
+ * @param array $args An array of arguments for retrieving terms for the given object(s).
+ * See {@see wp_get_object_terms()} for details.
+ */
+ return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
+}
+
+/**
+ * Add a new term to the database.
+ *
+ * A non-existent term is inserted in the following sequence:
+ * 1. The term is added to the term table, then related to the taxonomy.
+ * 2. If everything is correct, several actions are fired.
+ * 3. The 'term_id_filter' is evaluated.
+ * 4. The term cache is cleaned.
+ * 5. Several more actions are fired.
+ * 6. An array is returned containing the term_id and term_taxonomy_id.
+ *
+ * If the 'slug' argument is not empty, then it is checked to see if the term
+ * is invalid. If it is not a valid, existing term, it is added and the term_id
+ * is given.
+ *
+ * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
+ * the term is inserted and the term_id will be given.
+
+ * Error handling:
+ * If $taxonomy does not exist or $term is empty,
+ * a WP_Error object will be returned.
+ *
+ * If the term already exists on the same hierarchical level,
+ * or the term slug and name are not unique, a WP_Error object will be returned.
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+
+ * @since 2.3.0
+ *
+ * @param string $term The term to add or update.
+ * @param string $taxonomy The taxonomy to which to add the term.
+ * @param array|string $args {
+ * Optional. Array or string of arguments for inserting a term.
+ *
+ * @type string $alias_of Slug of the term to make this term an alias of.
+ * Default empty string. Accepts a term slug.
+ * @type string $description The term description. Default empty string.
+ * @type int $parent The id of the parent term. Default 0.
+ * @type string $slug The term slug to use. Default empty string.
+ * }
+ * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
+ * {@see WP_Error} otherwise.
+ */
+function wp_insert_term( $term, $taxonomy, $args = array() ) {
+ global $wpdb;
+
+ if ( ! taxonomy_exists($taxonomy) ) {
+ return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
+ }
+ /**
+ * Filter a term before it is sanitized and inserted into the database.
+ *
+ * @since 3.0.0
+ *
+ * @param string $term The term to add or update.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ $term = apply_filters( 'pre_insert_term', $term, $taxonomy );
+ if ( is_wp_error( $term ) ) {
+ return $term;
+ }
+ if ( is_int($term) && 0 == $term ) {
+ return new WP_Error('invalid_term_id', __('Invalid term ID'));
+ }
+ if ( '' == trim($term) ) {
+ return new WP_Error('empty_term_name', __('A name is required for this term'));
+ }
+ $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
+ $args = wp_parse_args( $args, $defaults );
+
+ if ( $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
+ return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
+ }
+ $args['name'] = $term;
+ $args['taxonomy'] = $taxonomy;
+ $args = sanitize_term($args, $taxonomy, 'db');
+
+ // expected_slashed ($name)
+ $name = wp_unslash( $args['name'] );
+ $description = wp_unslash( $args['description'] );
+ $parent = (int) $args['parent'];
+
+ $slug_provided = ! empty( $args['slug'] );
+ if ( ! $slug_provided ) {
+ $slug = sanitize_title( $name );
+ } else {
+ $slug = $args['slug'];
+ }
+
+ $term_group = 0;
+ if ( $args['alias_of'] ) {
+ $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
+ if ( ! empty( $alias->term_group ) ) {
+ // The alias we want is already in a group, so let's use that one.
+ $term_group = $alias->term_group;
+ } elseif ( ! empty( $alias->term_id ) ) {
+ /*
+ * The alias is not in a group, so we create a new one
+ * and add the alias to it.
+ */
+ $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
+
+ wp_update_term( $alias->term_id, $taxonomy, array(
+ 'term_group' => $term_group,
+ ) );
+ }
+ }
+
+ /*
+ * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
+ * unless a unique slug has been explicitly provided.
+ */
+ if ( $name_match = get_term_by( 'name', $name, $taxonomy ) ) {
+ $slug_match = get_term_by( 'slug', $slug, $taxonomy );
+ if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
+ if ( is_taxonomy_hierarchical( $taxonomy ) ) {
+ $siblings = get_terms( $taxonomy, array( 'get' => 'all', 'parent' => $parent ) );
+
+ $existing_term = null;
+ if ( $name_match->slug === $slug && in_array( $name, wp_list_pluck( $siblings, 'name' ) ) ) {
+ $existing_term = $name_match;
+ } elseif ( $slug_match && in_array( $slug, wp_list_pluck( $siblings, 'slug' ) ) ) {
+ $existing_term = $slug_match;
+ }
+
+ if ( $existing_term ) {
+ return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
+ }
+ } else {
+ return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
+ }
+ }
+ }
+
+ $slug = wp_unique_term_slug( $slug, (object) $args );
+
+ if ( false === $wpdb->insert( $wpdb->terms, compact( 'name', 'slug', 'term_group' ) ) ) {
+ return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database' ), $wpdb->last_error );
+ }
+
+ $term_id = (int) $wpdb->insert_id;
+
+ // Seems unreachable, However, Is used in the case that a term name is provided, which sanitizes to an empty string.
+ if ( empty($slug) ) {
+ $slug = sanitize_title($slug, $term_id);
+
+ /** This action is documented in wp-includes/taxonomy.php */
+ do_action( 'edit_terms', $term_id, $taxonomy );
+ $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
+
+ /** This action is documented in wp-includes/taxonomy.php */
+ do_action( 'edited_terms', $term_id, $taxonomy );
+ }
+
+ $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id ) );
+
+ if ( !empty($tt_id) ) {
+ return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
+ }
+ $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent') + array( 'count' => 0 ) );
+ $tt_id = (int) $wpdb->insert_id;
+
+ /*
+ * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
+ * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
+ * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
+ * are not fired.
+ */
+ $duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, tt.term_taxonomy_id FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id ) );
+ if ( $duplicate_term ) {
+ $wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
+ $wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
+
+ $term_id = (int) $duplicate_term->term_id;
+ $tt_id = (int) $duplicate_term->term_taxonomy_id;
+
+ clean_term_cache( $term_id, $taxonomy );
+ return array( 'term_id' => $term_id, 'term_taxonomy_id' => $tt_id );
+ }
+
+ /**
+ * Fires immediately after a new term is created, before the term cache is cleaned.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( "create_term", $term_id, $tt_id, $taxonomy );
+
+ /**
+ * Fires after a new term is created for a specific taxonomy.
+ *
+ * The dynamic portion of the hook name, `$taxonomy`, refers
+ * to the slug of the taxonomy the term was created for.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( "create_$taxonomy", $term_id, $tt_id );
+
+ /**
+ * Filter the term ID after a new term is created.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Taxonomy term ID.
+ */
+ $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
+
+ clean_term_cache($term_id, $taxonomy);
+
+ /**
+ * Fires after a new term is created, and after the term cache has been cleaned.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( 'created_term', $term_id, $tt_id, $taxonomy );
+
+ /**
+ * Fires after a new term in a specific taxonomy is created, and after the term
+ * cache has been cleaned.
+ *
+ * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( "created_$taxonomy", $term_id, $tt_id );
+
+ return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
+}
+
+/**
+ * Create Term and Taxonomy Relationships.
+ *
+ * Relates an object (post, link etc) to a term and taxonomy type. Creates the
+ * term and taxonomy relationship if it doesn't already exist. Creates a term if
+ * it doesn't exist (using the slug).
+ *
+ * A relationship means that the term is grouped in or belongs to the taxonomy.
+ * A term has no meaning until it is given context by defining which taxonomy it
+ * exists under.
+ *
+ * @since 2.3.0
+ *
+ * @global wpdb $wpdb The WordPress database abstraction object.
+ *
+ * @param int $object_id The object to relate to.
+ * @param array|int|string $terms A single term slug, single term id, or array of either term slugs or ids.
+ * Will replace all existing related terms in this taxonomy.
+ * @param string $taxonomy The context in which to relate the term to the object.
+ * @param bool $append Optional. If false will delete difference of terms. Default false.
+ * @return array|WP_Error Affected Term IDs.
+ */
+function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
+ global $wpdb;
+
+ $object_id = (int) $object_id;
+
+ if ( ! taxonomy_exists($taxonomy) )
+ return new WP_Error('invalid_taxonomy', __('Invalid taxonomy'));
+
+ if ( !is_array($terms) )
+ $terms = array($terms);
+
+ if ( ! $append )
+ $old_tt_ids = wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids', 'orderby' => 'none'));
+ else
+ $old_tt_ids = array();
+
+ $tt_ids = array();
+ $term_ids = array();
+ $new_tt_ids = array();
+
+ foreach ( (array) $terms as $term) {
+ if ( !strlen(trim($term)) )
+ continue;
+
+ if ( !$term_info = term_exists($term, $taxonomy) ) {
+ // Skip if a non-existent term ID is passed.
+ if ( is_int($term) )
+ continue;
+ $term_info = wp_insert_term($term, $taxonomy);
+ }
+ if ( is_wp_error($term_info) )
+ return $term_info;
+ $term_ids[] = $term_info['term_id'];
+ $tt_id = $term_info['term_taxonomy_id'];
+ $tt_ids[] = $tt_id;
+
+ if ( $wpdb->get_var( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id = %d", $object_id, $tt_id ) ) )
+ continue;
+
+ /**
+ * Fires immediately before an object-term relationship is added.
+ *
+ * @since 2.9.0
+ *
+ * @param int $object_id Object ID.
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( 'add_term_relationship', $object_id, $tt_id );
+ $wpdb->insert( $wpdb->term_relationships, array( 'object_id' => $object_id, 'term_taxonomy_id' => $tt_id ) );
+
+ /**
+ * Fires immediately after an object-term relationship is added.
+ *
+ * @since 2.9.0
+ *
+ * @param int $object_id Object ID.
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( 'added_term_relationship', $object_id, $tt_id );
+ $new_tt_ids[] = $tt_id;
+ }
+
+ if ( $new_tt_ids )
+ wp_update_term_count( $new_tt_ids, $taxonomy );
+
+ if ( ! $append ) {
+ $delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
+
+ if ( $delete_tt_ids ) {
+ $in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
+ $delete_term_ids = $wpdb->get_col( $wpdb->prepare( "SELECT tt.term_id FROM $wpdb->term_taxonomy AS tt WHERE tt.taxonomy = %s AND tt.term_taxonomy_id IN ($in_delete_tt_ids)", $taxonomy ) );
+ $delete_term_ids = array_map( 'intval', $delete_term_ids );
+
+ $remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
+ if ( is_wp_error( $remove ) ) {
+ return $remove;
+ }
+ }
+ }
+
+ $t = get_taxonomy($taxonomy);
+ if ( ! $append && isset($t->sort) && $t->sort ) {
+ $values = array();
+ $term_order = 0;
+ $final_tt_ids = wp_get_object_terms($object_id, $taxonomy, array('fields' => 'tt_ids'));
+ foreach ( $tt_ids as $tt_id )
+ if ( in_array($tt_id, $final_tt_ids) )
+ $values[] = $wpdb->prepare( "(%d, %d, %d)", $object_id, $tt_id, ++$term_order);
+ if ( $values )
+ if ( false === $wpdb->query( "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES " . join( ',', $values ) . " ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)" ) )
+ return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database' ), $wpdb->last_error );
+ }
+
+ wp_cache_delete( $object_id, $taxonomy . '_relationships' );
+
+ /**
+ * Fires after an object's terms have been set.
+ *
+ * @since 2.8.0
+ *
+ * @param int $object_id Object ID.
+ * @param array $terms An array of object terms.
+ * @param array $tt_ids An array of term taxonomy IDs.
+ * @param string $taxonomy Taxonomy slug.
+ * @param bool $append Whether to append new terms to the old terms.
+ * @param array $old_tt_ids Old array of term taxonomy IDs.
+ */
+ do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
+ return $tt_ids;
+}
+
+/**
+ * Add term(s) associated with a given object.
+ *
+ * @since 3.6.0
+ *
+ * @param int $object_id The ID of the object to which the terms will be added.
+ * @param array|int|string $terms The slug(s) or ID(s) of the term(s) to add.
+ * @param array|string $taxonomy Taxonomy name.
+ * @return array|WP_Error Affected Term IDs
+ */
+function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
+ return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
+}
+
+/**
+ * Remove term(s) associated with a given object.
+ *
+ * @since 3.6.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param int $object_id The ID of the object from which the terms will be removed.
+ * @param array|int|string $terms The slug(s) or ID(s) of the term(s) to remove.
+ * @param array|string $taxonomy Taxonomy name.
+ * @return bool|WP_Error True on success, false or WP_Error on failure.
+ */
+function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
+ global $wpdb;
+
+ $object_id = (int) $object_id;
+
+ if ( ! taxonomy_exists( $taxonomy ) ) {
+ return new WP_Error( 'invalid_taxonomy', __( 'Invalid Taxonomy' ) );
+ }
+
+ if ( ! is_array( $terms ) ) {
+ $terms = array( $terms );
+ }
+
+ $tt_ids = array();
+
+ foreach ( (array) $terms as $term ) {
+ if ( ! strlen( trim( $term ) ) ) {
+ continue;
+ }
+
+ if ( ! $term_info = term_exists( $term, $taxonomy ) ) {
+ // Skip if a non-existent term ID is passed.
+ if ( is_int( $term ) ) {
+ continue;
+ }
+ }
+
+ if ( is_wp_error( $term_info ) ) {
+ return $term_info;
+ }
+
+ $tt_ids[] = $term_info['term_taxonomy_id'];
+ }
+
+ if ( $tt_ids ) {
+ $in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
+
+ /**
+ * Fires immediately before an object-term relationship is deleted.
+ *
+ * @since 2.9.0
+ *
+ * @param int $object_id Object ID.
+ * @param array $tt_ids An array of term taxonomy IDs.
+ */
+ do_action( 'delete_term_relationships', $object_id, $tt_ids );
+ $deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
+
+ /**
+ * Fires immediately after an object-term relationship is deleted.
+ *
+ * @since 2.9.0
+ *
+ * @param int $object_id Object ID.
+ * @param array $tt_ids An array of term taxonomy IDs.
+ */
+ do_action( 'deleted_term_relationships', $object_id, $tt_ids );
+
+ wp_update_term_count( $tt_ids, $taxonomy );
+
+ return (bool) $deleted;
+ }
+
+ return false;
+}
+
+/**
+ * Will make slug unique, if it isn't already.
+ *
+ * The `$slug` has to be unique global to every taxonomy, meaning that one
+ * taxonomy term can't have a matching slug with another taxonomy term. Each
+ * slug has to be globally unique for every taxonomy.
+ *
+ * The way this works is that if the taxonomy that the term belongs to is
+ * hierarchical and has a parent, it will append that parent to the $slug.
+ *
+ * If that still doesn't return an unique slug, then it try to append a number
+ * until it finds a number that is truly unique.
+ *
+ * The only purpose for `$term` is for appending a parent, if one exists.
+ *
+ * @since 2.3.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param string $slug The string that will be tried for a unique slug.
+ * @param object $term The term object that the `$slug` will belong to.
+ * @return string Will return a true unique slug.
+ */
+function wp_unique_term_slug( $slug, $term ) {
+ global $wpdb;
+
+ $needs_suffix = true;
+ $original_slug = $slug;
+
+ // As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
+ if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
+ $needs_suffix = false;
+ }
+
+ /*
+ * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
+ * by incorporating parent slugs.
+ */
+ $parent_suffix = '';
+ if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
+ $the_parent = $term->parent;
+ while ( ! empty($the_parent) ) {
+ $parent_term = get_term($the_parent, $term->taxonomy);
+ if ( is_wp_error($parent_term) || empty($parent_term) )
+ break;
+ $parent_suffix .= '-' . $parent_term->slug;
+ if ( ! term_exists( $slug . $parent_suffix ) ) {
+ break;
+ }
+
+ if ( empty($parent_term->parent) )
+ break;
+ $the_parent = $parent_term->parent;
+ }
+ }
+
+ // If we didn't get a unique slug, try appending a number to make it unique.
+
+ /**
+ * Filter whether the proposed unique term slug is bad.
+ *
+ * @since 4.3.0
+ *
+ * @param bool $needs_suffix Whether the slug needs to be made unique with a suffix.
+ * @param string $slug The slug.
+ * @param object $term Term object.
+ */
+ if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
+ if ( $parent_suffix ) {
+ $slug .= $parent_suffix;
+ } else {
+ if ( ! empty( $term->term_id ) )
+ $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
+ else
+ $query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
+
+ if ( $wpdb->get_var( $query ) ) {
+ $num = 2;
+ do {
+ $alt_slug = $slug . "-$num";
+ $num++;
+ $slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
+ } while ( $slug_check );
+ $slug = $alt_slug;
+ }
+ }
+ }
+
+ /**
+ * Filter the unique term slug.
+ *
+ * @since 4.3.0
+ *
+ * @param string $slug Unique term slug.
+ * @param object $term Term object.
+ * @param string $original_slug Slug originally passed to the function for testing.
+ */
+ return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
+}
+
+/**
+ * Update term based on arguments provided.
+ *
+ * The $args will indiscriminately override all values with the same field name.
+ * Care must be taken to not override important information need to update or
+ * update will fail (or perhaps create a new term, neither would be acceptable).
+ *
+ * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
+ * defined in $args already.
+ *
+ * 'alias_of' will create a term group, if it doesn't already exist, and update
+ * it for the $term.
+ *
+ * If the 'slug' argument in $args is missing, then the 'name' in $args will be
+ * used. It should also be noted that if you set 'slug' and it isn't unique then
+ * a WP_Error will be passed back. If you don't pass any slug, then a unique one
+ * will be created for you.
+ *
+ * For what can be overrode in `$args`, check the term scheme can contain and stay
+ * away from the term keys.
+ *
+ * @since 2.3.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param int $term_id The ID of the term
+ * @param string $taxonomy The context in which to relate the term to the object.
+ * @param array|string $args Optional. Array of get_terms() arguments. Default empty array.
+ * @return array|WP_Error Returns Term ID and Taxonomy Term ID
+ */
+function wp_update_term( $term_id, $taxonomy, $args = array() ) {
+ global $wpdb;
+
+ if ( ! taxonomy_exists( $taxonomy ) ) {
+ return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy' ) );
+ }
+
+ $term_id = (int) $term_id;
+
+ // First, get all of the original args
+ $term = get_term( $term_id, $taxonomy, ARRAY_A );
+
+ if ( is_wp_error( $term ) ) {
+ return $term;
+ }
+
+ if ( ! $term ) {
+ return new WP_Error( 'invalid_term', __( 'Empty Term' ) );
+ }
+
+ // Escape data pulled from DB.
+ $term = wp_slash($term);
+
+ // Merge old and new args with new args overwriting old ones.
+ $args = array_merge($term, $args);
+
+ $defaults = array( 'alias_of' => '', 'description' => '', 'parent' => 0, 'slug' => '');
+ $args = wp_parse_args($args, $defaults);
+ $args = sanitize_term($args, $taxonomy, 'db');
+ $parsed_args = $args;
+
+ // expected_slashed ($name)
+ $name = wp_unslash( $args['name'] );
+ $description = wp_unslash( $args['description'] );
+
+ $parsed_args['name'] = $name;
+ $parsed_args['description'] = $description;
+
+ if ( '' == trim($name) )
+ return new WP_Error('empty_term_name', __('A name is required for this term'));
+
+ if ( $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
+ return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
+ }
+
+ $empty_slug = false;
+ if ( empty( $args['slug'] ) ) {
+ $empty_slug = true;
+ $slug = sanitize_title($name);
+ } else {
+ $slug = $args['slug'];
+ }
+
+ $parsed_args['slug'] = $slug;
+
+ $term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
+ if ( $args['alias_of'] ) {
+ $alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
+ if ( ! empty( $alias->term_group ) ) {
+ // The alias we want is already in a group, so let's use that one.
+ $term_group = $alias->term_group;
+ } elseif ( ! empty( $alias->term_id ) ) {
+ /*
+ * The alias is not in a group, so we create a new one
+ * and add the alias to it.
+ */
+ $term_group = $wpdb->get_var("SELECT MAX(term_group) FROM $wpdb->terms") + 1;
+
+ wp_update_term( $alias->term_id, $taxonomy, array(
+ 'term_group' => $term_group,
+ ) );
+ }
+
+ $parsed_args['term_group'] = $term_group;
+ }
+
+ /**
+ * Filter the term parent.
+ *
+ * Hook to this filter to see if it will cause a hierarchy loop.
+ *
+ * @since 3.1.0
+ *
+ * @param int $parent ID of the parent term.
+ * @param int $term_id Term ID.
+ * @param string $taxonomy Taxonomy slug.
+ * @param array $parsed_args An array of potentially altered update arguments for the given term.
+ * @param array $args An array of update arguments for the given term.
+ */
+ $parent = apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
+
+ // Check for duplicate slug
+ $duplicate = get_term_by( 'slug', $slug, $taxonomy );
+ if ( $duplicate && $duplicate->term_id != $term_id ) {
+ // If an empty slug was passed or the parent changed, reset the slug to something unique.
+ // Otherwise, bail.
+ if ( $empty_slug || ( $parent != $term['parent']) )
+ $slug = wp_unique_term_slug($slug, (object) $args);
+ else
+ return new WP_Error('duplicate_term_slug', sprintf(__('The slug “%s” is already in use by another term'), $slug));
+ }
+
+ $tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id) );
+
+ // Check whether this is a shared term that needs splitting.
+ $_term_id = _split_shared_term( $term_id, $tt_id );
+ if ( ! is_wp_error( $_term_id ) ) {
+ $term_id = $_term_id;
+ }
+
+ /**
+ * Fires immediately before the given terms are edited.
+ *
+ * @since 2.9.0
+ *
+ * @param int $term_id Term ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( 'edit_terms', $term_id, $taxonomy );
+ $wpdb->update($wpdb->terms, compact( 'name', 'slug', 'term_group' ), compact( 'term_id' ) );
+ if ( empty($slug) ) {
+ $slug = sanitize_title($name, $term_id);
+ $wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
+ }
+
+ /**
+ * Fires immediately after the given terms are edited.
+ *
+ * @since 2.9.0
+ *
+ * @param int $term_id Term ID
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( 'edited_terms', $term_id, $taxonomy );
+
+ /**
+ * Fires immediate before a term-taxonomy relationship is updated.
+ *
+ * @since 2.9.0
+ *
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
+
+ $wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
+
+ /**
+ * Fires immediately after a term-taxonomy relationship is updated.
+ *
+ * @since 2.9.0
+ *
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
+
+ // Clean the relationship caches for all object types using this term.
+ $objects = $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
+ $tax_object = get_taxonomy( $taxonomy );
+ foreach ( $tax_object->object_type as $object_type ) {
+ clean_object_term_cache( $objects, $object_type );
+ }
+
+ /**
+ * Fires after a term has been updated, but before the term cache has been cleaned.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( "edit_term", $term_id, $tt_id, $taxonomy );
+
+ /**
+ * Fires after a term in a specific taxonomy has been updated, but before the term
+ * cache has been cleaned.
+ *
+ * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( "edit_$taxonomy", $term_id, $tt_id );
+
+ /** This filter is documented in wp-includes/taxonomy.php */
+ $term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
+
+ clean_term_cache($term_id, $taxonomy);
+
+ /**
+ * Fires after a term has been updated, and the term cache has been cleaned.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( "edited_term", $term_id, $tt_id, $taxonomy );
+
+ /**
+ * Fires after a term for a specific taxonomy has been updated, and the term
+ * cache has been cleaned.
+ *
+ * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
+ *
+ * @since 2.3.0
+ *
+ * @param int $term_id Term ID.
+ * @param int $tt_id Term taxonomy ID.
+ */
+ do_action( "edited_$taxonomy", $term_id, $tt_id );
+
+ return array('term_id' => $term_id, 'term_taxonomy_id' => $tt_id);
+}
+
+/**
+ * Enable or disable term counting.
+ *
+ * @since 2.5.0
+ *
+ * @staticvar bool $_defer
+ *
+ * @param bool $defer Optional. Enable if true, disable if false.
+ * @return bool Whether term counting is enabled or disabled.
+ */
+function wp_defer_term_counting($defer=null) {
+ static $_defer = false;
+
+ if ( is_bool($defer) ) {
+ $_defer = $defer;
+ // flush any deferred counts
+ if ( !$defer )
+ wp_update_term_count( null, null, true );
+ }
+
+ return $_defer;
+}
+
+/**
+ * Updates the amount of terms in taxonomy.
+ *
+ * If there is a taxonomy callback applied, then it will be called for updating
+ * the count.
+ *
+ * The default action is to count what the amount of terms have the relationship
+ * of term ID. Once that is done, then update the database.
+ *
+ * @since 2.3.0
+ *
+ * @staticvar array $_deferred
+ *
+ * @param int|array $terms The term_taxonomy_id of the terms.
+ * @param string $taxonomy The context of the term.
+ * @return bool If no terms will return false, and if successful will return true.
+ */
+function wp_update_term_count( $terms, $taxonomy, $do_deferred=false ) {
+ static $_deferred = array();
+
+ if ( $do_deferred ) {
+ foreach ( (array) array_keys($_deferred) as $tax ) {
+ wp_update_term_count_now( $_deferred[$tax], $tax );
+ unset( $_deferred[$tax] );
+ }
+ }
+
+ if ( empty($terms) )
+ return false;
+
+ if ( !is_array($terms) )
+ $terms = array($terms);
+
+ if ( wp_defer_term_counting() ) {
+ if ( !isset($_deferred[$taxonomy]) )
+ $_deferred[$taxonomy] = array();
+ $_deferred[$taxonomy] = array_unique( array_merge($_deferred[$taxonomy], $terms) );
+ return true;
+ }
+
+ return wp_update_term_count_now( $terms, $taxonomy );
+}
+
+/**
+ * Perform term count update immediately.
+ *
+ * @since 2.5.0
+ *
+ * @param array $terms The term_taxonomy_id of terms to update.
+ * @param string $taxonomy The context of the term.
+ * @return true Always true when complete.
+ */
+function wp_update_term_count_now( $terms, $taxonomy ) {
+ $terms = array_map('intval', $terms);
+
+ $taxonomy = get_taxonomy($taxonomy);
+ if ( !empty($taxonomy->update_count_callback) ) {
+ call_user_func($taxonomy->update_count_callback, $terms, $taxonomy);
+ } else {
+ $object_types = (array) $taxonomy->object_type;
+ foreach ( $object_types as &$object_type ) {
+ if ( 0 === strpos( $object_type, 'attachment:' ) )
+ list( $object_type ) = explode( ':', $object_type );
+ }
+
+ if ( $object_types == array_filter( $object_types, 'post_type_exists' ) ) {
+ // Only post types are attached to this taxonomy
+ _update_post_term_count( $terms, $taxonomy );
+ } else {
+ // Default count updater
+ _update_generic_term_count( $terms, $taxonomy );
+ }
+ }
+
+ clean_term_cache($terms, '', false);
+
+ return true;
+}
+
+//
+// Cache
+//
+
+/**
+ * Removes the taxonomy relationship to terms from the cache.
+ *
+ * Will remove the entire taxonomy relationship containing term `$object_id`. The
+ * term IDs have to exist within the taxonomy `$object_type` for the deletion to
+ * take place.
+ *
+ * @since 2.3.0
+ *
+ * @see get_object_taxonomies() for more on $object_type.
+ *
+ * @param int|array $object_ids Single or list of term object ID(s).
+ * @param array|string $object_type The taxonomy object type.
+ */
+function clean_object_term_cache($object_ids, $object_type) {
+ if ( !is_array($object_ids) )
+ $object_ids = array($object_ids);
+
+ $taxonomies = get_object_taxonomies( $object_type );
+
+ foreach ( $object_ids as $id ) {
+ foreach ( $taxonomies as $taxonomy ) {
+ wp_cache_delete($id, "{$taxonomy}_relationships");
+ }
+ }
+
+ /**
+ * Fires after the object term cache has been cleaned.
+ *
+ * @since 2.5.0
+ *
+ * @param array $object_ids An array of object IDs.
+ * @param string $objet_type Object type.
+ */
+ do_action( 'clean_object_term_cache', $object_ids, $object_type );
+}
+
+/**
+ * Will remove all of the term ids from the cache.
+ *
+ * @since 2.3.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ * @global bool $_wp_suspend_cache_invalidation
+ *
+ * @param int|array $ids Single or list of Term IDs.
+ * @param string $taxonomy Optional. Can be empty and will assume `tt_ids`, else will use for context.
+ * Default empty.
+ * @param bool $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
+ * term object caches (false). Default true.
+ */
+function clean_term_cache($ids, $taxonomy = '', $clean_taxonomy = true) {
+ global $wpdb, $_wp_suspend_cache_invalidation;
+
+ if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
+ return;
+ }
+
+ if ( !is_array($ids) )
+ $ids = array($ids);
+
+ $taxonomies = array();
+ // If no taxonomy, assume tt_ids.
+ if ( empty($taxonomy) ) {
+ $tt_ids = array_map('intval', $ids);
+ $tt_ids = implode(', ', $tt_ids);
+ $terms = $wpdb->get_results("SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)");
+ $ids = array();
+ foreach ( (array) $terms as $term ) {
+ $taxonomies[] = $term->taxonomy;
+ $ids[] = $term->term_id;
+ wp_cache_delete($term->term_id, $term->taxonomy);
+ }
+ $taxonomies = array_unique($taxonomies);
+ } else {
+ $taxonomies = array($taxonomy);
+ foreach ( $taxonomies as $taxonomy ) {
+ foreach ( $ids as $id ) {
+ wp_cache_delete($id, $taxonomy);
+ }
+ }
+ }
+
+ foreach ( $taxonomies as $taxonomy ) {
+ if ( $clean_taxonomy ) {
+ wp_cache_delete('all_ids', $taxonomy);
+ wp_cache_delete('get', $taxonomy);
+ delete_option("{$taxonomy}_children");
+ // Regenerate {$taxonomy}_children
+ _get_term_hierarchy($taxonomy);
+ }
+
+ /**
+ * Fires once after each taxonomy's term cache has been cleaned.
+ *
+ * @since 2.5.0
+ *
+ * @param array $ids An array of term IDs.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ do_action( 'clean_term_cache', $ids, $taxonomy );
+ }
+
+ wp_cache_set( 'last_changed', microtime(), 'terms' );
+}
+
+/**
+ * Retrieves the taxonomy relationship to the term object id.
+ *
+ * @since 2.3.0
+ *
+ * @param int $id Term object ID.
+ * @param string $taxonomy Taxonomy name.
+ * @return bool|mixed Empty array if $terms found, but not `$taxonomy`. False if nothing is in cache
+ * for `$taxonomy` and `$id`.
+ */
+function get_object_term_cache( $id, $taxonomy ) {
+ return wp_cache_get( $id, "{$taxonomy}_relationships" );
+}
+
+/**
+ * Updates the cache for the given term object ID(s).
+ *
+ * Note: Due to performance concerns, great care should be taken to only update
+ * term caches when necessary. Processing time can increase exponentially depending
+ * on both the number of passed term IDs and the number of taxonomies those terms
+ * belong to.
+ *
+ * Caches will only be updated for terms not already cached.
+ *
+ * @since 2.3.0
+ *
+ * @param string|array $object_ids Comma-separated list or array of term object IDs.
+ * @param array|string $object_type The taxonomy object type.
+ * @return void|false False if all of the terms in `$object_ids` are already cached.
+ */
+function update_object_term_cache($object_ids, $object_type) {
+ if ( empty($object_ids) )
+ return;
+
+ if ( !is_array($object_ids) )
+ $object_ids = explode(',', $object_ids);
+
+ $object_ids = array_map('intval', $object_ids);
+
+ $taxonomies = get_object_taxonomies($object_type);
+
+ $ids = array();
+ foreach ( (array) $object_ids as $id ) {
+ foreach ( $taxonomies as $taxonomy ) {
+ if ( false === wp_cache_get($id, "{$taxonomy}_relationships") ) {
+ $ids[] = $id;
+ break;
+ }
+ }
+ }
+
+ if ( empty( $ids ) )
+ return false;
+
+ $terms = wp_get_object_terms($ids, $taxonomies, array('fields' => 'all_with_object_id'));
+
+ $object_terms = array();
+ foreach ( (array) $terms as $term )
+ $object_terms[$term->object_id][$term->taxonomy][] = $term;
+
+ foreach ( $ids as $id ) {
+ foreach ( $taxonomies as $taxonomy ) {
+ if ( ! isset($object_terms[$id][$taxonomy]) ) {
+ if ( !isset($object_terms[$id]) )
+ $object_terms[$id] = array();
+ $object_terms[$id][$taxonomy] = array();
+ }
+ }