+/**
+ * Check default categories when a term gets split to see if any of them need to be updated.
+ *
+ * @ignore
+ * @since 4.2.0
+ *
+ * @param int $term_id ID of the formerly shared term.
+ * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
+ * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
+ * @param string $taxonomy Taxonomy for the split term.
+ */
+function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
+ if ( 'category' != $taxonomy ) {
+ return;
+ }
+
+ foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
+ if ( $term_id == get_option( $option, -1 ) ) {
+ update_option( $option, $new_term_id );
+ }
+ }
+}
+
+/**
+ * Check menu items when a term gets split to see if any of them need to be updated.
+ *
+ * @ignore
+ * @since 4.2.0
+ *
+ * @global wpdb $wpdb WordPress database abstraction object.
+ *
+ * @param int $term_id ID of the formerly shared term.
+ * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
+ * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
+ * @param string $taxonomy Taxonomy for the split term.
+ */
+function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
+ global $wpdb;
+ $post_ids = $wpdb->get_col( $wpdb->prepare(
+ "SELECT m1.post_id
+ FROM {$wpdb->postmeta} AS m1
+ INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
+ INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
+ WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
+ AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = '%s' )
+ AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
+ $taxonomy,
+ $term_id
+ ) );
+
+ if ( $post_ids ) {
+ foreach ( $post_ids as $post_id ) {
+ update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
+ }
+ }
+}
+
+/**
+ * If the term being split is a nav_menu, change associations.
+ *
+ * @ignore
+ * @since 4.3.0
+ *
+ * @param int $term_id ID of the formerly shared term.
+ * @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
+ * @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
+ * @param string $taxonomy Taxonomy for the split term.
+ */
+function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
+ if ( 'nav_menu' !== $taxonomy ) {
+ return;
+ }
+
+ // Update menu locations.
+ $locations = get_nav_menu_locations();
+ foreach ( $locations as $location => $menu_id ) {
+ if ( $term_id == $menu_id ) {
+ $locations[ $location ] = $new_term_id;
+ }
+ }
+ set_theme_mod( 'nav_menu_locations', $locations );
+}
+
+/**
+ * Get data about terms that previously shared a single term_id, but have since been split.
+ *
+ * @since 4.2.0
+ *
+ * @param int $old_term_id Term ID. This is the old, pre-split term ID.
+ * @return array Array of new term IDs, keyed by taxonomy.
+ */
+function wp_get_split_terms( $old_term_id ) {
+ $split_terms = get_option( '_split_terms', array() );
+
+ $terms = array();
+ if ( isset( $split_terms[ $old_term_id ] ) ) {
+ $terms = $split_terms[ $old_term_id ];
+ }
+
+ return $terms;
+}
+
+/**
+ * Get the new term ID corresponding to a previously split term.
+ *
+ * @since 4.2.0
+ *
+ * @param int $old_term_id Term ID. This is the old, pre-split term ID.
+ * @param string $taxonomy Taxonomy that the term belongs to.
+ * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
+ * the new term_id will be returned. If no previously split term is found matching
+ * the parameters, returns false.
+ */
+function wp_get_split_term( $old_term_id, $taxonomy ) {
+ $split_terms = wp_get_split_terms( $old_term_id );
+
+ $term_id = false;
+ if ( isset( $split_terms[ $taxonomy ] ) ) {
+ $term_id = (int) $split_terms[ $taxonomy ];
+ }
+
+ return $term_id;
+}
+
+/**
+ * Determine whether a term is shared between multiple taxonomies.
+ *
+ * Shared taxonomy terms began to be split in 4.3, but failed cron tasks or other delays in upgrade routines may cause
+ * shared terms to remain.
+ *
+ * @since 4.4.0
+ *
+ * @param int $term_id
+ * @return bool
+ */
+function wp_term_is_shared( $term_id ) {
+ global $wpdb;
+
+ if ( get_option( 'finished_splitting_shared_terms' ) ) {
+ return false;
+ }
+
+ $tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
+
+ return $tt_count > 1;
+}
+
+/**
+ * Generate a permalink for a taxonomy term archive.
+ *
+ * @since 2.5.0
+ *
+ * @global WP_Rewrite $wp_rewrite
+ *
+ * @param object|int|string $term The term object, ID, or slug whose link will be retrieved.
+ * @param string $taxonomy Optional. Taxonomy. Default empty.
+ * @return string|WP_Error HTML link to taxonomy term archive on success, WP_Error if term does not exist.
+ */
+function get_term_link( $term, $taxonomy = '' ) {
+ global $wp_rewrite;
+
+ if ( !is_object($term) ) {
+ if ( is_int( $term ) ) {
+ $term = get_term( $term, $taxonomy );
+ } else {
+ $term = get_term_by( 'slug', $term, $taxonomy );
+ }
+ }
+
+ if ( !is_object($term) )
+ $term = new WP_Error('invalid_term', __('Empty Term'));
+
+ if ( is_wp_error( $term ) )
+ return $term;
+
+ $taxonomy = $term->taxonomy;
+
+ $termlink = $wp_rewrite->get_extra_permastruct($taxonomy);
+
+ $slug = $term->slug;
+ $t = get_taxonomy($taxonomy);
+
+ if ( empty($termlink) ) {
+ if ( 'category' == $taxonomy )
+ $termlink = '?cat=' . $term->term_id;
+ elseif ( $t->query_var )
+ $termlink = "?$t->query_var=$slug";
+ else
+ $termlink = "?taxonomy=$taxonomy&term=$slug";
+ $termlink = home_url($termlink);
+ } else {
+ if ( $t->rewrite['hierarchical'] ) {
+ $hierarchical_slugs = array();
+ $ancestors = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
+ foreach ( (array)$ancestors as $ancestor ) {
+ $ancestor_term = get_term($ancestor, $taxonomy);
+ $hierarchical_slugs[] = $ancestor_term->slug;
+ }
+ $hierarchical_slugs = array_reverse($hierarchical_slugs);
+ $hierarchical_slugs[] = $slug;
+ $termlink = str_replace("%$taxonomy%", implode('/', $hierarchical_slugs), $termlink);
+ } else {
+ $termlink = str_replace("%$taxonomy%", $slug, $termlink);
+ }
+ $termlink = home_url( user_trailingslashit($termlink, 'category') );
+ }
+ // Back Compat filters.
+ if ( 'post_tag' == $taxonomy ) {
+
+ /**
+ * Filter the tag link.
+ *
+ * @since 2.3.0
+ * @deprecated 2.5.0 Use 'term_link' instead.
+ *
+ * @param string $termlink Tag link URL.
+ * @param int $term_id Term ID.
+ */
+ $termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
+ } elseif ( 'category' == $taxonomy ) {
+
+ /**
+ * Filter the category link.
+ *
+ * @since 1.5.0
+ * @deprecated 2.5.0 Use 'term_link' instead.
+ *
+ * @param string $termlink Category link URL.
+ * @param int $term_id Term ID.
+ */
+ $termlink = apply_filters( 'category_link', $termlink, $term->term_id );
+ }
+
+ /**
+ * Filter the term link.
+ *
+ * @since 2.5.0
+ *
+ * @param string $termlink Term link URL.
+ * @param object $term Term object.
+ * @param string $taxonomy Taxonomy slug.
+ */
+ return apply_filters( 'term_link', $termlink, $term, $taxonomy );
+}
+
+/**
+ * Display the taxonomies of a post with available options.
+ *
+ * This function can be used within the loop to display the taxonomies for a
+ * post without specifying the Post ID. You can also use it outside the Loop to
+ * display the taxonomies for a specific post.
+ *
+ * @since 2.5.0
+ *
+ * @param array $args {
+ * Arguments about which post to use and how to format the output. Shares all of the arguments
+ * supported by get_the_taxonomies(), in addition to the following.
+ *
+ * @type int|WP_Post $post Post ID or object to get taxonomies of. Default current post.
+ * @type string $before Displays before the taxonomies. Default empty string.
+ * @type string $sep Separates each taxonomy. Default is a space.
+ * @type string $after Displays after the taxonomies. Default empty string.
+ * }
+ * @param array $args See {@link get_the_taxonomies()} for a description of arguments and their defaults.
+ */
+function the_taxonomies( $args = array() ) {
+ $defaults = array(
+ 'post' => 0,
+ 'before' => '',
+ 'sep' => ' ',
+ 'after' => '',
+ );
+
+ $r = wp_parse_args( $args, $defaults );
+
+ echo $r['before'] . join( $r['sep'], get_the_taxonomies( $r['post'], $r ) ) . $r['after'];
+}
+
+/**
+ * Retrieve all taxonomies associated with a post.
+ *
+ * This function can be used within the loop. It will also return an array of
+ * the taxonomies with links to the taxonomy and name.
+ *
+ * @since 2.5.0
+ *
+ * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
+ * @param array $args {
+ * Optional. Arguments about how to format the list of taxonomies. Default empty array.
+ *
+ * @type string $template Template for displaying a taxonomy label and list of terms.
+ * Default is "Label: Terms."
+ * @type string $term_template Template for displaying a single term in the list. Default is the term name
+ * linked to its archive.
+ * }
+ * @return array List of taxonomies.
+ */
+function get_the_taxonomies( $post = 0, $args = array() ) {
+ $post = get_post( $post );
+
+ $args = wp_parse_args( $args, array(
+ /* translators: %s: taxonomy label, %l: list of terms formatted as per $term_template */
+ 'template' => __( '%s: %l.' ),
+ 'term_template' => '<a href="%1$s">%2$s</a>',
+ ) );
+
+ $taxonomies = array();
+
+ if ( ! $post ) {
+ return $taxonomies;
+ }
+
+ foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
+ $t = (array) get_taxonomy( $taxonomy );
+ if ( empty( $t['label'] ) ) {
+ $t['label'] = $taxonomy;
+ }
+ if ( empty( $t['args'] ) ) {
+ $t['args'] = array();
+ }
+ if ( empty( $t['template'] ) ) {
+ $t['template'] = $args['template'];
+ }
+ if ( empty( $t['term_template'] ) ) {
+ $t['term_template'] = $args['term_template'];
+ }
+
+ $terms = get_object_term_cache( $post->ID, $taxonomy );
+ if ( false === $terms ) {
+ $terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
+ }
+ $links = array();
+
+ foreach ( $terms as $term ) {
+ $links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name );
+ }
+ if ( $links ) {
+ $taxonomies[$taxonomy] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
+ }
+ }
+ return $taxonomies;
+}
+
+/**
+ * Retrieve all taxonomies of a post with just the names.
+ *
+ * @since 2.5.0
+ *
+ * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
+ * @return array
+ */
+function get_post_taxonomies( $post = 0 ) {
+ $post = get_post( $post );
+
+ return get_object_taxonomies($post);
+}
+
+/**
+ * Determine if the given object is associated with any of the given terms.
+ *
+ * The given terms are checked against the object's terms' term_ids, names and slugs.
+ * Terms given as integers will only be checked against the object's terms' term_ids.
+ * If no terms are given, determines if object is associated with any terms in the given taxonomy.
+ *
+ * @since 2.7.0
+ *
+ * @param int $object_id ID of the object (post ID, link ID, ...).
+ * @param string $taxonomy Single taxonomy name.
+ * @param int|string|array $terms Optional. Term term_id, name, slug or array of said. Default null.
+ * @return bool|WP_Error WP_Error on input error.
+ */
+function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
+ if ( !$object_id = (int) $object_id )
+ return new WP_Error( 'invalid_object', __( 'Invalid object ID' ) );
+
+ $object_terms = get_object_term_cache( $object_id, $taxonomy );
+ if ( false === $object_terms ) {
+ $object_terms = wp_get_object_terms( $object_id, $taxonomy, array( 'update_term_meta_cache' => false ) );
+ wp_cache_set( $object_id, $object_terms, "{$taxonomy}_relationships" );
+ }
+
+ if ( is_wp_error( $object_terms ) )
+ return $object_terms;
+ if ( empty( $object_terms ) )
+ return false;
+ if ( empty( $terms ) )
+ return ( !empty( $object_terms ) );
+
+ $terms = (array) $terms;
+
+ if ( $ints = array_filter( $terms, 'is_int' ) )
+ $strs = array_diff( $terms, $ints );
+ else
+ $strs =& $terms;
+
+ foreach ( $object_terms as $object_term ) {
+ // If term is an int, check against term_ids only.
+ if ( $ints && in_array( $object_term->term_id, $ints ) ) {
+ return true;
+ }
+
+ if ( $strs ) {
+ // Only check numeric strings against term_id, to avoid false matches due to type juggling.
+ $numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
+ if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
+ return true;
+ }
+
+ if ( in_array( $object_term->name, $strs ) ) return true;
+ if ( in_array( $object_term->slug, $strs ) ) return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Determine if the given object type is associated with the given taxonomy.
+ *
+ * @since 3.0.0
+ *
+ * @param string $object_type Object type string.
+ * @param string $taxonomy Single taxonomy name.
+ * @return bool True if object is associated with the taxonomy, otherwise false.
+ */
+function is_object_in_taxonomy( $object_type, $taxonomy ) {
+ $taxonomies = get_object_taxonomies( $object_type );
+ if ( empty( $taxonomies ) ) {
+ return false;
+ }
+ return in_array( $taxonomy, $taxonomies );
+}
+
+/**
+ * Get an array of ancestor IDs for a given object.
+ *
+ * @since 3.1.0
+ * @since 4.1.0 Introduced the `$resource_type` argument.
+ *
+ * @param int $object_id Optional. The ID of the object. Default 0.
+ * @param string $object_type Optional. The type of object for which we'll be retrieving
+ * ancestors. Accepts a post type or a taxonomy name. Default empty.
+ * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
+ * or 'taxonomy'. Default empty.
+ * @return array An array of ancestors from lowest to highest in the hierarchy.
+ */
+function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
+ $object_id = (int) $object_id;
+
+ $ancestors = array();
+
+ if ( empty( $object_id ) ) {
+
+ /** This filter is documented in wp-includes/taxonomy.php */
+ return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
+ }
+
+ if ( ! $resource_type ) {
+ if ( is_taxonomy_hierarchical( $object_type ) ) {
+ $resource_type = 'taxonomy';
+ } elseif ( post_type_exists( $object_type ) ) {
+ $resource_type = 'post_type';
+ }
+ }
+
+ if ( 'taxonomy' === $resource_type ) {
+ $term = get_term($object_id, $object_type);
+ while ( ! is_wp_error($term) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors ) ) {
+ $ancestors[] = (int) $term->parent;
+ $term = get_term($term->parent, $object_type);
+ }
+ } elseif ( 'post_type' === $resource_type ) {
+ $ancestors = get_post_ancestors($object_id);
+ }
+
+ /**
+ * Filter a given object's ancestors.
+ *
+ * @since 3.1.0
+ * @since 4.1.1 Introduced the `$resource_type` parameter.
+ *
+ * @param array $ancestors An array of object ancestors.
+ * @param int $object_id Object ID.
+ * @param string $object_type Type of object.
+ * @param string $resource_type Type of resource $object_type is.
+ */
+ return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
+}
+
+/**
+ * Returns the term's parent's term_ID.
+ *
+ * @since 3.1.0
+ *
+ * @param int $term_id Term ID.
+ * @param string $taxonomy Taxonomy name.
+ * @return int|false False on error.
+ */
+function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
+ $term = get_term( $term_id, $taxonomy );
+ if ( ! $term || is_wp_error( $term ) ) {
+ return false;
+ }
+ return (int) $term->parent;
+}
+
+/**
+ * Checks the given subset of the term hierarchy for hierarchy loops.
+ * Prevents loops from forming and breaks those that it finds.
+ *
+ * Attached to the {@see 'wp_update_term_parent'} filter.
+ *
+ * @since 3.1.0
+ *
+ * @param int $parent `term_id` of the parent for the term we're checking.
+ * @param int $term_id The term we're checking.
+ * @param string $taxonomy The taxonomy of the term we're checking.
+ *
+ * @return int The new parent for the term.
+ */
+function wp_check_term_hierarchy_for_loops( $parent, $term_id, $taxonomy ) {
+ // Nothing fancy here - bail
+ if ( !$parent )
+ return 0;
+
+ // Can't be its own parent.
+ if ( $parent == $term_id )
+ return 0;
+
+ // Now look for larger loops.
+ if ( !$loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent, array( $taxonomy ) ) )
+ return $parent; // No loop
+
+ // Setting $parent to the given value causes a loop.
+ if ( isset( $loop[$term_id] ) )
+ return 0;
+
+ // There's a loop, but it doesn't contain $term_id. Break the loop.
+ foreach ( array_keys( $loop ) as $loop_member )
+ wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
+
+ return $parent;
+}