angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

taxonomy.php (168153B)


      1 <?php
      2 /**
      3  * Core Taxonomy API
      4  *
      5  * @package WordPress
      6  * @subpackage Taxonomy
      7  */
      8 
      9 //
     10 // Taxonomy registration.
     11 //
     12 
     13 /**
     14  * Creates the initial taxonomies.
     15  *
     16  * This function fires twice: in wp-settings.php before plugins are loaded (for
     17  * backward compatibility reasons), and again on the {@see 'init'} action. We must
     18  * avoid registering rewrite rules before the {@see 'init'} action.
     19  *
     20  * @since 2.8.0
     21  *
     22  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
     23  */
     24 function create_initial_taxonomies() {
     25 	global $wp_rewrite;
     26 
     27 	if ( ! did_action( 'init' ) ) {
     28 		$rewrite = array(
     29 			'category'    => false,
     30 			'post_tag'    => false,
     31 			'post_format' => false,
     32 		);
     33 	} else {
     34 
     35 		/**
     36 		 * Filters the post formats rewrite base.
     37 		 *
     38 		 * @since 3.1.0
     39 		 *
     40 		 * @param string $context Context of the rewrite base. Default 'type'.
     41 		 */
     42 		$post_format_base = apply_filters( 'post_format_rewrite_base', 'type' );
     43 		$rewrite          = array(
     44 			'category'    => array(
     45 				'hierarchical' => true,
     46 				'slug'         => get_option( 'category_base' ) ? get_option( 'category_base' ) : 'category',
     47 				'with_front'   => ! get_option( 'category_base' ) || $wp_rewrite->using_index_permalinks(),
     48 				'ep_mask'      => EP_CATEGORIES,
     49 			),
     50 			'post_tag'    => array(
     51 				'hierarchical' => false,
     52 				'slug'         => get_option( 'tag_base' ) ? get_option( 'tag_base' ) : 'tag',
     53 				'with_front'   => ! get_option( 'tag_base' ) || $wp_rewrite->using_index_permalinks(),
     54 				'ep_mask'      => EP_TAGS,
     55 			),
     56 			'post_format' => $post_format_base ? array( 'slug' => $post_format_base ) : false,
     57 		);
     58 	}
     59 
     60 	register_taxonomy(
     61 		'category',
     62 		'post',
     63 		array(
     64 			'hierarchical'          => true,
     65 			'query_var'             => 'category_name',
     66 			'rewrite'               => $rewrite['category'],
     67 			'public'                => true,
     68 			'show_ui'               => true,
     69 			'show_admin_column'     => true,
     70 			'_builtin'              => true,
     71 			'capabilities'          => array(
     72 				'manage_terms' => 'manage_categories',
     73 				'edit_terms'   => 'edit_categories',
     74 				'delete_terms' => 'delete_categories',
     75 				'assign_terms' => 'assign_categories',
     76 			),
     77 			'show_in_rest'          => true,
     78 			'rest_base'             => 'categories',
     79 			'rest_controller_class' => 'WP_REST_Terms_Controller',
     80 		)
     81 	);
     82 
     83 	register_taxonomy(
     84 		'post_tag',
     85 		'post',
     86 		array(
     87 			'hierarchical'          => false,
     88 			'query_var'             => 'tag',
     89 			'rewrite'               => $rewrite['post_tag'],
     90 			'public'                => true,
     91 			'show_ui'               => true,
     92 			'show_admin_column'     => true,
     93 			'_builtin'              => true,
     94 			'capabilities'          => array(
     95 				'manage_terms' => 'manage_post_tags',
     96 				'edit_terms'   => 'edit_post_tags',
     97 				'delete_terms' => 'delete_post_tags',
     98 				'assign_terms' => 'assign_post_tags',
     99 			),
    100 			'show_in_rest'          => true,
    101 			'rest_base'             => 'tags',
    102 			'rest_controller_class' => 'WP_REST_Terms_Controller',
    103 		)
    104 	);
    105 
    106 	register_taxonomy(
    107 		'nav_menu',
    108 		'nav_menu_item',
    109 		array(
    110 			'public'            => false,
    111 			'hierarchical'      => false,
    112 			'labels'            => array(
    113 				'name'          => __( 'Navigation Menus' ),
    114 				'singular_name' => __( 'Navigation Menu' ),
    115 			),
    116 			'query_var'         => false,
    117 			'rewrite'           => false,
    118 			'show_ui'           => false,
    119 			'_builtin'          => true,
    120 			'show_in_nav_menus' => false,
    121 		)
    122 	);
    123 
    124 	register_taxonomy(
    125 		'link_category',
    126 		'link',
    127 		array(
    128 			'hierarchical' => false,
    129 			'labels'       => array(
    130 				'name'                       => __( 'Link Categories' ),
    131 				'singular_name'              => __( 'Link Category' ),
    132 				'search_items'               => __( 'Search Link Categories' ),
    133 				'popular_items'              => null,
    134 				'all_items'                  => __( 'All Link Categories' ),
    135 				'edit_item'                  => __( 'Edit Link Category' ),
    136 				'update_item'                => __( 'Update Link Category' ),
    137 				'add_new_item'               => __( 'Add New Link Category' ),
    138 				'new_item_name'              => __( 'New Link Category Name' ),
    139 				'separate_items_with_commas' => null,
    140 				'add_or_remove_items'        => null,
    141 				'choose_from_most_used'      => null,
    142 				'back_to_items'              => __( '&larr; Go to Link Categories' ),
    143 			),
    144 			'capabilities' => array(
    145 				'manage_terms' => 'manage_links',
    146 				'edit_terms'   => 'manage_links',
    147 				'delete_terms' => 'manage_links',
    148 				'assign_terms' => 'manage_links',
    149 			),
    150 			'query_var'    => false,
    151 			'rewrite'      => false,
    152 			'public'       => false,
    153 			'show_ui'      => true,
    154 			'_builtin'     => true,
    155 		)
    156 	);
    157 
    158 	register_taxonomy(
    159 		'post_format',
    160 		'post',
    161 		array(
    162 			'public'            => true,
    163 			'hierarchical'      => false,
    164 			'labels'            => array(
    165 				'name'          => _x( 'Formats', 'post format' ),
    166 				'singular_name' => _x( 'Format', 'post format' ),
    167 			),
    168 			'query_var'         => true,
    169 			'rewrite'           => $rewrite['post_format'],
    170 			'show_ui'           => false,
    171 			'_builtin'          => true,
    172 			'show_in_nav_menus' => current_theme_supports( 'post-formats' ),
    173 		)
    174 	);
    175 
    176 	register_taxonomy(
    177 		'wp_theme',
    178 		array( 'wp_template' ),
    179 		array(
    180 			'public'            => false,
    181 			'hierarchical'      => false,
    182 			'labels'            => array(
    183 				'name'          => __( 'Themes' ),
    184 				'singular_name' => __( 'Theme' ),
    185 			),
    186 			'query_var'         => false,
    187 			'rewrite'           => false,
    188 			'show_ui'           => false,
    189 			'_builtin'          => true,
    190 			'show_in_nav_menus' => false,
    191 			'show_in_rest'      => false,
    192 		)
    193 	);
    194 }
    195 
    196 /**
    197  * Retrieves a list of registered taxonomy names or objects.
    198  *
    199  * @since 3.0.0
    200  *
    201  * @global array $wp_taxonomies The registered taxonomies.
    202  *
    203  * @param array  $args     Optional. An array of `key => value` arguments to match against the taxonomy objects.
    204  *                         Default empty array.
    205  * @param string $output   Optional. The type of output to return in the array. Accepts either taxonomy 'names'
    206  *                         or 'objects'. Default 'names'.
    207  * @param string $operator Optional. The logical operation to perform. Accepts 'and' or 'or'. 'or' means only
    208  *                         one element from the array needs to match; 'and' means all elements must match.
    209  *                         Default 'and'.
    210  * @return string[]|WP_Taxonomy[] An array of taxonomy names or objects.
    211  */
    212 function get_taxonomies( $args = array(), $output = 'names', $operator = 'and' ) {
    213 	global $wp_taxonomies;
    214 
    215 	$field = ( 'names' === $output ) ? 'name' : false;
    216 
    217 	return wp_filter_object_list( $wp_taxonomies, $args, $operator, $field );
    218 }
    219 
    220 /**
    221  * Return the names or objects of the taxonomies which are registered for the requested object or object type, such as
    222  * a post object or post type name.
    223  *
    224  * Example:
    225  *
    226  *     $taxonomies = get_object_taxonomies( 'post' );
    227  *
    228  * This results in:
    229  *
    230  *     Array( 'category', 'post_tag' )
    231  *
    232  * @since 2.3.0
    233  *
    234  * @global array $wp_taxonomies The registered taxonomies.
    235  *
    236  * @param string|string[]|WP_Post $object Name of the type of taxonomy object, or an object (row from posts)
    237  * @param string                  $output Optional. The type of output to return in the array. Accepts either
    238  *                                        'names' or 'objects'. Default 'names'.
    239  * @return string[]|WP_Taxonomy[] The names or objects of all taxonomies of `$object_type`.
    240  */
    241 function get_object_taxonomies( $object, $output = 'names' ) {
    242 	global $wp_taxonomies;
    243 
    244 	if ( is_object( $object ) ) {
    245 		if ( 'attachment' === $object->post_type ) {
    246 			return get_attachment_taxonomies( $object, $output );
    247 		}
    248 		$object = $object->post_type;
    249 	}
    250 
    251 	$object = (array) $object;
    252 
    253 	$taxonomies = array();
    254 	foreach ( (array) $wp_taxonomies as $tax_name => $tax_obj ) {
    255 		if ( array_intersect( $object, (array) $tax_obj->object_type ) ) {
    256 			if ( 'names' === $output ) {
    257 				$taxonomies[] = $tax_name;
    258 			} else {
    259 				$taxonomies[ $tax_name ] = $tax_obj;
    260 			}
    261 		}
    262 	}
    263 
    264 	return $taxonomies;
    265 }
    266 
    267 /**
    268  * Retrieves the taxonomy object of $taxonomy.
    269  *
    270  * The get_taxonomy function will first check that the parameter string given
    271  * is a taxonomy object and if it is, it will return it.
    272  *
    273  * @since 2.3.0
    274  *
    275  * @global array $wp_taxonomies The registered taxonomies.
    276  *
    277  * @param string $taxonomy Name of taxonomy object to return.
    278  * @return WP_Taxonomy|false The Taxonomy Object or false if $taxonomy doesn't exist.
    279  */
    280 function get_taxonomy( $taxonomy ) {
    281 	global $wp_taxonomies;
    282 
    283 	if ( ! taxonomy_exists( $taxonomy ) ) {
    284 		return false;
    285 	}
    286 
    287 	return $wp_taxonomies[ $taxonomy ];
    288 }
    289 
    290 /**
    291  * Determines whether the taxonomy name exists.
    292  *
    293  * Formerly is_taxonomy(), introduced in 2.3.0.
    294  *
    295  * For more information on this and similar theme functions, check out
    296  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
    297  * Conditional Tags} article in the Theme Developer Handbook.
    298  *
    299  * @since 3.0.0
    300  *
    301  * @global array $wp_taxonomies The registered taxonomies.
    302  *
    303  * @param string $taxonomy Name of taxonomy object.
    304  * @return bool Whether the taxonomy exists.
    305  */
    306 function taxonomy_exists( $taxonomy ) {
    307 	global $wp_taxonomies;
    308 
    309 	return isset( $wp_taxonomies[ $taxonomy ] );
    310 }
    311 
    312 /**
    313  * Determines whether the taxonomy object is hierarchical.
    314  *
    315  * Checks to make sure that the taxonomy is an object first. Then Gets the
    316  * object, and finally returns the hierarchical value in the object.
    317  *
    318  * A false return value might also mean that the taxonomy does not exist.
    319  *
    320  * For more information on this and similar theme functions, check out
    321  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
    322  * Conditional Tags} article in the Theme Developer Handbook.
    323  *
    324  * @since 2.3.0
    325  *
    326  * @param string $taxonomy Name of taxonomy object.
    327  * @return bool Whether the taxonomy is hierarchical.
    328  */
    329 function is_taxonomy_hierarchical( $taxonomy ) {
    330 	if ( ! taxonomy_exists( $taxonomy ) ) {
    331 		return false;
    332 	}
    333 
    334 	$taxonomy = get_taxonomy( $taxonomy );
    335 	return $taxonomy->hierarchical;
    336 }
    337 
    338 /**
    339  * Creates or modifies a taxonomy object.
    340  *
    341  * Note: Do not use before the {@see 'init'} hook.
    342  *
    343  * A simple function for creating or modifying a taxonomy object based on
    344  * the parameters given. If modifying an existing taxonomy object, note
    345  * that the `$object_type` value from the original registration will be
    346  * overwritten.
    347  *
    348  * @since 2.3.0
    349  * @since 4.2.0 Introduced `show_in_quick_edit` argument.
    350  * @since 4.4.0 The `show_ui` argument is now enforced on the term editing screen.
    351  * @since 4.4.0 The `public` argument now controls whether the taxonomy can be queried on the front end.
    352  * @since 4.5.0 Introduced `publicly_queryable` argument.
    353  * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class'
    354  *              arguments to register the Taxonomy in REST API.
    355  * @since 5.1.0 Introduced `meta_box_sanitize_cb` argument.
    356  * @since 5.4.0 Added the registered taxonomy object as a return value.
    357  * @since 5.5.0 Introduced `default_term` argument.
    358  *
    359  * @global array $wp_taxonomies Registered taxonomies.
    360  *
    361  * @param string       $taxonomy    Taxonomy key, must not exceed 32 characters.
    362  * @param array|string $object_type Object type or array of object types with which the taxonomy should be associated.
    363  * @param array|string $args        {
    364  *     Optional. Array or query string of arguments for registering a taxonomy.
    365  *
    366  *     @type string[]      $labels                An array of labels for this taxonomy. By default, Tag labels are
    367  *                                                used for non-hierarchical taxonomies, and Category labels are used
    368  *                                                for hierarchical taxonomies. See accepted values in
    369  *                                                get_taxonomy_labels(). Default empty array.
    370  *     @type string        $description           A short descriptive summary of what the taxonomy is for. Default empty.
    371  *     @type bool          $public                Whether a taxonomy is intended for use publicly either via
    372  *                                                the admin interface or by front-end users. The default settings
    373  *                                                of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
    374  *                                                are inherited from `$public`.
    375  *     @type bool          $publicly_queryable    Whether the taxonomy is publicly queryable.
    376  *                                                If not set, the default is inherited from `$public`
    377  *     @type bool          $hierarchical          Whether the taxonomy is hierarchical. Default false.
    378  *     @type bool          $show_ui               Whether to generate and allow a UI for managing terms in this taxonomy in
    379  *                                                the admin. If not set, the default is inherited from `$public`
    380  *                                                (default true).
    381  *     @type bool          $show_in_menu          Whether to show the taxonomy in the admin menu. If true, the taxonomy is
    382  *                                                shown as a submenu of the object type menu. If false, no menu is shown.
    383  *                                                `$show_ui` must be true. If not set, default is inherited from `$show_ui`
    384  *                                                (default true).
    385  *     @type bool          $show_in_nav_menus     Makes this taxonomy available for selection in navigation menus. If not
    386  *                                                set, the default is inherited from `$public` (default true).
    387  *     @type bool          $show_in_rest          Whether to include the taxonomy in the REST API. Set this to true
    388  *                                                for the taxonomy to be available in the block editor.
    389  *     @type string        $rest_base             To change the base url of REST API route. Default is $taxonomy.
    390  *     @type string        $rest_controller_class REST API Controller class name. Default is 'WP_REST_Terms_Controller'.
    391  *     @type bool          $show_tagcloud         Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
    392  *                                                the default is inherited from `$show_ui` (default true).
    393  *     @type bool          $show_in_quick_edit    Whether to show the taxonomy in the quick/bulk edit panel. It not set,
    394  *                                                the default is inherited from `$show_ui` (default true).
    395  *     @type bool          $show_admin_column     Whether to display a column for the taxonomy on its post type listing
    396  *                                                screens. Default false.
    397  *     @type bool|callable $meta_box_cb           Provide a callback function for the meta box display. If not set,
    398  *                                                post_categories_meta_box() is used for hierarchical taxonomies, and
    399  *                                                post_tags_meta_box() is used for non-hierarchical. If false, no meta
    400  *                                                box is shown.
    401  *     @type callable      $meta_box_sanitize_cb  Callback function for sanitizing taxonomy data saved from a meta
    402  *                                                box. If no callback is defined, an appropriate one is determined
    403  *                                                based on the value of `$meta_box_cb`.
    404  *     @type string[]      $capabilities {
    405  *         Array of capabilities for this taxonomy.
    406  *
    407  *         @type string $manage_terms Default 'manage_categories'.
    408  *         @type string $edit_terms   Default 'manage_categories'.
    409  *         @type string $delete_terms Default 'manage_categories'.
    410  *         @type string $assign_terms Default 'edit_posts'.
    411  *     }
    412  *     @type bool|array    $rewrite {
    413  *         Triggers the handling of rewrites for this taxonomy. Default true, using $taxonomy as slug. To prevent
    414  *         rewrite, set to false. To specify rewrite rules, an array can be passed with any of these keys:
    415  *
    416  *         @type string $slug         Customize the permastruct slug. Default `$taxonomy` key.
    417  *         @type bool   $with_front   Should the permastruct be prepended with WP_Rewrite::$front. Default true.
    418  *         @type bool   $hierarchical Either hierarchical rewrite tag or not. Default false.
    419  *         @type int    $ep_mask      Assign an endpoint mask. Default `EP_NONE`.
    420  *     }
    421  *     @type string|bool   $query_var             Sets the query var key for this taxonomy. Default `$taxonomy` key. If
    422  *                                                false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
    423  *                                                string, the query `?{query_var}={term_slug}` will be valid.
    424  *     @type callable      $update_count_callback Works much like a hook, in that it will be called when the count is
    425  *                                                updated. Default _update_post_term_count() for taxonomies attached
    426  *                                                to post types, which confirms that the objects are published before
    427  *                                                counting them. Default _update_generic_term_count() for taxonomies
    428  *                                                attached to other object types, such as users.
    429  *     @type string|array  $default_term {
    430  *         Default term to be used for the taxonomy.
    431  *
    432  *         @type string $name         Name of default term.
    433  *         @type string $slug         Slug for default term. Default empty.
    434  *         @type string $description  Description for default term. Default empty.
    435  *     }
    436  *     @type bool          $sort                  Whether terms in this taxonomy should be sorted in the order they are
    437  *                                                provided to `wp_set_object_terms()`. Default null which equates to false.
    438  *     @type array         $args                  Array of arguments to automatically use inside `wp_get_object_terms()`
    439  *                                                for this taxonomy.
    440  *     @type bool          $_builtin              This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
    441  *                                                Default false.
    442  * }
    443  * @return WP_Taxonomy|WP_Error The registered taxonomy object on success, WP_Error object on failure.
    444  */
    445 function register_taxonomy( $taxonomy, $object_type, $args = array() ) {
    446 	global $wp_taxonomies;
    447 
    448 	if ( ! is_array( $wp_taxonomies ) ) {
    449 		$wp_taxonomies = array();
    450 	}
    451 
    452 	$args = wp_parse_args( $args );
    453 
    454 	if ( empty( $taxonomy ) || strlen( $taxonomy ) > 32 ) {
    455 		_doing_it_wrong( __FUNCTION__, __( 'Taxonomy names must be between 1 and 32 characters in length.' ), '4.2.0' );
    456 		return new WP_Error( 'taxonomy_length_invalid', __( 'Taxonomy names must be between 1 and 32 characters in length.' ) );
    457 	}
    458 
    459 	$taxonomy_object = new WP_Taxonomy( $taxonomy, $object_type, $args );
    460 	$taxonomy_object->add_rewrite_rules();
    461 
    462 	$wp_taxonomies[ $taxonomy ] = $taxonomy_object;
    463 
    464 	$taxonomy_object->add_hooks();
    465 
    466 	// Add default term.
    467 	if ( ! empty( $taxonomy_object->default_term ) ) {
    468 		$term = term_exists( $taxonomy_object->default_term['name'], $taxonomy );
    469 		if ( $term ) {
    470 			update_option( 'default_term_' . $taxonomy_object->name, $term['term_id'] );
    471 		} else {
    472 			$term = wp_insert_term(
    473 				$taxonomy_object->default_term['name'],
    474 				$taxonomy,
    475 				array(
    476 					'slug'        => sanitize_title( $taxonomy_object->default_term['slug'] ),
    477 					'description' => $taxonomy_object->default_term['description'],
    478 				)
    479 			);
    480 
    481 			// Update `term_id` in options.
    482 			if ( ! is_wp_error( $term ) ) {
    483 				update_option( 'default_term_' . $taxonomy_object->name, $term['term_id'] );
    484 			}
    485 		}
    486 	}
    487 
    488 	/**
    489 	 * Fires after a taxonomy is registered.
    490 	 *
    491 	 * @since 3.3.0
    492 	 *
    493 	 * @param string       $taxonomy    Taxonomy slug.
    494 	 * @param array|string $object_type Object type or array of object types.
    495 	 * @param array        $args        Array of taxonomy registration arguments.
    496 	 */
    497 	do_action( 'registered_taxonomy', $taxonomy, $object_type, (array) $taxonomy_object );
    498 
    499 	return $taxonomy_object;
    500 }
    501 
    502 /**
    503  * Unregisters a taxonomy.
    504  *
    505  * Can not be used to unregister built-in taxonomies.
    506  *
    507  * @since 4.5.0
    508  *
    509  * @global WP    $wp            Current WordPress environment instance.
    510  * @global array $wp_taxonomies List of taxonomies.
    511  *
    512  * @param string $taxonomy Taxonomy name.
    513  * @return true|WP_Error True on success, WP_Error on failure or if the taxonomy doesn't exist.
    514  */
    515 function unregister_taxonomy( $taxonomy ) {
    516 	if ( ! taxonomy_exists( $taxonomy ) ) {
    517 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    518 	}
    519 
    520 	$taxonomy_object = get_taxonomy( $taxonomy );
    521 
    522 	// Do not allow unregistering internal taxonomies.
    523 	if ( $taxonomy_object->_builtin ) {
    524 		return new WP_Error( 'invalid_taxonomy', __( 'Unregistering a built-in taxonomy is not allowed.' ) );
    525 	}
    526 
    527 	global $wp_taxonomies;
    528 
    529 	$taxonomy_object->remove_rewrite_rules();
    530 	$taxonomy_object->remove_hooks();
    531 
    532 	// Remove custom taxonomy default term option.
    533 	if ( ! empty( $taxonomy_object->default_term ) ) {
    534 		delete_option( 'default_term_' . $taxonomy_object->name );
    535 	}
    536 
    537 	// Remove the taxonomy.
    538 	unset( $wp_taxonomies[ $taxonomy ] );
    539 
    540 	/**
    541 	 * Fires after a taxonomy is unregistered.
    542 	 *
    543 	 * @since 4.5.0
    544 	 *
    545 	 * @param string $taxonomy Taxonomy name.
    546 	 */
    547 	do_action( 'unregistered_taxonomy', $taxonomy );
    548 
    549 	return true;
    550 }
    551 
    552 /**
    553  * Builds an object with all taxonomy labels out of a taxonomy object.
    554  *
    555  * @since 3.0.0
    556  * @since 4.3.0 Added the `no_terms` label.
    557  * @since 4.4.0 Added the `items_list_navigation` and `items_list` labels.
    558  * @since 4.9.0 Added the `most_used` and `back_to_items` labels.
    559  * @since 5.7.0 Added the `filter_by_item` label.
    560  * @since 5.8.0 Added the `item_link` and `item_link_description` labels.
    561  *
    562  * @param WP_Taxonomy $tax Taxonomy object.
    563  * @return object {
    564  *     Taxonomy labels object. The first default value is for non-hierarchical taxonomies
    565  *     (like tags) and the second one is for hierarchical taxonomies (like categories).
    566  *
    567  *     @type string $name                       General name for the taxonomy, usually plural. The same
    568  *                                              as and overridden by `$tax->label`. Default 'Tags'/'Categories'.
    569  *     @type string $singular_name              Name for one object of this taxonomy. Default 'Tag'/'Category'.
    570  *     @type string $search_items               Default 'Search Tags'/'Search Categories'.
    571  *     @type string $popular_items              This label is only used for non-hierarchical taxonomies.
    572  *                                              Default 'Popular Tags'.
    573  *     @type string $all_items                  Default 'All Tags'/'All Categories'.
    574  *     @type string $parent_item                This label is only used for hierarchical taxonomies. Default
    575  *                                              'Parent Category'.
    576  *     @type string $parent_item_colon          The same as `parent_item`, but with colon `:` in the end.
    577  *     @type string $edit_item                  Default 'Edit Tag'/'Edit Category'.
    578  *     @type string $view_item                  Default 'View Tag'/'View Category'.
    579  *     @type string $update_item                Default 'Update Tag'/'Update Category'.
    580  *     @type string $add_new_item               Default 'Add New Tag'/'Add New Category'.
    581  *     @type string $new_item_name              Default 'New Tag Name'/'New Category Name'.
    582  *     @type string $separate_items_with_commas This label is only used for non-hierarchical taxonomies. Default
    583  *                                              'Separate tags with commas', used in the meta box.
    584  *     @type string $add_or_remove_items        This label is only used for non-hierarchical taxonomies. Default
    585  *                                              'Add or remove tags', used in the meta box when JavaScript
    586  *                                              is disabled.
    587  *     @type string $choose_from_most_used      This label is only used on non-hierarchical taxonomies. Default
    588  *                                              'Choose from the most used tags', used in the meta box.
    589  *     @type string $not_found                  Default 'No tags found'/'No categories found', used in
    590  *                                              the meta box and taxonomy list table.
    591  *     @type string $no_terms                   Default 'No tags'/'No categories', used in the posts and media
    592  *                                              list tables.
    593  *     @type string $filter_by_item             This label is only used for hierarchical taxonomies. Default
    594  *                                              'Filter by category', used in the posts list table.
    595  *     @type string $items_list_navigation      Label for the table pagination hidden heading.
    596  *     @type string $items_list                 Label for the table hidden heading.
    597  *     @type string $most_used                  Title for the Most Used tab. Default 'Most Used'.
    598  *     @type string $back_to_items              Label displayed after a term has been updated.
    599  *     @type string $item_link                  Used in the block editor. Title for a navigation link block variation.
    600  *                                              Default 'Tag Link'/'Category Link'.
    601  *     @type string $item_link_description      Used in the block editor. Description for a navigation link block
    602  *                                              variation. Default 'A link to a tag'/'A link to a category'.
    603  * }
    604  */
    605 function get_taxonomy_labels( $tax ) {
    606 	$tax->labels = (array) $tax->labels;
    607 
    608 	if ( isset( $tax->helps ) && empty( $tax->labels['separate_items_with_commas'] ) ) {
    609 		$tax->labels['separate_items_with_commas'] = $tax->helps;
    610 	}
    611 
    612 	if ( isset( $tax->no_tagcloud ) && empty( $tax->labels['not_found'] ) ) {
    613 		$tax->labels['not_found'] = $tax->no_tagcloud;
    614 	}
    615 
    616 	$nohier_vs_hier_defaults = array(
    617 		'name'                       => array( _x( 'Tags', 'taxonomy general name' ), _x( 'Categories', 'taxonomy general name' ) ),
    618 		'singular_name'              => array( _x( 'Tag', 'taxonomy singular name' ), _x( 'Category', 'taxonomy singular name' ) ),
    619 		'search_items'               => array( __( 'Search Tags' ), __( 'Search Categories' ) ),
    620 		'popular_items'              => array( __( 'Popular Tags' ), null ),
    621 		'all_items'                  => array( __( 'All Tags' ), __( 'All Categories' ) ),
    622 		'parent_item'                => array( null, __( 'Parent Category' ) ),
    623 		'parent_item_colon'          => array( null, __( 'Parent Category:' ) ),
    624 		'edit_item'                  => array( __( 'Edit Tag' ), __( 'Edit Category' ) ),
    625 		'view_item'                  => array( __( 'View Tag' ), __( 'View Category' ) ),
    626 		'update_item'                => array( __( 'Update Tag' ), __( 'Update Category' ) ),
    627 		'add_new_item'               => array( __( 'Add New Tag' ), __( 'Add New Category' ) ),
    628 		'new_item_name'              => array( __( 'New Tag Name' ), __( 'New Category Name' ) ),
    629 		'separate_items_with_commas' => array( __( 'Separate tags with commas' ), null ),
    630 		'add_or_remove_items'        => array( __( 'Add or remove tags' ), null ),
    631 		'choose_from_most_used'      => array( __( 'Choose from the most used tags' ), null ),
    632 		'not_found'                  => array( __( 'No tags found.' ), __( 'No categories found.' ) ),
    633 		'no_terms'                   => array( __( 'No tags' ), __( 'No categories' ) ),
    634 		'filter_by_item'             => array( null, __( 'Filter by category' ) ),
    635 		'items_list_navigation'      => array( __( 'Tags list navigation' ), __( 'Categories list navigation' ) ),
    636 		'items_list'                 => array( __( 'Tags list' ), __( 'Categories list' ) ),
    637 		/* translators: Tab heading when selecting from the most used terms. */
    638 		'most_used'                  => array( _x( 'Most Used', 'tags' ), _x( 'Most Used', 'categories' ) ),
    639 		'back_to_items'              => array( __( '&larr; Go to Tags' ), __( '&larr; Go to Categories' ) ),
    640 		'item_link'                  => array(
    641 			_x( 'Tag Link', 'navigation link block title' ),
    642 			_x( 'Category Link', 'navigation link block description' ),
    643 		),
    644 		'item_link_description'      => array(
    645 			_x( 'A link to a tag.', 'navigation link block description' ),
    646 			_x( 'A link to a category.', 'navigation link block description' ),
    647 		),
    648 	);
    649 
    650 	$nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
    651 
    652 	$labels = _get_custom_object_labels( $tax, $nohier_vs_hier_defaults );
    653 
    654 	$taxonomy = $tax->name;
    655 
    656 	$default_labels = clone $labels;
    657 
    658 	/**
    659 	 * Filters the labels of a specific taxonomy.
    660 	 *
    661 	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
    662 	 *
    663 	 * Possible hook names include:
    664 	 *
    665 	 *  - `taxonomy_labels_category`
    666 	 *  - `taxonomy_labels_post_tag`
    667 	 *
    668 	 * @since 4.4.0
    669 	 *
    670 	 * @see get_taxonomy_labels() for the full list of taxonomy labels.
    671 	 *
    672 	 * @param object $labels Object with labels for the taxonomy as member variables.
    673 	 */
    674 	$labels = apply_filters( "taxonomy_labels_{$taxonomy}", $labels );
    675 
    676 	// Ensure that the filtered labels contain all required default values.
    677 	$labels = (object) array_merge( (array) $default_labels, (array) $labels );
    678 
    679 	return $labels;
    680 }
    681 
    682 /**
    683  * Add an already registered taxonomy to an object type.
    684  *
    685  * @since 3.0.0
    686  *
    687  * @global array $wp_taxonomies The registered taxonomies.
    688  *
    689  * @param string $taxonomy    Name of taxonomy object.
    690  * @param string $object_type Name of the object type.
    691  * @return bool True if successful, false if not.
    692  */
    693 function register_taxonomy_for_object_type( $taxonomy, $object_type ) {
    694 	global $wp_taxonomies;
    695 
    696 	if ( ! isset( $wp_taxonomies[ $taxonomy ] ) ) {
    697 		return false;
    698 	}
    699 
    700 	if ( ! get_post_type_object( $object_type ) ) {
    701 		return false;
    702 	}
    703 
    704 	if ( ! in_array( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true ) ) {
    705 		$wp_taxonomies[ $taxonomy ]->object_type[] = $object_type;
    706 	}
    707 
    708 	// Filter out empties.
    709 	$wp_taxonomies[ $taxonomy ]->object_type = array_filter( $wp_taxonomies[ $taxonomy ]->object_type );
    710 
    711 	/**
    712 	 * Fires after a taxonomy is registered for an object type.
    713 	 *
    714 	 * @since 5.1.0
    715 	 *
    716 	 * @param string $taxonomy    Taxonomy name.
    717 	 * @param string $object_type Name of the object type.
    718 	 */
    719 	do_action( 'registered_taxonomy_for_object_type', $taxonomy, $object_type );
    720 
    721 	return true;
    722 }
    723 
    724 /**
    725  * Remove an already registered taxonomy from an object type.
    726  *
    727  * @since 3.7.0
    728  *
    729  * @global array $wp_taxonomies The registered taxonomies.
    730  *
    731  * @param string $taxonomy    Name of taxonomy object.
    732  * @param string $object_type Name of the object type.
    733  * @return bool True if successful, false if not.
    734  */
    735 function unregister_taxonomy_for_object_type( $taxonomy, $object_type ) {
    736 	global $wp_taxonomies;
    737 
    738 	if ( ! isset( $wp_taxonomies[ $taxonomy ] ) ) {
    739 		return false;
    740 	}
    741 
    742 	if ( ! get_post_type_object( $object_type ) ) {
    743 		return false;
    744 	}
    745 
    746 	$key = array_search( $object_type, $wp_taxonomies[ $taxonomy ]->object_type, true );
    747 	if ( false === $key ) {
    748 		return false;
    749 	}
    750 
    751 	unset( $wp_taxonomies[ $taxonomy ]->object_type[ $key ] );
    752 
    753 	/**
    754 	 * Fires after a taxonomy is unregistered for an object type.
    755 	 *
    756 	 * @since 5.1.0
    757 	 *
    758 	 * @param string $taxonomy    Taxonomy name.
    759 	 * @param string $object_type Name of the object type.
    760 	 */
    761 	do_action( 'unregistered_taxonomy_for_object_type', $taxonomy, $object_type );
    762 
    763 	return true;
    764 }
    765 
    766 //
    767 // Term API.
    768 //
    769 
    770 /**
    771  * Retrieve object_ids of valid taxonomy and term.
    772  *
    773  * The strings of $taxonomies must exist before this function will continue.
    774  * On failure of finding a valid taxonomy, it will return a WP_Error class,
    775  * kind of like Exceptions in PHP 5, except you can't catch them. Even so,
    776  * you can still test for the WP_Error class and get the error message.
    777  *
    778  * The $terms aren't checked the same as $taxonomies, but still need to exist
    779  * for $object_ids to be returned.
    780  *
    781  * It is possible to change the order that object_ids is returned by either
    782  * using PHP sort family functions or using the database by using $args with
    783  * either ASC or DESC array. The value should be in the key named 'order'.
    784  *
    785  * @since 2.3.0
    786  *
    787  * @global wpdb $wpdb WordPress database abstraction object.
    788  *
    789  * @param int|array    $term_ids   Term ID or array of term IDs of terms that will be used.
    790  * @param string|array $taxonomies String of taxonomy name or Array of string values of taxonomy names.
    791  * @param array|string $args       Change the order of the object_ids, either ASC or DESC.
    792  * @return array|WP_Error An array of $object_ids on success, WP_Error if the taxonomy does not exist.
    793  */
    794 function get_objects_in_term( $term_ids, $taxonomies, $args = array() ) {
    795 	global $wpdb;
    796 
    797 	if ( ! is_array( $term_ids ) ) {
    798 		$term_ids = array( $term_ids );
    799 	}
    800 	if ( ! is_array( $taxonomies ) ) {
    801 		$taxonomies = array( $taxonomies );
    802 	}
    803 	foreach ( (array) $taxonomies as $taxonomy ) {
    804 		if ( ! taxonomy_exists( $taxonomy ) ) {
    805 			return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    806 		}
    807 	}
    808 
    809 	$defaults = array( 'order' => 'ASC' );
    810 	$args     = wp_parse_args( $args, $defaults );
    811 
    812 	$order = ( 'desc' === strtolower( $args['order'] ) ) ? 'DESC' : 'ASC';
    813 
    814 	$term_ids = array_map( 'intval', $term_ids );
    815 
    816 	$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
    817 	$term_ids   = "'" . implode( "', '", $term_ids ) . "'";
    818 
    819 	$sql = "SELECT tr.object_id FROM $wpdb->term_relationships AS tr INNER JOIN $wpdb->term_taxonomy AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE tt.taxonomy IN ($taxonomies) AND tt.term_id IN ($term_ids) ORDER BY tr.object_id $order";
    820 
    821 	$last_changed = wp_cache_get_last_changed( 'terms' );
    822 	$cache_key    = 'get_objects_in_term:' . md5( $sql ) . ":$last_changed";
    823 	$cache        = wp_cache_get( $cache_key, 'terms' );
    824 	if ( false === $cache ) {
    825 		$object_ids = $wpdb->get_col( $sql );
    826 		wp_cache_set( $cache_key, $object_ids, 'terms' );
    827 	} else {
    828 		$object_ids = (array) $cache;
    829 	}
    830 
    831 	if ( ! $object_ids ) {
    832 		return array();
    833 	}
    834 	return $object_ids;
    835 }
    836 
    837 /**
    838  * Given a taxonomy query, generates SQL to be appended to a main query.
    839  *
    840  * @since 3.1.0
    841  *
    842  * @see WP_Tax_Query
    843  *
    844  * @param array  $tax_query         A compact tax query
    845  * @param string $primary_table
    846  * @param string $primary_id_column
    847  * @return array
    848  */
    849 function get_tax_sql( $tax_query, $primary_table, $primary_id_column ) {
    850 	$tax_query_obj = new WP_Tax_Query( $tax_query );
    851 	return $tax_query_obj->get_sql( $primary_table, $primary_id_column );
    852 }
    853 
    854 /**
    855  * Get all Term data from database by Term ID.
    856  *
    857  * The usage of the get_term function is to apply filters to a term object. It
    858  * is possible to get a term object from the database before applying the
    859  * filters.
    860  *
    861  * $term ID must be part of $taxonomy, to get from the database. Failure, might
    862  * be able to be captured by the hooks. Failure would be the same value as $wpdb
    863  * returns for the get_row method.
    864  *
    865  * There are two hooks, one is specifically for each term, named 'get_term', and
    866  * the second is for the taxonomy name, 'term_$taxonomy'. Both hooks gets the
    867  * term object, and the taxonomy name as parameters. Both hooks are expected to
    868  * return a Term object.
    869  *
    870  * {@see 'get_term'} hook - Takes two parameters the term Object and the taxonomy name.
    871  * Must return term object. Used in get_term() as a catch-all filter for every
    872  * $term.
    873  *
    874  * {@see 'get_$taxonomy'} hook - Takes two parameters the term Object and the taxonomy
    875  * name. Must return term object. $taxonomy will be the taxonomy name, so for
    876  * example, if 'category', it would be 'get_category' as the filter name. Useful
    877  * for custom taxonomies or plugging into default taxonomies.
    878  *
    879  * @todo Better formatting for DocBlock
    880  *
    881  * @since 2.3.0
    882  * @since 4.4.0 Converted to return a WP_Term object if `$output` is `OBJECT`.
    883  *              The `$taxonomy` parameter was made optional.
    884  *
    885  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
    886  *
    887  * @param int|WP_Term|object $term     If integer, term data will be fetched from the database,
    888  *                                     or from the cache if available.
    889  *                                     If stdClass object (as in the results of a database query),
    890  *                                     will apply filters and return a `WP_Term` object with the `$term` data.
    891  *                                     If `WP_Term`, will return `$term`.
    892  * @param string             $taxonomy Optional. Taxonomy name that `$term` is part of.
    893  * @param string             $output   Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
    894  *                                     correspond to a WP_Term object, an associative array, or a numeric array,
    895  *                                     respectively. Default OBJECT.
    896  * @param string             $filter   Optional. How to sanitize term fields. Default 'raw'.
    897  * @return WP_Term|array|WP_Error|null WP_Term instance (or array) on success, depending on the `$output` value.
    898  *                                     WP_Error if `$taxonomy` does not exist. Null for miscellaneous failure.
    899  */
    900 function get_term( $term, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
    901 	if ( empty( $term ) ) {
    902 		return new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
    903 	}
    904 
    905 	if ( $taxonomy && ! taxonomy_exists( $taxonomy ) ) {
    906 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    907 	}
    908 
    909 	if ( $term instanceof WP_Term ) {
    910 		$_term = $term;
    911 	} elseif ( is_object( $term ) ) {
    912 		if ( empty( $term->filter ) || 'raw' === $term->filter ) {
    913 			$_term = sanitize_term( $term, $taxonomy, 'raw' );
    914 			$_term = new WP_Term( $_term );
    915 		} else {
    916 			$_term = WP_Term::get_instance( $term->term_id );
    917 		}
    918 	} else {
    919 		$_term = WP_Term::get_instance( $term, $taxonomy );
    920 	}
    921 
    922 	if ( is_wp_error( $_term ) ) {
    923 		return $_term;
    924 	} elseif ( ! $_term ) {
    925 		return null;
    926 	}
    927 
    928 	// Ensure for filters that this is not empty.
    929 	$taxonomy = $_term->taxonomy;
    930 
    931 	/**
    932 	 * Filters a taxonomy term object.
    933 	 *
    934 	 * The {@see 'get_$taxonomy'} hook is also available for targeting a specific
    935 	 * taxonomy.
    936 	 *
    937 	 * @since 2.3.0
    938 	 * @since 4.4.0 `$_term` is now a `WP_Term` object.
    939 	 *
    940 	 * @param WP_Term $_term    Term object.
    941 	 * @param string  $taxonomy The taxonomy slug.
    942 	 */
    943 	$_term = apply_filters( 'get_term', $_term, $taxonomy );
    944 
    945 	/**
    946 	 * Filters a taxonomy term object.
    947 	 *
    948 	 * The dynamic portion of the filter name, `$taxonomy`, refers
    949 	 * to the slug of the term's taxonomy.
    950 	 *
    951 	 * @since 2.3.0
    952 	 * @since 4.4.0 `$_term` is now a `WP_Term` object.
    953 	 *
    954 	 * @param WP_Term $_term    Term object.
    955 	 * @param string  $taxonomy The taxonomy slug.
    956 	 */
    957 	$_term = apply_filters( "get_{$taxonomy}", $_term, $taxonomy );
    958 
    959 	// Bail if a filter callback has changed the type of the `$_term` object.
    960 	if ( ! ( $_term instanceof WP_Term ) ) {
    961 		return $_term;
    962 	}
    963 
    964 	// Sanitize term, according to the specified filter.
    965 	$_term->filter( $filter );
    966 
    967 	if ( ARRAY_A === $output ) {
    968 		return $_term->to_array();
    969 	} elseif ( ARRAY_N === $output ) {
    970 		return array_values( $_term->to_array() );
    971 	}
    972 
    973 	return $_term;
    974 }
    975 
    976 /**
    977  * Get all Term data from database by Term field and data.
    978  *
    979  * Warning: $value is not escaped for 'name' $field. You must do it yourself, if
    980  * required.
    981  *
    982  * The default $field is 'id', therefore it is possible to also use null for
    983  * field, but not recommended that you do so.
    984  *
    985  * If $value does not exist, the return value will be false. If $taxonomy exists
    986  * and $field and $value combinations exist, the Term will be returned.
    987  *
    988  * This function will always return the first term that matches the `$field`-
    989  * `$value`-`$taxonomy` combination specified in the parameters. If your query
    990  * is likely to match more than one term (as is likely to be the case when
    991  * `$field` is 'name', for example), consider using get_terms() instead; that
    992  * way, you will get all matching terms, and can provide your own logic for
    993  * deciding which one was intended.
    994  *
    995  * @todo Better formatting for DocBlock.
    996  *
    997  * @since 2.3.0
    998  * @since 4.4.0 `$taxonomy` is optional if `$field` is 'term_taxonomy_id'. Converted to return
    999  *              a WP_Term object if `$output` is `OBJECT`.
   1000  * @since 5.5.0 Added 'ID' as an alias of 'id' for the `$field` parameter.
   1001  *
   1002  * @see sanitize_term_field() The $context param lists the available values for get_term_by() $filter param.
   1003  *
   1004  * @param string     $field    Either 'slug', 'name', 'id' or 'ID' (term_id), or 'term_taxonomy_id'.
   1005  * @param string|int $value    Search for this term value.
   1006  * @param string     $taxonomy Taxonomy name. Optional, if `$field` is 'term_taxonomy_id'.
   1007  * @param string     $output   Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
   1008  *                             correspond to a WP_Term object, an associative array, or a numeric array,
   1009  *                             respectively. Default OBJECT.
   1010  * @param string     $filter   Optional. How to sanitize term fields. Default 'raw'.
   1011  * @return WP_Term|array|false WP_Term instance (or array) on success, depending on the `$output` value.
   1012  *                             False if `$taxonomy` does not exist or `$term` was not found.
   1013  */
   1014 function get_term_by( $field, $value, $taxonomy = '', $output = OBJECT, $filter = 'raw' ) {
   1015 
   1016 	// 'term_taxonomy_id' lookups don't require taxonomy checks.
   1017 	if ( 'term_taxonomy_id' !== $field && ! taxonomy_exists( $taxonomy ) ) {
   1018 		return false;
   1019 	}
   1020 
   1021 	// No need to perform a query for empty 'slug' or 'name'.
   1022 	if ( 'slug' === $field || 'name' === $field ) {
   1023 		$value = (string) $value;
   1024 
   1025 		if ( 0 === strlen( $value ) ) {
   1026 			return false;
   1027 		}
   1028 	}
   1029 
   1030 	if ( 'id' === $field || 'ID' === $field || 'term_id' === $field ) {
   1031 		$term = get_term( (int) $value, $taxonomy, $output, $filter );
   1032 		if ( is_wp_error( $term ) || null === $term ) {
   1033 			$term = false;
   1034 		}
   1035 		return $term;
   1036 	}
   1037 
   1038 	$args = array(
   1039 		'get'                    => 'all',
   1040 		'number'                 => 1,
   1041 		'taxonomy'               => $taxonomy,
   1042 		'update_term_meta_cache' => false,
   1043 		'orderby'                => 'none',
   1044 		'suppress_filter'        => true,
   1045 	);
   1046 
   1047 	switch ( $field ) {
   1048 		case 'slug':
   1049 			$args['slug'] = $value;
   1050 			break;
   1051 		case 'name':
   1052 			$args['name'] = $value;
   1053 			break;
   1054 		case 'term_taxonomy_id':
   1055 			$args['term_taxonomy_id'] = $value;
   1056 			unset( $args['taxonomy'] );
   1057 			break;
   1058 		default:
   1059 			return false;
   1060 	}
   1061 
   1062 	$terms = get_terms( $args );
   1063 	if ( is_wp_error( $terms ) || empty( $terms ) ) {
   1064 		return false;
   1065 	}
   1066 
   1067 	$term = array_shift( $terms );
   1068 
   1069 	// In the case of 'term_taxonomy_id', override the provided `$taxonomy` with whatever we find in the DB.
   1070 	if ( 'term_taxonomy_id' === $field ) {
   1071 		$taxonomy = $term->taxonomy;
   1072 	}
   1073 
   1074 	return get_term( $term, $taxonomy, $output, $filter );
   1075 }
   1076 
   1077 /**
   1078  * Merge all term children into a single array of their IDs.
   1079  *
   1080  * This recursive function will merge all of the children of $term into the same
   1081  * array of term IDs. Only useful for taxonomies which are hierarchical.
   1082  *
   1083  * Will return an empty array if $term does not exist in $taxonomy.
   1084  *
   1085  * @since 2.3.0
   1086  *
   1087  * @param int    $term_id  ID of Term to get children.
   1088  * @param string $taxonomy Taxonomy Name.
   1089  * @return array|WP_Error List of Term IDs. WP_Error returned if `$taxonomy` does not exist.
   1090  */
   1091 function get_term_children( $term_id, $taxonomy ) {
   1092 	if ( ! taxonomy_exists( $taxonomy ) ) {
   1093 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   1094 	}
   1095 
   1096 	$term_id = (int) $term_id;
   1097 
   1098 	$terms = _get_term_hierarchy( $taxonomy );
   1099 
   1100 	if ( ! isset( $terms[ $term_id ] ) ) {
   1101 		return array();
   1102 	}
   1103 
   1104 	$children = $terms[ $term_id ];
   1105 
   1106 	foreach ( (array) $terms[ $term_id ] as $child ) {
   1107 		if ( $term_id === $child ) {
   1108 			continue;
   1109 		}
   1110 
   1111 		if ( isset( $terms[ $child ] ) ) {
   1112 			$children = array_merge( $children, get_term_children( $child, $taxonomy ) );
   1113 		}
   1114 	}
   1115 
   1116 	return $children;
   1117 }
   1118 
   1119 /**
   1120  * Get sanitized Term field.
   1121  *
   1122  * The function is for contextual reasons and for simplicity of usage.
   1123  *
   1124  * @since 2.3.0
   1125  * @since 4.4.0 The `$taxonomy` parameter was made optional. `$term` can also now accept a WP_Term object.
   1126  *
   1127  * @see sanitize_term_field()
   1128  *
   1129  * @param string      $field    Term field to fetch.
   1130  * @param int|WP_Term $term     Term ID or object.
   1131  * @param string      $taxonomy Optional. Taxonomy Name. Default empty.
   1132  * @param string      $context  Optional. How to sanitize term fields. Look at sanitize_term_field() for available options.
   1133  *                              Default 'display'.
   1134  * @return string|int|null|WP_Error Will return an empty string if $term is not an object or if $field is not set in $term.
   1135  */
   1136 function get_term_field( $field, $term, $taxonomy = '', $context = 'display' ) {
   1137 	$term = get_term( $term, $taxonomy );
   1138 	if ( is_wp_error( $term ) ) {
   1139 		return $term;
   1140 	}
   1141 
   1142 	if ( ! is_object( $term ) ) {
   1143 		return '';
   1144 	}
   1145 
   1146 	if ( ! isset( $term->$field ) ) {
   1147 		return '';
   1148 	}
   1149 
   1150 	return sanitize_term_field( $field, $term->$field, $term->term_id, $term->taxonomy, $context );
   1151 }
   1152 
   1153 /**
   1154  * Sanitizes Term for editing.
   1155  *
   1156  * Return value is sanitize_term() and usage is for sanitizing the term for
   1157  * editing. Function is for contextual and simplicity.
   1158  *
   1159  * @since 2.3.0
   1160  *
   1161  * @param int|object $id       Term ID or object.
   1162  * @param string     $taxonomy Taxonomy name.
   1163  * @return string|int|null|WP_Error Will return empty string if $term is not an object.
   1164  */
   1165 function get_term_to_edit( $id, $taxonomy ) {
   1166 	$term = get_term( $id, $taxonomy );
   1167 
   1168 	if ( is_wp_error( $term ) ) {
   1169 		return $term;
   1170 	}
   1171 
   1172 	if ( ! is_object( $term ) ) {
   1173 		return '';
   1174 	}
   1175 
   1176 	return sanitize_term( $term, $taxonomy, 'edit' );
   1177 }
   1178 
   1179 /**
   1180  * Retrieves the terms in a given taxonomy or list of taxonomies.
   1181  *
   1182  * You can fully inject any customizations to the query before it is sent, as
   1183  * well as control the output with a filter.
   1184  *
   1185  * The return type varies depending on the value passed to `$args['fields']`. See
   1186  * WP_Term_Query::get_terms() for details. In all cases, a `WP_Error` object will
   1187  * be returned if an invalid taxonomy is requested.
   1188  *
   1189  * The {@see 'get_terms'} filter will be called when the cache has the term and will
   1190  * pass the found term along with the array of $taxonomies and array of $args.
   1191  * This filter is also called before the array of terms is passed and will pass
   1192  * the array of terms, along with the $taxonomies and $args.
   1193  *
   1194  * The {@see 'list_terms_exclusions'} filter passes the compiled exclusions along with
   1195  * the $args.
   1196  *
   1197  * The {@see 'get_terms_orderby'} filter passes the `ORDER BY` clause for the query
   1198  * along with the $args array.
   1199  *
   1200  * Prior to 4.5.0, the first parameter of `get_terms()` was a taxonomy or list of taxonomies:
   1201  *
   1202  *     $terms = get_terms( 'post_tag', array(
   1203  *         'hide_empty' => false,
   1204  *     ) );
   1205  *
   1206  * Since 4.5.0, taxonomies should be passed via the 'taxonomy' argument in the `$args` array:
   1207  *
   1208  *     $terms = get_terms( array(
   1209  *         'taxonomy' => 'post_tag',
   1210  *         'hide_empty' => false,
   1211  *     ) );
   1212  *
   1213  * @since 2.3.0
   1214  * @since 4.2.0 Introduced 'name' and 'childless' parameters.
   1215  * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
   1216  *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters. Converted to return
   1217  *              a list of WP_Term objects.
   1218  * @since 4.5.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
   1219  *              Introduced 'meta_key' and 'meta_value' parameters. Introduced the ability to order results by metadata.
   1220  * @since 4.8.0 Introduced 'suppress_filter' parameter.
   1221  *
   1222  * @internal The `$deprecated` parameter is parsed for backward compatibility only.
   1223  *
   1224  * @param array|string $args       Optional. Array or string of arguments. See WP_Term_Query::__construct()
   1225  *                                 for information on accepted arguments. Default empty array.
   1226  * @param array|string $deprecated Optional. Argument array, when using the legacy function parameter format.
   1227  *                                 If present, this parameter will be interpreted as `$args`, and the first
   1228  *                                 function parameter will be parsed as a taxonomy or array of taxonomies.
   1229  *                                 Default empty.
   1230  * @return WP_Term[]|int[]|string[]|string|WP_Error Array of terms, a count thereof as a numeric string,
   1231  *                                                  or WP_Error if any of the taxonomies do not exist.
   1232  *                                                  See the function description for more information.
   1233  */
   1234 function get_terms( $args = array(), $deprecated = '' ) {
   1235 	$term_query = new WP_Term_Query();
   1236 
   1237 	$defaults = array(
   1238 		'suppress_filter' => false,
   1239 	);
   1240 
   1241 	/*
   1242 	 * Legacy argument format ($taxonomy, $args) takes precedence.
   1243 	 *
   1244 	 * We detect legacy argument format by checking if
   1245 	 * (a) a second non-empty parameter is passed, or
   1246 	 * (b) the first parameter shares no keys with the default array (ie, it's a list of taxonomies)
   1247 	 */
   1248 	$_args          = wp_parse_args( $args );
   1249 	$key_intersect  = array_intersect_key( $term_query->query_var_defaults, (array) $_args );
   1250 	$do_legacy_args = $deprecated || empty( $key_intersect );
   1251 
   1252 	if ( $do_legacy_args ) {
   1253 		$taxonomies       = (array) $args;
   1254 		$args             = wp_parse_args( $deprecated, $defaults );
   1255 		$args['taxonomy'] = $taxonomies;
   1256 	} else {
   1257 		$args = wp_parse_args( $args, $defaults );
   1258 		if ( isset( $args['taxonomy'] ) && null !== $args['taxonomy'] ) {
   1259 			$args['taxonomy'] = (array) $args['taxonomy'];
   1260 		}
   1261 	}
   1262 
   1263 	if ( ! empty( $args['taxonomy'] ) ) {
   1264 		foreach ( $args['taxonomy'] as $taxonomy ) {
   1265 			if ( ! taxonomy_exists( $taxonomy ) ) {
   1266 				return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   1267 			}
   1268 		}
   1269 	}
   1270 
   1271 	// Don't pass suppress_filter to WP_Term_Query.
   1272 	$suppress_filter = $args['suppress_filter'];
   1273 	unset( $args['suppress_filter'] );
   1274 
   1275 	$terms = $term_query->query( $args );
   1276 
   1277 	// Count queries are not filtered, for legacy reasons.
   1278 	if ( ! is_array( $terms ) ) {
   1279 		return $terms;
   1280 	}
   1281 
   1282 	if ( $suppress_filter ) {
   1283 		return $terms;
   1284 	}
   1285 
   1286 	/**
   1287 	 * Filters the found terms.
   1288 	 *
   1289 	 * @since 2.3.0
   1290 	 * @since 4.6.0 Added the `$term_query` parameter.
   1291 	 *
   1292 	 * @param array         $terms      Array of found terms.
   1293 	 * @param array         $taxonomies An array of taxonomies.
   1294 	 * @param array         $args       An array of get_terms() arguments.
   1295 	 * @param WP_Term_Query $term_query The WP_Term_Query object.
   1296 	 */
   1297 	return apply_filters( 'get_terms', $terms, $term_query->query_vars['taxonomy'], $term_query->query_vars, $term_query );
   1298 }
   1299 
   1300 /**
   1301  * Adds metadata to a term.
   1302  *
   1303  * @since 4.4.0
   1304  *
   1305  * @param int    $term_id    Term ID.
   1306  * @param string $meta_key   Metadata name.
   1307  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
   1308  * @param bool   $unique     Optional. Whether the same key should not be added.
   1309  *                           Default false.
   1310  * @return int|false|WP_Error Meta ID on success, false on failure.
   1311  *                            WP_Error when term_id is ambiguous between taxonomies.
   1312  */
   1313 function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
   1314 	if ( wp_term_is_shared( $term_id ) ) {
   1315 		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
   1316 	}
   1317 
   1318 	return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
   1319 }
   1320 
   1321 /**
   1322  * Removes metadata matching criteria from a term.
   1323  *
   1324  * @since 4.4.0
   1325  *
   1326  * @param int    $term_id    Term ID.
   1327  * @param string $meta_key   Metadata name.
   1328  * @param mixed  $meta_value Optional. Metadata value. If provided,
   1329  *                           rows will only be removed that match the value.
   1330  *                           Must be serializable if non-scalar. Default empty.
   1331  * @return bool True on success, false on failure.
   1332  */
   1333 function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
   1334 	return delete_metadata( 'term', $term_id, $meta_key, $meta_value );
   1335 }
   1336 
   1337 /**
   1338  * Retrieves metadata for a term.
   1339  *
   1340  * @since 4.4.0
   1341  *
   1342  * @param int    $term_id Term ID.
   1343  * @param string $key     Optional. The meta key to retrieve. By default,
   1344  *                        returns data for all keys. Default empty.
   1345  * @param bool   $single  Optional. Whether to return a single value.
   1346  *                        This parameter has no effect if `$key` is not specified.
   1347  *                        Default false.
   1348  * @return mixed An array of values if `$single` is false.
   1349  *               The value of the meta field if `$single` is true.
   1350  *               False for an invalid `$term_id` (non-numeric, zero, or negative value).
   1351  *               An empty string if a valid but non-existing term ID is passed.
   1352  */
   1353 function get_term_meta( $term_id, $key = '', $single = false ) {
   1354 	return get_metadata( 'term', $term_id, $key, $single );
   1355 }
   1356 
   1357 /**
   1358  * Updates term metadata.
   1359  *
   1360  * Use the `$prev_value` parameter to differentiate between meta fields with the same key and term ID.
   1361  *
   1362  * If the meta field for the term does not exist, it will be added.
   1363  *
   1364  * @since 4.4.0
   1365  *
   1366  * @param int    $term_id    Term ID.
   1367  * @param string $meta_key   Metadata key.
   1368  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
   1369  * @param mixed  $prev_value Optional. Previous value to check before updating.
   1370  *                           If specified, only update existing metadata entries with
   1371  *                           this value. Otherwise, update all entries. Default empty.
   1372  * @return int|bool|WP_Error Meta ID if the key didn't exist. true on successful update,
   1373  *                           false on failure or if the value passed to the function
   1374  *                           is the same as the one that is already in the database.
   1375  *                           WP_Error when term_id is ambiguous between taxonomies.
   1376  */
   1377 function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
   1378 	if ( wp_term_is_shared( $term_id ) ) {
   1379 		return new WP_Error( 'ambiguous_term_id', __( 'Term meta cannot be added to terms that are shared between taxonomies.' ), $term_id );
   1380 	}
   1381 
   1382 	return update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
   1383 }
   1384 
   1385 /**
   1386  * Updates metadata cache for list of term IDs.
   1387  *
   1388  * Performs SQL query to retrieve all metadata for the terms matching `$term_ids` and stores them in the cache.
   1389  * Subsequent calls to `get_term_meta()` will not need to query the database.
   1390  *
   1391  * @since 4.4.0
   1392  *
   1393  * @param array $term_ids List of term IDs.
   1394  * @return array|false An array of metadata on success, false if there is nothing to update.
   1395  */
   1396 function update_termmeta_cache( $term_ids ) {
   1397 	return update_meta_cache( 'term', $term_ids );
   1398 }
   1399 
   1400 /**
   1401  * Get all meta data, including meta IDs, for the given term ID.
   1402  *
   1403  * @since 4.9.0
   1404  *
   1405  * @global wpdb $wpdb WordPress database abstraction object.
   1406  *
   1407  * @param int $term_id Term ID.
   1408  * @return array|false Array with meta data, or false when the meta table is not installed.
   1409  */
   1410 function has_term_meta( $term_id ) {
   1411 	$check = wp_check_term_meta_support_prefilter( null );
   1412 	if ( null !== $check ) {
   1413 		return $check;
   1414 	}
   1415 
   1416 	global $wpdb;
   1417 
   1418 	return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, term_id FROM $wpdb->termmeta WHERE term_id = %d ORDER BY meta_key,meta_id", $term_id ), ARRAY_A );
   1419 }
   1420 
   1421 /**
   1422  * Registers a meta key for terms.
   1423  *
   1424  * @since 4.9.8
   1425  *
   1426  * @param string $taxonomy Taxonomy to register a meta key for. Pass an empty string
   1427  *                         to register the meta key across all existing taxonomies.
   1428  * @param string $meta_key The meta key to register.
   1429  * @param array  $args     Data used to describe the meta key when registered. See
   1430  *                         {@see register_meta()} for a list of supported arguments.
   1431  * @return bool True if the meta key was successfully registered, false if not.
   1432  */
   1433 function register_term_meta( $taxonomy, $meta_key, array $args ) {
   1434 	$args['object_subtype'] = $taxonomy;
   1435 
   1436 	return register_meta( 'term', $meta_key, $args );
   1437 }
   1438 
   1439 /**
   1440  * Unregisters a meta key for terms.
   1441  *
   1442  * @since 4.9.8
   1443  *
   1444  * @param string $taxonomy Taxonomy the meta key is currently registered for. Pass
   1445  *                         an empty string if the meta key is registered across all
   1446  *                         existing taxonomies.
   1447  * @param string $meta_key The meta key to unregister.
   1448  * @return bool True on success, false if the meta key was not previously registered.
   1449  */
   1450 function unregister_term_meta( $taxonomy, $meta_key ) {
   1451 	return unregister_meta_key( 'term', $meta_key, $taxonomy );
   1452 }
   1453 
   1454 /**
   1455  * Determines whether a taxonomy term exists.
   1456  *
   1457  * Formerly is_term(), introduced in 2.3.0.
   1458  *
   1459  * For more information on this and similar theme functions, check out
   1460  * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
   1461  * Conditional Tags} article in the Theme Developer Handbook.
   1462  *
   1463  * @since 3.0.0
   1464  *
   1465  * @global wpdb $wpdb WordPress database abstraction object.
   1466  *
   1467  * @param int|string $term     The term to check. Accepts term ID, slug, or name.
   1468  * @param string     $taxonomy Optional. The taxonomy name to use.
   1469  * @param int        $parent   Optional. ID of parent term under which to confine the exists search.
   1470  * @return mixed Returns null if the term does not exist.
   1471  *               Returns the term ID if no taxonomy is specified and the term ID exists.
   1472  *               Returns an array of the term ID and the term taxonomy ID if the taxonomy is specified and the pairing exists.
   1473  *               Returns 0 if term ID 0 is passed to the function.
   1474  */
   1475 function term_exists( $term, $taxonomy = '', $parent = null ) {
   1476 	global $wpdb;
   1477 
   1478 	$select     = "SELECT term_id FROM $wpdb->terms as t WHERE ";
   1479 	$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 ";
   1480 
   1481 	if ( is_int( $term ) ) {
   1482 		if ( 0 === $term ) {
   1483 			return 0;
   1484 		}
   1485 		$where = 't.term_id = %d';
   1486 		if ( ! empty( $taxonomy ) ) {
   1487 			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
   1488 			return $wpdb->get_row( $wpdb->prepare( $tax_select . $where . ' AND tt.taxonomy = %s', $term, $taxonomy ), ARRAY_A );
   1489 		} else {
   1490 			return $wpdb->get_var( $wpdb->prepare( $select . $where, $term ) );
   1491 		}
   1492 	}
   1493 
   1494 	$term = trim( wp_unslash( $term ) );
   1495 	$slug = sanitize_title( $term );
   1496 
   1497 	$where             = 't.slug = %s';
   1498 	$else_where        = 't.name = %s';
   1499 	$where_fields      = array( $slug );
   1500 	$else_where_fields = array( $term );
   1501 	$orderby           = 'ORDER BY t.term_id ASC';
   1502 	$limit             = 'LIMIT 1';
   1503 	if ( ! empty( $taxonomy ) ) {
   1504 		if ( is_numeric( $parent ) ) {
   1505 			$parent              = (int) $parent;
   1506 			$where_fields[]      = $parent;
   1507 			$else_where_fields[] = $parent;
   1508 			$where              .= ' AND tt.parent = %d';
   1509 			$else_where         .= ' AND tt.parent = %d';
   1510 		}
   1511 
   1512 		$where_fields[]      = $taxonomy;
   1513 		$else_where_fields[] = $taxonomy;
   1514 
   1515 		$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 );
   1516 		if ( $result ) {
   1517 			return $result;
   1518 		}
   1519 
   1520 		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 );
   1521 	}
   1522 
   1523 	// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
   1524 	$result = $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $where $orderby $limit", $where_fields ) );
   1525 	if ( $result ) {
   1526 		return $result;
   1527 	}
   1528 
   1529 	// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
   1530 	return $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->terms as t WHERE $else_where $orderby $limit", $else_where_fields ) );
   1531 }
   1532 
   1533 /**
   1534  * Check if a term is an ancestor of another term.
   1535  *
   1536  * You can use either an ID or the term object for both parameters.
   1537  *
   1538  * @since 3.4.0
   1539  *
   1540  * @param int|object $term1    ID or object to check if this is the parent term.
   1541  * @param int|object $term2    The child term.
   1542  * @param string     $taxonomy Taxonomy name that $term1 and `$term2` belong to.
   1543  * @return bool Whether `$term2` is a child of `$term1`.
   1544  */
   1545 function term_is_ancestor_of( $term1, $term2, $taxonomy ) {
   1546 	if ( ! isset( $term1->term_id ) ) {
   1547 		$term1 = get_term( $term1, $taxonomy );
   1548 	}
   1549 	if ( ! isset( $term2->parent ) ) {
   1550 		$term2 = get_term( $term2, $taxonomy );
   1551 	}
   1552 
   1553 	if ( empty( $term1->term_id ) || empty( $term2->parent ) ) {
   1554 		return false;
   1555 	}
   1556 	if ( $term2->parent === $term1->term_id ) {
   1557 		return true;
   1558 	}
   1559 
   1560 	return term_is_ancestor_of( $term1, get_term( $term2->parent, $taxonomy ), $taxonomy );
   1561 }
   1562 
   1563 /**
   1564  * Sanitize all term fields.
   1565  *
   1566  * Relies on sanitize_term_field() to sanitize the term. The difference is that
   1567  * this function will sanitize **all** fields. The context is based
   1568  * on sanitize_term_field().
   1569  *
   1570  * The `$term` is expected to be either an array or an object.
   1571  *
   1572  * @since 2.3.0
   1573  *
   1574  * @param array|object $term     The term to check.
   1575  * @param string       $taxonomy The taxonomy name to use.
   1576  * @param string       $context  Optional. Context in which to sanitize the term.
   1577  *                               Accepts 'raw', 'edit', 'db', 'display', 'rss',
   1578  *                               'attribute', or 'js'. Default 'display'.
   1579  * @return array|object Term with all fields sanitized.
   1580  */
   1581 function sanitize_term( $term, $taxonomy, $context = 'display' ) {
   1582 	$fields = array( 'term_id', 'name', 'description', 'slug', 'count', 'parent', 'term_group', 'term_taxonomy_id', 'object_id' );
   1583 
   1584 	$do_object = is_object( $term );
   1585 
   1586 	$term_id = $do_object ? $term->term_id : ( isset( $term['term_id'] ) ? $term['term_id'] : 0 );
   1587 
   1588 	foreach ( (array) $fields as $field ) {
   1589 		if ( $do_object ) {
   1590 			if ( isset( $term->$field ) ) {
   1591 				$term->$field = sanitize_term_field( $field, $term->$field, $term_id, $taxonomy, $context );
   1592 			}
   1593 		} else {
   1594 			if ( isset( $term[ $field ] ) ) {
   1595 				$term[ $field ] = sanitize_term_field( $field, $term[ $field ], $term_id, $taxonomy, $context );
   1596 			}
   1597 		}
   1598 	}
   1599 
   1600 	if ( $do_object ) {
   1601 		$term->filter = $context;
   1602 	} else {
   1603 		$term['filter'] = $context;
   1604 	}
   1605 
   1606 	return $term;
   1607 }
   1608 
   1609 /**
   1610  * Cleanse the field value in the term based on the context.
   1611  *
   1612  * Passing a term field value through the function should be assumed to have
   1613  * cleansed the value for whatever context the term field is going to be used.
   1614  *
   1615  * If no context or an unsupported context is given, then default filters will
   1616  * be applied.
   1617  *
   1618  * There are enough filters for each context to support a custom filtering
   1619  * without creating your own filter function. Simply create a function that
   1620  * hooks into the filter you need.
   1621  *
   1622  * @since 2.3.0
   1623  *
   1624  * @param string $field    Term field to sanitize.
   1625  * @param string $value    Search for this term value.
   1626  * @param int    $term_id  Term ID.
   1627  * @param string $taxonomy Taxonomy Name.
   1628  * @param string $context  Context in which to sanitize the term field.
   1629  *                         Accepts 'raw', 'edit', 'db', 'display', 'rss',
   1630  *                         'attribute', or 'js'. Default 'display'.
   1631  * @return mixed Sanitized field.
   1632  */
   1633 function sanitize_term_field( $field, $value, $term_id, $taxonomy, $context ) {
   1634 	$int_fields = array( 'parent', 'term_id', 'count', 'term_group', 'term_taxonomy_id', 'object_id' );
   1635 	if ( in_array( $field, $int_fields, true ) ) {
   1636 		$value = (int) $value;
   1637 		if ( $value < 0 ) {
   1638 			$value = 0;
   1639 		}
   1640 	}
   1641 
   1642 	$context = strtolower( $context );
   1643 
   1644 	if ( 'raw' === $context ) {
   1645 		return $value;
   1646 	}
   1647 
   1648 	if ( 'edit' === $context ) {
   1649 
   1650 		/**
   1651 		 * Filters a term field to edit before it is sanitized.
   1652 		 *
   1653 		 * The dynamic portion of the filter name, `$field`, refers to the term field.
   1654 		 *
   1655 		 * @since 2.3.0
   1656 		 *
   1657 		 * @param mixed $value     Value of the term field.
   1658 		 * @param int   $term_id   Term ID.
   1659 		 * @param string $taxonomy Taxonomy slug.
   1660 		 */
   1661 		$value = apply_filters( "edit_term_{$field}", $value, $term_id, $taxonomy );
   1662 
   1663 		/**
   1664 		 * Filters the taxonomy field to edit before it is sanitized.
   1665 		 *
   1666 		 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
   1667 		 * to the taxonomy slug and taxonomy field, respectively.
   1668 		 *
   1669 		 * @since 2.3.0
   1670 		 *
   1671 		 * @param mixed $value   Value of the taxonomy field to edit.
   1672 		 * @param int   $term_id Term ID.
   1673 		 */
   1674 		$value = apply_filters( "edit_{$taxonomy}_{$field}", $value, $term_id );
   1675 
   1676 		if ( 'description' === $field ) {
   1677 			$value = esc_html( $value ); // textarea_escaped
   1678 		} else {
   1679 			$value = esc_attr( $value );
   1680 		}
   1681 	} elseif ( 'db' === $context ) {
   1682 
   1683 		/**
   1684 		 * Filters a term field value before it is sanitized.
   1685 		 *
   1686 		 * The dynamic portion of the filter name, `$field`, refers to the term field.
   1687 		 *
   1688 		 * @since 2.3.0
   1689 		 *
   1690 		 * @param mixed  $value    Value of the term field.
   1691 		 * @param string $taxonomy Taxonomy slug.
   1692 		 */
   1693 		$value = apply_filters( "pre_term_{$field}", $value, $taxonomy );
   1694 
   1695 		/**
   1696 		 * Filters a taxonomy field before it is sanitized.
   1697 		 *
   1698 		 * The dynamic portions of the filter name, `$taxonomy` and `$field`, refer
   1699 		 * to the taxonomy slug and field name, respectively.
   1700 		 *
   1701 		 * @since 2.3.0
   1702 		 *
   1703 		 * @param mixed $value Value of the taxonomy field.
   1704 		 */
   1705 		$value = apply_filters( "pre_{$taxonomy}_{$field}", $value );
   1706 
   1707 		// Back compat filters.
   1708 		if ( 'slug' === $field ) {
   1709 			/**
   1710 			 * Filters the category nicename before it is sanitized.
   1711 			 *
   1712 			 * Use the {@see 'pre_$taxonomy_$field'} hook instead.
   1713 			 *
   1714 			 * @since 2.0.3
   1715 			 *
   1716 			 * @param string $value The category nicename.
   1717 			 */
   1718 			$value = apply_filters( 'pre_category_nicename', $value );
   1719 		}
   1720 	} elseif ( 'rss' === $context ) {
   1721 
   1722 		/**
   1723 		 * Filters the term field for use in RSS.
   1724 		 *
   1725 		 * The dynamic portion of the filter name, `$field`, refers to the term field.
   1726 		 *
   1727 		 * @since 2.3.0
   1728 		 *
   1729 		 * @param mixed  $value    Value of the term field.
   1730 		 * @param string $taxonomy Taxonomy slug.
   1731 		 */
   1732 		$value = apply_filters( "term_{$field}_rss", $value, $taxonomy );
   1733 
   1734 		/**
   1735 		 * Filters the taxonomy field for use in RSS.
   1736 		 *
   1737 		 * The dynamic portions of the hook name, `$taxonomy`, and `$field`, refer
   1738 		 * to the taxonomy slug and field name, respectively.
   1739 		 *
   1740 		 * @since 2.3.0
   1741 		 *
   1742 		 * @param mixed $value Value of the taxonomy field.
   1743 		 */
   1744 		$value = apply_filters( "{$taxonomy}_{$field}_rss", $value );
   1745 	} else {
   1746 		// Use display filters by default.
   1747 
   1748 		/**
   1749 		 * Filters the term field sanitized for display.
   1750 		 *
   1751 		 * The dynamic portion of the filter name, `$field`, refers to the term field name.
   1752 		 *
   1753 		 * @since 2.3.0
   1754 		 *
   1755 		 * @param mixed  $value    Value of the term field.
   1756 		 * @param int    $term_id  Term ID.
   1757 		 * @param string $taxonomy Taxonomy slug.
   1758 		 * @param string $context  Context to retrieve the term field value.
   1759 		 */
   1760 		$value = apply_filters( "term_{$field}", $value, $term_id, $taxonomy, $context );
   1761 
   1762 		/**
   1763 		 * Filters the taxonomy field sanitized for display.
   1764 		 *
   1765 		 * The dynamic portions of the filter name, `$taxonomy`, and `$field`, refer
   1766 		 * to the taxonomy slug and taxonomy field, respectively.
   1767 		 *
   1768 		 * @since 2.3.0
   1769 		 *
   1770 		 * @param mixed  $value   Value of the taxonomy field.
   1771 		 * @param int    $term_id Term ID.
   1772 		 * @param string $context Context to retrieve the taxonomy field value.
   1773 		 */
   1774 		$value = apply_filters( "{$taxonomy}_{$field}", $value, $term_id, $context );
   1775 	}
   1776 
   1777 	if ( 'attribute' === $context ) {
   1778 		$value = esc_attr( $value );
   1779 	} elseif ( 'js' === $context ) {
   1780 		$value = esc_js( $value );
   1781 	}
   1782 
   1783 	// Restore the type for integer fields after esc_attr().
   1784 	if ( in_array( $field, $int_fields, true ) ) {
   1785 		$value = (int) $value;
   1786 	}
   1787 
   1788 	return $value;
   1789 }
   1790 
   1791 /**
   1792  * Count how many terms are in Taxonomy.
   1793  *
   1794  * Default $args is 'hide_empty' which can be 'hide_empty=true' or array('hide_empty' => true).
   1795  *
   1796  * @since 2.3.0
   1797  * @since 5.6.0 Changed the function signature so that the `$args` array can be provided as the first parameter.
   1798  *
   1799  * @internal The `$deprecated` parameter is parsed for backward compatibility only.
   1800  *
   1801  * @param array|string $args       Optional. Array of arguments that get passed to get_terms().
   1802  *                                 Default empty array.
   1803  * @param array|string $deprecated Optional. Argument array, when using the legacy function parameter format.
   1804  *                                 If present, this parameter will be interpreted as `$args`, and the first
   1805  *                                 function parameter will be parsed as a taxonomy or array of taxonomies.
   1806  *                                 Default empty.
   1807  * @return string|WP_Error Numeric string containing the number of terms in that
   1808  *                         taxonomy or WP_Error if the taxonomy does not exist.
   1809  */
   1810 function wp_count_terms( $args = array(), $deprecated = '' ) {
   1811 	$use_legacy_args = false;
   1812 
   1813 	// Check whether function is used with legacy signature: `$taxonomy` and `$args`.
   1814 	if ( $args
   1815 		&& ( is_string( $args ) && taxonomy_exists( $args )
   1816 			|| is_array( $args ) && wp_is_numeric_array( $args ) )
   1817 	) {
   1818 		$use_legacy_args = true;
   1819 	}
   1820 
   1821 	$defaults = array( 'hide_empty' => false );
   1822 
   1823 	if ( $use_legacy_args ) {
   1824 		$defaults['taxonomy'] = $args;
   1825 		$args                 = $deprecated;
   1826 	}
   1827 
   1828 	$args = wp_parse_args( $args, $defaults );
   1829 
   1830 	// Backward compatibility.
   1831 	if ( isset( $args['ignore_empty'] ) ) {
   1832 		$args['hide_empty'] = $args['ignore_empty'];
   1833 		unset( $args['ignore_empty'] );
   1834 	}
   1835 
   1836 	$args['fields'] = 'count';
   1837 
   1838 	return get_terms( $args );
   1839 }
   1840 
   1841 /**
   1842  * Will unlink the object from the taxonomy or taxonomies.
   1843  *
   1844  * Will remove all relationships between the object and any terms in
   1845  * a particular taxonomy or taxonomies. Does not remove the term or
   1846  * taxonomy itself.
   1847  *
   1848  * @since 2.3.0
   1849  *
   1850  * @param int          $object_id  The term Object Id that refers to the term.
   1851  * @param string|array $taxonomies List of Taxonomy Names or single Taxonomy name.
   1852  */
   1853 function wp_delete_object_term_relationships( $object_id, $taxonomies ) {
   1854 	$object_id = (int) $object_id;
   1855 
   1856 	if ( ! is_array( $taxonomies ) ) {
   1857 		$taxonomies = array( $taxonomies );
   1858 	}
   1859 
   1860 	foreach ( (array) $taxonomies as $taxonomy ) {
   1861 		$term_ids = wp_get_object_terms( $object_id, $taxonomy, array( 'fields' => 'ids' ) );
   1862 		$term_ids = array_map( 'intval', $term_ids );
   1863 		wp_remove_object_terms( $object_id, $term_ids, $taxonomy );
   1864 	}
   1865 }
   1866 
   1867 /**
   1868  * Removes a term from the database.
   1869  *
   1870  * If the term is a parent of other terms, then the children will be updated to
   1871  * that term's parent.
   1872  *
   1873  * Metadata associated with the term will be deleted.
   1874  *
   1875  * @since 2.3.0
   1876  *
   1877  * @global wpdb $wpdb WordPress database abstraction object.
   1878  *
   1879  * @param int          $term     Term ID.
   1880  * @param string       $taxonomy Taxonomy Name.
   1881  * @param array|string $args {
   1882  *     Optional. Array of arguments to override the default term ID. Default empty array.
   1883  *
   1884  *     @type int  $default       The term ID to make the default term. This will only override
   1885  *                               the terms found if there is only one term found. Any other and
   1886  *                               the found terms are used.
   1887  *     @type bool $force_default Optional. Whether to force the supplied term as default to be
   1888  *                               assigned even if the object was not going to be term-less.
   1889  *                               Default false.
   1890  * }
   1891  * @return bool|int|WP_Error True on success, false if term does not exist. Zero on attempted
   1892  *                           deletion of default Category. WP_Error if the taxonomy does not exist.
   1893  */
   1894 function wp_delete_term( $term, $taxonomy, $args = array() ) {
   1895 	global $wpdb;
   1896 
   1897 	$term = (int) $term;
   1898 
   1899 	$ids = term_exists( $term, $taxonomy );
   1900 	if ( ! $ids ) {
   1901 		return false;
   1902 	}
   1903 	if ( is_wp_error( $ids ) ) {
   1904 		return $ids;
   1905 	}
   1906 
   1907 	$tt_id = $ids['term_taxonomy_id'];
   1908 
   1909 	$defaults = array();
   1910 
   1911 	if ( 'category' === $taxonomy ) {
   1912 		$defaults['default'] = (int) get_option( 'default_category' );
   1913 		if ( $defaults['default'] === $term ) {
   1914 			return 0; // Don't delete the default category.
   1915 		}
   1916 	}
   1917 
   1918 	// Don't delete the default custom taxonomy term.
   1919 	$taxonomy_object = get_taxonomy( $taxonomy );
   1920 	if ( ! empty( $taxonomy_object->default_term ) ) {
   1921 		$defaults['default'] = (int) get_option( 'default_term_' . $taxonomy );
   1922 		if ( $defaults['default'] === $term ) {
   1923 			return 0;
   1924 		}
   1925 	}
   1926 
   1927 	$args = wp_parse_args( $args, $defaults );
   1928 
   1929 	if ( isset( $args['default'] ) ) {
   1930 		$default = (int) $args['default'];
   1931 		if ( ! term_exists( $default, $taxonomy ) ) {
   1932 			unset( $default );
   1933 		}
   1934 	}
   1935 
   1936 	if ( isset( $args['force_default'] ) ) {
   1937 		$force_default = $args['force_default'];
   1938 	}
   1939 
   1940 	/**
   1941 	 * Fires when deleting a term, before any modifications are made to posts or terms.
   1942 	 *
   1943 	 * @since 4.1.0
   1944 	 *
   1945 	 * @param int    $term     Term ID.
   1946 	 * @param string $taxonomy Taxonomy Name.
   1947 	 */
   1948 	do_action( 'pre_delete_term', $term, $taxonomy );
   1949 
   1950 	// Update children to point to new parent.
   1951 	if ( is_taxonomy_hierarchical( $taxonomy ) ) {
   1952 		$term_obj = get_term( $term, $taxonomy );
   1953 		if ( is_wp_error( $term_obj ) ) {
   1954 			return $term_obj;
   1955 		}
   1956 		$parent = $term_obj->parent;
   1957 
   1958 		$edit_ids    = $wpdb->get_results( "SELECT term_id, term_taxonomy_id FROM $wpdb->term_taxonomy WHERE `parent` = " . (int) $term_obj->term_id );
   1959 		$edit_tt_ids = wp_list_pluck( $edit_ids, 'term_taxonomy_id' );
   1960 
   1961 		/**
   1962 		 * Fires immediately before a term to delete's children are reassigned a parent.
   1963 		 *
   1964 		 * @since 2.9.0
   1965 		 *
   1966 		 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
   1967 		 */
   1968 		do_action( 'edit_term_taxonomies', $edit_tt_ids );
   1969 
   1970 		$wpdb->update( $wpdb->term_taxonomy, compact( 'parent' ), array( 'parent' => $term_obj->term_id ) + compact( 'taxonomy' ) );
   1971 
   1972 		// Clean the cache for all child terms.
   1973 		$edit_term_ids = wp_list_pluck( $edit_ids, 'term_id' );
   1974 		clean_term_cache( $edit_term_ids, $taxonomy );
   1975 
   1976 		/**
   1977 		 * Fires immediately after a term to delete's children are reassigned a parent.
   1978 		 *
   1979 		 * @since 2.9.0
   1980 		 *
   1981 		 * @param array $edit_tt_ids An array of term taxonomy IDs for the given term.
   1982 		 */
   1983 		do_action( 'edited_term_taxonomies', $edit_tt_ids );
   1984 	}
   1985 
   1986 	// Get the term before deleting it or its term relationships so we can pass to actions below.
   1987 	$deleted_term = get_term( $term, $taxonomy );
   1988 
   1989 	$object_ids = (array) $wpdb->get_col( $wpdb->prepare( "SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $tt_id ) );
   1990 
   1991 	foreach ( $object_ids as $object_id ) {
   1992 		if ( ! isset( $default ) ) {
   1993 			wp_remove_object_terms( $object_id, $term, $taxonomy );
   1994 			continue;
   1995 		}
   1996 
   1997 		$terms = wp_get_object_terms(
   1998 			$object_id,
   1999 			$taxonomy,
   2000 			array(
   2001 				'fields'  => 'ids',
   2002 				'orderby' => 'none',
   2003 			)
   2004 		);
   2005 
   2006 		if ( 1 === count( $terms ) && isset( $default ) ) {
   2007 			$terms = array( $default );
   2008 		} else {
   2009 			$terms = array_diff( $terms, array( $term ) );
   2010 			if ( isset( $default ) && isset( $force_default ) && $force_default ) {
   2011 				$terms = array_merge( $terms, array( $default ) );
   2012 			}
   2013 		}
   2014 
   2015 		$terms = array_map( 'intval', $terms );
   2016 		wp_set_object_terms( $object_id, $terms, $taxonomy );
   2017 	}
   2018 
   2019 	// Clean the relationship caches for all object types using this term.
   2020 	$tax_object = get_taxonomy( $taxonomy );
   2021 	foreach ( $tax_object->object_type as $object_type ) {
   2022 		clean_object_term_cache( $object_ids, $object_type );
   2023 	}
   2024 
   2025 	$term_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->termmeta WHERE term_id = %d ", $term ) );
   2026 	foreach ( $term_meta_ids as $mid ) {
   2027 		delete_metadata_by_mid( 'term', $mid );
   2028 	}
   2029 
   2030 	/**
   2031 	 * Fires immediately before a term taxonomy ID is deleted.
   2032 	 *
   2033 	 * @since 2.9.0
   2034 	 *
   2035 	 * @param int $tt_id Term taxonomy ID.
   2036 	 */
   2037 	do_action( 'delete_term_taxonomy', $tt_id );
   2038 
   2039 	$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
   2040 
   2041 	/**
   2042 	 * Fires immediately after a term taxonomy ID is deleted.
   2043 	 *
   2044 	 * @since 2.9.0
   2045 	 *
   2046 	 * @param int $tt_id Term taxonomy ID.
   2047 	 */
   2048 	do_action( 'deleted_term_taxonomy', $tt_id );
   2049 
   2050 	// Delete the term if no taxonomies use it.
   2051 	if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term ) ) ) {
   2052 		$wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
   2053 	}
   2054 
   2055 	clean_term_cache( $term, $taxonomy );
   2056 
   2057 	/**
   2058 	 * Fires after a term is deleted from the database and the cache is cleaned.
   2059 	 *
   2060 	 * The {@see 'delete_$taxonomy'} hook is also available for targeting a specific
   2061 	 * taxonomy.
   2062 	 *
   2063 	 * @since 2.5.0
   2064 	 * @since 4.5.0 Introduced the `$object_ids` argument.
   2065 	 *
   2066 	 * @param int     $term         Term ID.
   2067 	 * @param int     $tt_id        Term taxonomy ID.
   2068 	 * @param string  $taxonomy     Taxonomy slug.
   2069 	 * @param WP_Term $deleted_term Copy of the already-deleted term.
   2070 	 * @param array   $object_ids   List of term object IDs.
   2071 	 */
   2072 	do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term, $object_ids );
   2073 
   2074 	/**
   2075 	 * Fires after a term in a specific taxonomy is deleted.
   2076 	 *
   2077 	 * The dynamic portion of the hook name, `$taxonomy`, refers to the specific
   2078 	 * taxonomy the term belonged to.
   2079 	 *
   2080 	 * Possible hook names include:
   2081 	 *
   2082 	 *  - `delete_category`
   2083 	 *  - `delete_post_tag`
   2084 	 *
   2085 	 * @since 2.3.0
   2086 	 * @since 4.5.0 Introduced the `$object_ids` argument.
   2087 	 *
   2088 	 * @param int     $term         Term ID.
   2089 	 * @param int     $tt_id        Term taxonomy ID.
   2090 	 * @param WP_Term $deleted_term Copy of the already-deleted term.
   2091 	 * @param array   $object_ids   List of term object IDs.
   2092 	 */
   2093 	do_action( "delete_{$taxonomy}", $term, $tt_id, $deleted_term, $object_ids );
   2094 
   2095 	return true;
   2096 }
   2097 
   2098 /**
   2099  * Deletes one existing category.
   2100  *
   2101  * @since 2.0.0
   2102  *
   2103  * @param int $cat_ID Category term ID.
   2104  * @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
   2105  *  Zero on attempted deletion of default Category; WP_Error object is also a possibility.
   2106  */
   2107 function wp_delete_category( $cat_ID ) {
   2108 	return wp_delete_term( $cat_ID, 'category' );
   2109 }
   2110 
   2111 /**
   2112  * Retrieves the terms associated with the given object(s), in the supplied taxonomies.
   2113  *
   2114  * @since 2.3.0
   2115  * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
   2116  *              Introduced `$parent` argument.
   2117  * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments. When `$fields` is 'all' or
   2118  *              'all_with_object_id', an array of `WP_Term` objects will be returned.
   2119  * @since 4.7.0 Refactored to use WP_Term_Query, and to support any WP_Term_Query arguments.
   2120  *
   2121  * @param int|int[]       $object_ids The ID(s) of the object(s) to retrieve.
   2122  * @param string|string[] $taxonomies The taxonomy names to retrieve terms from.
   2123  * @param array|string    $args       See WP_Term_Query::__construct() for supported arguments.
   2124  * @return WP_Term[]|WP_Error Array of terms or empty array if no terms found.
   2125  *                            WP_Error if any of the taxonomies don't exist.
   2126  */
   2127 function wp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
   2128 	if ( empty( $object_ids ) || empty( $taxonomies ) ) {
   2129 		return array();
   2130 	}
   2131 
   2132 	if ( ! is_array( $taxonomies ) ) {
   2133 		$taxonomies = array( $taxonomies );
   2134 	}
   2135 
   2136 	foreach ( $taxonomies as $taxonomy ) {
   2137 		if ( ! taxonomy_exists( $taxonomy ) ) {
   2138 			return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   2139 		}
   2140 	}
   2141 
   2142 	if ( ! is_array( $object_ids ) ) {
   2143 		$object_ids = array( $object_ids );
   2144 	}
   2145 	$object_ids = array_map( 'intval', $object_ids );
   2146 
   2147 	$args = wp_parse_args( $args );
   2148 
   2149 	/**
   2150 	 * Filters arguments for retrieving object terms.
   2151 	 *
   2152 	 * @since 4.9.0
   2153 	 *
   2154 	 * @param array    $args       An array of arguments for retrieving terms for the given object(s).
   2155 	 *                             See {@see wp_get_object_terms()} for details.
   2156 	 * @param int[]    $object_ids Array of object IDs.
   2157 	 * @param string[] $taxonomies Array of taxonomy names to retrieve terms from.
   2158 	 */
   2159 	$args = apply_filters( 'wp_get_object_terms_args', $args, $object_ids, $taxonomies );
   2160 
   2161 	/*
   2162 	 * When one or more queried taxonomies is registered with an 'args' array,
   2163 	 * those params override the `$args` passed to this function.
   2164 	 */
   2165 	$terms = array();
   2166 	if ( count( $taxonomies ) > 1 ) {
   2167 		foreach ( $taxonomies as $index => $taxonomy ) {
   2168 			$t = get_taxonomy( $taxonomy );
   2169 			if ( isset( $t->args ) && is_array( $t->args ) && array_merge( $args, $t->args ) != $args ) {
   2170 				unset( $taxonomies[ $index ] );
   2171 				$terms = array_merge( $terms, wp_get_object_terms( $object_ids, $taxonomy, array_merge( $args, $t->args ) ) );
   2172 			}
   2173 		}
   2174 	} else {
   2175 		$t = get_taxonomy( $taxonomies[0] );
   2176 		if ( isset( $t->args ) && is_array( $t->args ) ) {
   2177 			$args = array_merge( $args, $t->args );
   2178 		}
   2179 	}
   2180 
   2181 	$args['taxonomy']   = $taxonomies;
   2182 	$args['object_ids'] = $object_ids;
   2183 
   2184 	// Taxonomies registered without an 'args' param are handled here.
   2185 	if ( ! empty( $taxonomies ) ) {
   2186 		$terms_from_remaining_taxonomies = get_terms( $args );
   2187 
   2188 		// Array keys should be preserved for values of $fields that use term_id for keys.
   2189 		if ( ! empty( $args['fields'] ) && 0 === strpos( $args['fields'], 'id=>' ) ) {
   2190 			$terms = $terms + $terms_from_remaining_taxonomies;
   2191 		} else {
   2192 			$terms = array_merge( $terms, $terms_from_remaining_taxonomies );
   2193 		}
   2194 	}
   2195 
   2196 	/**
   2197 	 * Filters the terms for a given object or objects.
   2198 	 *
   2199 	 * @since 4.2.0
   2200 	 *
   2201 	 * @param WP_Term[] $terms      Array of terms for the given object or objects.
   2202 	 * @param int[]     $object_ids Array of object IDs for which terms were retrieved.
   2203 	 * @param string[]  $taxonomies Array of taxonomy names from which terms were retrieved.
   2204 	 * @param array     $args       Array of arguments for retrieving terms for the given
   2205 	 *                              object(s). See wp_get_object_terms() for details.
   2206 	 */
   2207 	$terms = apply_filters( 'get_object_terms', $terms, $object_ids, $taxonomies, $args );
   2208 
   2209 	$object_ids = implode( ',', $object_ids );
   2210 	$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
   2211 
   2212 	/**
   2213 	 * Filters the terms for a given object or objects.
   2214 	 *
   2215 	 * The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
   2216 	 * {@see 'get_object_terms'} filter is recommended as an alternative.
   2217 	 *
   2218 	 * @since 2.8.0
   2219 	 *
   2220 	 * @param WP_Term[] $terms      Array of terms for the given object or objects.
   2221 	 * @param string    $object_ids Comma separated list of object IDs for which terms were retrieved.
   2222 	 * @param string    $taxonomies SQL fragment of taxonomy names from which terms were retrieved.
   2223 	 * @param array     $args       Array of arguments for retrieving terms for the given
   2224 	 *                              object(s). See wp_get_object_terms() for details.
   2225 	 */
   2226 	return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
   2227 }
   2228 
   2229 /**
   2230  * Add a new term to the database.
   2231  *
   2232  * A non-existent term is inserted in the following sequence:
   2233  * 1. The term is added to the term table, then related to the taxonomy.
   2234  * 2. If everything is correct, several actions are fired.
   2235  * 3. The 'term_id_filter' is evaluated.
   2236  * 4. The term cache is cleaned.
   2237  * 5. Several more actions are fired.
   2238  * 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
   2239  *
   2240  * If the 'slug' argument is not empty, then it is checked to see if the term
   2241  * is invalid. If it is not a valid, existing term, it is added and the term_id
   2242  * is given.
   2243  *
   2244  * If the taxonomy is hierarchical, and the 'parent' argument is not empty,
   2245  * the term is inserted and the term_id will be given.
   2246  *
   2247  * Error handling:
   2248  * If `$taxonomy` does not exist or `$term` is empty,
   2249  * a WP_Error object will be returned.
   2250  *
   2251  * If the term already exists on the same hierarchical level,
   2252  * or the term slug and name are not unique, a WP_Error object will be returned.
   2253  *
   2254  * @global wpdb $wpdb WordPress database abstraction object.
   2255  *
   2256  * @since 2.3.0
   2257  *
   2258  * @param string       $term     The term name to add.
   2259  * @param string       $taxonomy The taxonomy to which to add the term.
   2260  * @param array|string $args {
   2261  *     Optional. Array or query string of arguments for inserting a term.
   2262  *
   2263  *     @type string $alias_of    Slug of the term to make this term an alias of.
   2264  *                               Default empty string. Accepts a term slug.
   2265  *     @type string $description The term description. Default empty string.
   2266  *     @type int    $parent      The id of the parent term. Default 0.
   2267  *     @type string $slug        The term slug to use. Default empty string.
   2268  * }
   2269  * @return array|WP_Error {
   2270  *     An array of the new term data, WP_Error otherwise.
   2271  *
   2272  *     @type int        $term_id          The new term ID.
   2273  *     @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
   2274  * }
   2275  */
   2276 function wp_insert_term( $term, $taxonomy, $args = array() ) {
   2277 	global $wpdb;
   2278 
   2279 	if ( ! taxonomy_exists( $taxonomy ) ) {
   2280 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   2281 	}
   2282 
   2283 	/**
   2284 	 * Filters a term before it is sanitized and inserted into the database.
   2285 	 *
   2286 	 * @since 3.0.0
   2287 	 *
   2288 	 * @param string|WP_Error $term     The term name to add, or a WP_Error object if there's an error.
   2289 	 * @param string          $taxonomy Taxonomy slug.
   2290 	 */
   2291 	$term = apply_filters( 'pre_insert_term', $term, $taxonomy );
   2292 
   2293 	if ( is_wp_error( $term ) ) {
   2294 		return $term;
   2295 	}
   2296 
   2297 	if ( is_int( $term ) && 0 === $term ) {
   2298 		return new WP_Error( 'invalid_term_id', __( 'Invalid term ID.' ) );
   2299 	}
   2300 
   2301 	if ( '' === trim( $term ) ) {
   2302 		return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
   2303 	}
   2304 
   2305 	$defaults = array(
   2306 		'alias_of'    => '',
   2307 		'description' => '',
   2308 		'parent'      => 0,
   2309 		'slug'        => '',
   2310 	);
   2311 	$args     = wp_parse_args( $args, $defaults );
   2312 
   2313 	if ( (int) $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
   2314 		return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
   2315 	}
   2316 
   2317 	$args['name']     = $term;
   2318 	$args['taxonomy'] = $taxonomy;
   2319 
   2320 	// Coerce null description to strings, to avoid database errors.
   2321 	$args['description'] = (string) $args['description'];
   2322 
   2323 	$args = sanitize_term( $args, $taxonomy, 'db' );
   2324 
   2325 	// expected_slashed ($name)
   2326 	$name        = wp_unslash( $args['name'] );
   2327 	$description = wp_unslash( $args['description'] );
   2328 	$parent      = (int) $args['parent'];
   2329 
   2330 	$slug_provided = ! empty( $args['slug'] );
   2331 	if ( ! $slug_provided ) {
   2332 		$slug = sanitize_title( $name );
   2333 	} else {
   2334 		$slug = $args['slug'];
   2335 	}
   2336 
   2337 	$term_group = 0;
   2338 	if ( $args['alias_of'] ) {
   2339 		$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
   2340 		if ( ! empty( $alias->term_group ) ) {
   2341 			// The alias we want is already in a group, so let's use that one.
   2342 			$term_group = $alias->term_group;
   2343 		} elseif ( ! empty( $alias->term_id ) ) {
   2344 			/*
   2345 			 * The alias is not in a group, so we create a new one
   2346 			 * and add the alias to it.
   2347 			 */
   2348 			$term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
   2349 
   2350 			wp_update_term(
   2351 				$alias->term_id,
   2352 				$taxonomy,
   2353 				array(
   2354 					'term_group' => $term_group,
   2355 				)
   2356 			);
   2357 		}
   2358 	}
   2359 
   2360 	/*
   2361 	 * Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
   2362 	 * unless a unique slug has been explicitly provided.
   2363 	 */
   2364 	$name_matches = get_terms(
   2365 		array(
   2366 			'taxonomy'               => $taxonomy,
   2367 			'name'                   => $name,
   2368 			'hide_empty'             => false,
   2369 			'parent'                 => $args['parent'],
   2370 			'update_term_meta_cache' => false,
   2371 		)
   2372 	);
   2373 
   2374 	/*
   2375 	 * The `name` match in `get_terms()` doesn't differentiate accented characters,
   2376 	 * so we do a stricter comparison here.
   2377 	 */
   2378 	$name_match = null;
   2379 	if ( $name_matches ) {
   2380 		foreach ( $name_matches as $_match ) {
   2381 			if ( strtolower( $name ) === strtolower( $_match->name ) ) {
   2382 				$name_match = $_match;
   2383 				break;
   2384 			}
   2385 		}
   2386 	}
   2387 
   2388 	if ( $name_match ) {
   2389 		$slug_match = get_term_by( 'slug', $slug, $taxonomy );
   2390 		if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
   2391 			if ( is_taxonomy_hierarchical( $taxonomy ) ) {
   2392 				$siblings = get_terms(
   2393 					array(
   2394 						'taxonomy'               => $taxonomy,
   2395 						'get'                    => 'all',
   2396 						'parent'                 => $parent,
   2397 						'update_term_meta_cache' => false,
   2398 					)
   2399 				);
   2400 
   2401 				$existing_term = null;
   2402 				$sibling_names = wp_list_pluck( $siblings, 'name' );
   2403 				$sibling_slugs = wp_list_pluck( $siblings, 'slug' );
   2404 
   2405 				if ( ( ! $slug_provided || $name_match->slug === $slug ) && in_array( $name, $sibling_names, true ) ) {
   2406 					$existing_term = $name_match;
   2407 				} elseif ( $slug_match && in_array( $slug, $sibling_slugs, true ) ) {
   2408 					$existing_term = $slug_match;
   2409 				}
   2410 
   2411 				if ( $existing_term ) {
   2412 					return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
   2413 				}
   2414 			} else {
   2415 				return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
   2416 			}
   2417 		}
   2418 	}
   2419 
   2420 	$slug = wp_unique_term_slug( $slug, (object) $args );
   2421 
   2422 	$data = compact( 'name', 'slug', 'term_group' );
   2423 
   2424 	/**
   2425 	 * Filters term data before it is inserted into the database.
   2426 	 *
   2427 	 * @since 4.7.0
   2428 	 *
   2429 	 * @param array  $data     Term data to be inserted.
   2430 	 * @param string $taxonomy Taxonomy slug.
   2431 	 * @param array  $args     Arguments passed to wp_insert_term().
   2432 	 */
   2433 	$data = apply_filters( 'wp_insert_term_data', $data, $taxonomy, $args );
   2434 
   2435 	if ( false === $wpdb->insert( $wpdb->terms, $data ) ) {
   2436 		return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database.' ), $wpdb->last_error );
   2437 	}
   2438 
   2439 	$term_id = (int) $wpdb->insert_id;
   2440 
   2441 	// Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
   2442 	if ( empty( $slug ) ) {
   2443 		$slug = sanitize_title( $slug, $term_id );
   2444 
   2445 		/** This action is documented in wp-includes/taxonomy.php */
   2446 		do_action( 'edit_terms', $term_id, $taxonomy );
   2447 		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
   2448 
   2449 		/** This action is documented in wp-includes/taxonomy.php */
   2450 		do_action( 'edited_terms', $term_id, $taxonomy );
   2451 	}
   2452 
   2453 	$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 ) );
   2454 
   2455 	if ( ! empty( $tt_id ) ) {
   2456 		return array(
   2457 			'term_id'          => $term_id,
   2458 			'term_taxonomy_id' => $tt_id,
   2459 		);
   2460 	}
   2461 
   2462 	if ( false === $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ) + array( 'count' => 0 ) ) ) {
   2463 		return new WP_Error( 'db_insert_error', __( 'Could not insert term taxonomy into the database.' ), $wpdb->last_error );
   2464 	}
   2465 
   2466 	$tt_id = (int) $wpdb->insert_id;
   2467 
   2468 	/*
   2469 	 * Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
   2470 	 * an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
   2471 	 * and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
   2472 	 * are not fired.
   2473 	 */
   2474 	$duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy 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 ) );
   2475 
   2476 	/**
   2477 	 * Filters the duplicate term check that takes place during term creation.
   2478 	 *
   2479 	 * Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
   2480 	 * performs a last-minute confirmation of this uniqueness before allowing a new term
   2481 	 * to be created. Plugins with different uniqueness requirements may use this filter
   2482 	 * to bypass or modify the duplicate-term check.
   2483 	 *
   2484 	 * @since 5.1.0
   2485 	 *
   2486 	 * @param object $duplicate_term Duplicate term row from terms table, if found.
   2487 	 * @param string $term           Term being inserted.
   2488 	 * @param string $taxonomy       Taxonomy name.
   2489 	 * @param array  $args           Term arguments passed to the function.
   2490 	 * @param int    $tt_id          term_taxonomy_id for the newly created term.
   2491 	 */
   2492 	$duplicate_term = apply_filters( 'wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id );
   2493 
   2494 	if ( $duplicate_term ) {
   2495 		$wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
   2496 		$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
   2497 
   2498 		$term_id = (int) $duplicate_term->term_id;
   2499 		$tt_id   = (int) $duplicate_term->term_taxonomy_id;
   2500 
   2501 		clean_term_cache( $term_id, $taxonomy );
   2502 		return array(
   2503 			'term_id'          => $term_id,
   2504 			'term_taxonomy_id' => $tt_id,
   2505 		);
   2506 	}
   2507 
   2508 	/**
   2509 	 * Fires immediately after a new term is created, before the term cache is cleaned.
   2510 	 *
   2511 	 * The {@see 'create_$taxonomy'} hook is also available for targeting a specific
   2512 	 * taxonomy.
   2513 	 *
   2514 	 * @since 2.3.0
   2515 	 *
   2516 	 * @param int    $term_id  Term ID.
   2517 	 * @param int    $tt_id    Term taxonomy ID.
   2518 	 * @param string $taxonomy Taxonomy slug.
   2519 	 */
   2520 	do_action( 'create_term', $term_id, $tt_id, $taxonomy );
   2521 
   2522 	/**
   2523 	 * Fires after a new term is created for a specific taxonomy.
   2524 	 *
   2525 	 * The dynamic portion of the hook name, `$taxonomy`, refers
   2526 	 * to the slug of the taxonomy the term was created for.
   2527 	 *
   2528 	 * Possible hook names include:
   2529 	 *
   2530 	 *  - `create_category`
   2531 	 *  - `create_post_tag`
   2532 	 *
   2533 	 * @since 2.3.0
   2534 	 *
   2535 	 * @param int $term_id Term ID.
   2536 	 * @param int $tt_id   Term taxonomy ID.
   2537 	 */
   2538 	do_action( "create_{$taxonomy}", $term_id, $tt_id );
   2539 
   2540 	/**
   2541 	 * Filters the term ID after a new term is created.
   2542 	 *
   2543 	 * @since 2.3.0
   2544 	 *
   2545 	 * @param int $term_id Term ID.
   2546 	 * @param int $tt_id   Term taxonomy ID.
   2547 	 */
   2548 	$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
   2549 
   2550 	clean_term_cache( $term_id, $taxonomy );
   2551 
   2552 	/**
   2553 	 * Fires after a new term is created, and after the term cache has been cleaned.
   2554 	 *
   2555 	 * The {@see 'created_$taxonomy'} hook is also available for targeting a specific
   2556 	 * taxonomy.
   2557 	 *
   2558 	 * @since 2.3.0
   2559 	 *
   2560 	 * @param int    $term_id  Term ID.
   2561 	 * @param int    $tt_id    Term taxonomy ID.
   2562 	 * @param string $taxonomy Taxonomy slug.
   2563 	 */
   2564 	do_action( 'created_term', $term_id, $tt_id, $taxonomy );
   2565 
   2566 	/**
   2567 	 * Fires after a new term in a specific taxonomy is created, and after the term
   2568 	 * cache has been cleaned.
   2569 	 *
   2570 	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
   2571 	 *
   2572 	 * Possible hook names include:
   2573 	 *
   2574 	 *  - `created_category`
   2575 	 *  - `created_post_tag`
   2576 	 *
   2577 	 * @since 2.3.0
   2578 	 *
   2579 	 * @param int $term_id Term ID.
   2580 	 * @param int $tt_id   Term taxonomy ID.
   2581 	 */
   2582 	do_action( "created_{$taxonomy}", $term_id, $tt_id );
   2583 
   2584 	/**
   2585 	 * Fires after a term has been saved, and the term cache has been cleared.
   2586 	 *
   2587 	 * The {@see 'saved_$taxonomy'} hook is also available for targeting a specific
   2588 	 * taxonomy.
   2589 	 *
   2590 	 * @since 5.5.0
   2591 	 *
   2592 	 * @param int    $term_id  Term ID.
   2593 	 * @param int    $tt_id    Term taxonomy ID.
   2594 	 * @param string $taxonomy Taxonomy slug.
   2595 	 * @param bool   $update   Whether this is an existing term being updated.
   2596 	 */
   2597 	do_action( 'saved_term', $term_id, $tt_id, $taxonomy, false );
   2598 
   2599 	/**
   2600 	 * Fires after a term in a specific taxonomy has been saved, and the term
   2601 	 * cache has been cleared.
   2602 	 *
   2603 	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
   2604 	 *
   2605 	 * Possible hook names include:
   2606 	 *
   2607 	 *  - `saved_category`
   2608 	 *  - `saved_post_tag`
   2609 	 *
   2610 	 * @since 5.5.0
   2611 	 *
   2612 	 * @param int  $term_id Term ID.
   2613 	 * @param int  $tt_id   Term taxonomy ID.
   2614 	 * @param bool $update  Whether this is an existing term being updated.
   2615 	 */
   2616 	do_action( "saved_{$taxonomy}", $term_id, $tt_id, false );
   2617 
   2618 	return array(
   2619 		'term_id'          => $term_id,
   2620 		'term_taxonomy_id' => $tt_id,
   2621 	);
   2622 }
   2623 
   2624 /**
   2625  * Create Term and Taxonomy Relationships.
   2626  *
   2627  * Relates an object (post, link etc) to a term and taxonomy type. Creates the
   2628  * term and taxonomy relationship if it doesn't already exist. Creates a term if
   2629  * it doesn't exist (using the slug).
   2630  *
   2631  * A relationship means that the term is grouped in or belongs to the taxonomy.
   2632  * A term has no meaning until it is given context by defining which taxonomy it
   2633  * exists under.
   2634  *
   2635  * @since 2.3.0
   2636  *
   2637  * @global wpdb $wpdb WordPress database abstraction object.
   2638  *
   2639  * @param int              $object_id The object to relate to.
   2640  * @param string|int|array $terms     A single term slug, single term ID, or array of either term slugs or IDs.
   2641  *                                    Will replace all existing related terms in this taxonomy. Passing an
   2642  *                                    empty value will remove all related terms.
   2643  * @param string           $taxonomy  The context in which to relate the term to the object.
   2644  * @param bool             $append    Optional. If false will delete difference of terms. Default false.
   2645  * @return array|WP_Error Term taxonomy IDs of the affected terms or WP_Error on failure.
   2646  */
   2647 function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) {
   2648 	global $wpdb;
   2649 
   2650 	$object_id = (int) $object_id;
   2651 
   2652 	if ( ! taxonomy_exists( $taxonomy ) ) {
   2653 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   2654 	}
   2655 
   2656 	if ( ! is_array( $terms ) ) {
   2657 		$terms = array( $terms );
   2658 	}
   2659 
   2660 	if ( ! $append ) {
   2661 		$old_tt_ids = wp_get_object_terms(
   2662 			$object_id,
   2663 			$taxonomy,
   2664 			array(
   2665 				'fields'                 => 'tt_ids',
   2666 				'orderby'                => 'none',
   2667 				'update_term_meta_cache' => false,
   2668 			)
   2669 		);
   2670 	} else {
   2671 		$old_tt_ids = array();
   2672 	}
   2673 
   2674 	$tt_ids     = array();
   2675 	$term_ids   = array();
   2676 	$new_tt_ids = array();
   2677 
   2678 	foreach ( (array) $terms as $term ) {
   2679 		if ( '' === trim( $term ) ) {
   2680 			continue;
   2681 		}
   2682 
   2683 		$term_info = term_exists( $term, $taxonomy );
   2684 
   2685 		if ( ! $term_info ) {
   2686 			// Skip if a non-existent term ID is passed.
   2687 			if ( is_int( $term ) ) {
   2688 				continue;
   2689 			}
   2690 
   2691 			$term_info = wp_insert_term( $term, $taxonomy );
   2692 		}
   2693 
   2694 		if ( is_wp_error( $term_info ) ) {
   2695 			return $term_info;
   2696 		}
   2697 
   2698 		$term_ids[] = $term_info['term_id'];
   2699 		$tt_id      = $term_info['term_taxonomy_id'];
   2700 		$tt_ids[]   = $tt_id;
   2701 
   2702 		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 ) ) ) {
   2703 			continue;
   2704 		}
   2705 
   2706 		/**
   2707 		 * Fires immediately before an object-term relationship is added.
   2708 		 *
   2709 		 * @since 2.9.0
   2710 		 * @since 4.7.0 Added the `$taxonomy` parameter.
   2711 		 *
   2712 		 * @param int    $object_id Object ID.
   2713 		 * @param int    $tt_id     Term taxonomy ID.
   2714 		 * @param string $taxonomy  Taxonomy slug.
   2715 		 */
   2716 		do_action( 'add_term_relationship', $object_id, $tt_id, $taxonomy );
   2717 
   2718 		$wpdb->insert(
   2719 			$wpdb->term_relationships,
   2720 			array(
   2721 				'object_id'        => $object_id,
   2722 				'term_taxonomy_id' => $tt_id,
   2723 			)
   2724 		);
   2725 
   2726 		/**
   2727 		 * Fires immediately after an object-term relationship is added.
   2728 		 *
   2729 		 * @since 2.9.0
   2730 		 * @since 4.7.0 Added the `$taxonomy` parameter.
   2731 		 *
   2732 		 * @param int    $object_id Object ID.
   2733 		 * @param int    $tt_id     Term taxonomy ID.
   2734 		 * @param string $taxonomy  Taxonomy slug.
   2735 		 */
   2736 		do_action( 'added_term_relationship', $object_id, $tt_id, $taxonomy );
   2737 
   2738 		$new_tt_ids[] = $tt_id;
   2739 	}
   2740 
   2741 	if ( $new_tt_ids ) {
   2742 		wp_update_term_count( $new_tt_ids, $taxonomy );
   2743 	}
   2744 
   2745 	if ( ! $append ) {
   2746 		$delete_tt_ids = array_diff( $old_tt_ids, $tt_ids );
   2747 
   2748 		if ( $delete_tt_ids ) {
   2749 			$in_delete_tt_ids = "'" . implode( "', '", $delete_tt_ids ) . "'";
   2750 			$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 ) );
   2751 			$delete_term_ids  = array_map( 'intval', $delete_term_ids );
   2752 
   2753 			$remove = wp_remove_object_terms( $object_id, $delete_term_ids, $taxonomy );
   2754 			if ( is_wp_error( $remove ) ) {
   2755 				return $remove;
   2756 			}
   2757 		}
   2758 	}
   2759 
   2760 	$t = get_taxonomy( $taxonomy );
   2761 
   2762 	if ( ! $append && isset( $t->sort ) && $t->sort ) {
   2763 		$values     = array();
   2764 		$term_order = 0;
   2765 
   2766 		$final_tt_ids = wp_get_object_terms(
   2767 			$object_id,
   2768 			$taxonomy,
   2769 			array(
   2770 				'fields'                 => 'tt_ids',
   2771 				'update_term_meta_cache' => false,
   2772 			)
   2773 		);
   2774 
   2775 		foreach ( $tt_ids as $tt_id ) {
   2776 			if ( in_array( (int) $tt_id, $final_tt_ids, true ) ) {
   2777 				$values[] = $wpdb->prepare( '(%d, %d, %d)', $object_id, $tt_id, ++$term_order );
   2778 			}
   2779 		}
   2780 
   2781 		if ( $values ) {
   2782 			if ( false === $wpdb->query( "INSERT INTO $wpdb->term_relationships (object_id, term_taxonomy_id, term_order) VALUES " . implode( ',', $values ) . ' ON DUPLICATE KEY UPDATE term_order = VALUES(term_order)' ) ) {
   2783 				return new WP_Error( 'db_insert_error', __( 'Could not insert term relationship into the database.' ), $wpdb->last_error );
   2784 			}
   2785 		}
   2786 	}
   2787 
   2788 	wp_cache_delete( $object_id, $taxonomy . '_relationships' );
   2789 	wp_cache_delete( 'last_changed', 'terms' );
   2790 
   2791 	/**
   2792 	 * Fires after an object's terms have been set.
   2793 	 *
   2794 	 * @since 2.8.0
   2795 	 *
   2796 	 * @param int    $object_id  Object ID.
   2797 	 * @param array  $terms      An array of object term IDs or slugs.
   2798 	 * @param array  $tt_ids     An array of term taxonomy IDs.
   2799 	 * @param string $taxonomy   Taxonomy slug.
   2800 	 * @param bool   $append     Whether to append new terms to the old terms.
   2801 	 * @param array  $old_tt_ids Old array of term taxonomy IDs.
   2802 	 */
   2803 	do_action( 'set_object_terms', $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids );
   2804 
   2805 	return $tt_ids;
   2806 }
   2807 
   2808 /**
   2809  * Add term(s) associated with a given object.
   2810  *
   2811  * @since 3.6.0
   2812  *
   2813  * @param int              $object_id The ID of the object to which the terms will be added.
   2814  * @param string|int|array $terms     The slug(s) or ID(s) of the term(s) to add.
   2815  * @param array|string     $taxonomy  Taxonomy name.
   2816  * @return array|WP_Error Term taxonomy IDs of the affected terms.
   2817  */
   2818 function wp_add_object_terms( $object_id, $terms, $taxonomy ) {
   2819 	return wp_set_object_terms( $object_id, $terms, $taxonomy, true );
   2820 }
   2821 
   2822 /**
   2823  * Remove term(s) associated with a given object.
   2824  *
   2825  * @since 3.6.0
   2826  *
   2827  * @global wpdb $wpdb WordPress database abstraction object.
   2828  *
   2829  * @param int              $object_id The ID of the object from which the terms will be removed.
   2830  * @param string|int|array $terms     The slug(s) or ID(s) of the term(s) to remove.
   2831  * @param array|string     $taxonomy  Taxonomy name.
   2832  * @return bool|WP_Error True on success, false or WP_Error on failure.
   2833  */
   2834 function wp_remove_object_terms( $object_id, $terms, $taxonomy ) {
   2835 	global $wpdb;
   2836 
   2837 	$object_id = (int) $object_id;
   2838 
   2839 	if ( ! taxonomy_exists( $taxonomy ) ) {
   2840 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   2841 	}
   2842 
   2843 	if ( ! is_array( $terms ) ) {
   2844 		$terms = array( $terms );
   2845 	}
   2846 
   2847 	$tt_ids = array();
   2848 
   2849 	foreach ( (array) $terms as $term ) {
   2850 		if ( '' === trim( $term ) ) {
   2851 			continue;
   2852 		}
   2853 
   2854 		$term_info = term_exists( $term, $taxonomy );
   2855 		if ( ! $term_info ) {
   2856 			// Skip if a non-existent term ID is passed.
   2857 			if ( is_int( $term ) ) {
   2858 				continue;
   2859 			}
   2860 		}
   2861 
   2862 		if ( is_wp_error( $term_info ) ) {
   2863 			return $term_info;
   2864 		}
   2865 
   2866 		$tt_ids[] = $term_info['term_taxonomy_id'];
   2867 	}
   2868 
   2869 	if ( $tt_ids ) {
   2870 		$in_tt_ids = "'" . implode( "', '", $tt_ids ) . "'";
   2871 
   2872 		/**
   2873 		 * Fires immediately before an object-term relationship is deleted.
   2874 		 *
   2875 		 * @since 2.9.0
   2876 		 * @since 4.7.0 Added the `$taxonomy` parameter.
   2877 		 *
   2878 		 * @param int   $object_id Object ID.
   2879 		 * @param array $tt_ids    An array of term taxonomy IDs.
   2880 		 * @param string $taxonomy  Taxonomy slug.
   2881 		 */
   2882 		do_action( 'delete_term_relationships', $object_id, $tt_ids, $taxonomy );
   2883 
   2884 		$deleted = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->term_relationships WHERE object_id = %d AND term_taxonomy_id IN ($in_tt_ids)", $object_id ) );
   2885 
   2886 		wp_cache_delete( $object_id, $taxonomy . '_relationships' );
   2887 		wp_cache_delete( 'last_changed', 'terms' );
   2888 
   2889 		/**
   2890 		 * Fires immediately after an object-term relationship is deleted.
   2891 		 *
   2892 		 * @since 2.9.0
   2893 		 * @since 4.7.0 Added the `$taxonomy` parameter.
   2894 		 *
   2895 		 * @param int    $object_id Object ID.
   2896 		 * @param array  $tt_ids    An array of term taxonomy IDs.
   2897 		 * @param string $taxonomy  Taxonomy slug.
   2898 		 */
   2899 		do_action( 'deleted_term_relationships', $object_id, $tt_ids, $taxonomy );
   2900 
   2901 		wp_update_term_count( $tt_ids, $taxonomy );
   2902 
   2903 		return (bool) $deleted;
   2904 	}
   2905 
   2906 	return false;
   2907 }
   2908 
   2909 /**
   2910  * Will make slug unique, if it isn't already.
   2911  *
   2912  * The `$slug` has to be unique global to every taxonomy, meaning that one
   2913  * taxonomy term can't have a matching slug with another taxonomy term. Each
   2914  * slug has to be globally unique for every taxonomy.
   2915  *
   2916  * The way this works is that if the taxonomy that the term belongs to is
   2917  * hierarchical and has a parent, it will append that parent to the $slug.
   2918  *
   2919  * If that still doesn't return a unique slug, then it tries to append a number
   2920  * until it finds a number that is truly unique.
   2921  *
   2922  * The only purpose for `$term` is for appending a parent, if one exists.
   2923  *
   2924  * @since 2.3.0
   2925  *
   2926  * @global wpdb $wpdb WordPress database abstraction object.
   2927  *
   2928  * @param string $slug The string that will be tried for a unique slug.
   2929  * @param object $term The term object that the `$slug` will belong to.
   2930  * @return string Will return a true unique slug.
   2931  */
   2932 function wp_unique_term_slug( $slug, $term ) {
   2933 	global $wpdb;
   2934 
   2935 	$needs_suffix  = true;
   2936 	$original_slug = $slug;
   2937 
   2938 	// As of 4.1, duplicate slugs are allowed as long as they're in different taxonomies.
   2939 	if ( ! term_exists( $slug ) || get_option( 'db_version' ) >= 30133 && ! get_term_by( 'slug', $slug, $term->taxonomy ) ) {
   2940 		$needs_suffix = false;
   2941 	}
   2942 
   2943 	/*
   2944 	 * If the taxonomy supports hierarchy and the term has a parent, make the slug unique
   2945 	 * by incorporating parent slugs.
   2946 	 */
   2947 	$parent_suffix = '';
   2948 	if ( $needs_suffix && is_taxonomy_hierarchical( $term->taxonomy ) && ! empty( $term->parent ) ) {
   2949 		$the_parent = $term->parent;
   2950 		while ( ! empty( $the_parent ) ) {
   2951 			$parent_term = get_term( $the_parent, $term->taxonomy );
   2952 			if ( is_wp_error( $parent_term ) || empty( $parent_term ) ) {
   2953 				break;
   2954 			}
   2955 			$parent_suffix .= '-' . $parent_term->slug;
   2956 			if ( ! term_exists( $slug . $parent_suffix ) ) {
   2957 				break;
   2958 			}
   2959 
   2960 			if ( empty( $parent_term->parent ) ) {
   2961 				break;
   2962 			}
   2963 			$the_parent = $parent_term->parent;
   2964 		}
   2965 	}
   2966 
   2967 	// If we didn't get a unique slug, try appending a number to make it unique.
   2968 
   2969 	/**
   2970 	 * Filters whether the proposed unique term slug is bad.
   2971 	 *
   2972 	 * @since 4.3.0
   2973 	 *
   2974 	 * @param bool   $needs_suffix Whether the slug needs to be made unique with a suffix.
   2975 	 * @param string $slug         The slug.
   2976 	 * @param object $term         Term object.
   2977 	 */
   2978 	if ( apply_filters( 'wp_unique_term_slug_is_bad_slug', $needs_suffix, $slug, $term ) ) {
   2979 		if ( $parent_suffix ) {
   2980 			$slug .= $parent_suffix;
   2981 		}
   2982 
   2983 		if ( ! empty( $term->term_id ) ) {
   2984 			$query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s AND term_id != %d", $slug, $term->term_id );
   2985 		} else {
   2986 			$query = $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $slug );
   2987 		}
   2988 
   2989 		if ( $wpdb->get_var( $query ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
   2990 			$num = 2;
   2991 			do {
   2992 				$alt_slug = $slug . "-$num";
   2993 				$num++;
   2994 				$slug_check = $wpdb->get_var( $wpdb->prepare( "SELECT slug FROM $wpdb->terms WHERE slug = %s", $alt_slug ) );
   2995 			} while ( $slug_check );
   2996 			$slug = $alt_slug;
   2997 		}
   2998 	}
   2999 
   3000 	/**
   3001 	 * Filters the unique term slug.
   3002 	 *
   3003 	 * @since 4.3.0
   3004 	 *
   3005 	 * @param string $slug          Unique term slug.
   3006 	 * @param object $term          Term object.
   3007 	 * @param string $original_slug Slug originally passed to the function for testing.
   3008 	 */
   3009 	return apply_filters( 'wp_unique_term_slug', $slug, $term, $original_slug );
   3010 }
   3011 
   3012 /**
   3013  * Update term based on arguments provided.
   3014  *
   3015  * The `$args` will indiscriminately override all values with the same field name.
   3016  * Care must be taken to not override important information need to update or
   3017  * update will fail (or perhaps create a new term, neither would be acceptable).
   3018  *
   3019  * Defaults will set 'alias_of', 'description', 'parent', and 'slug' if not
   3020  * defined in `$args` already.
   3021  *
   3022  * 'alias_of' will create a term group, if it doesn't already exist, and
   3023  * update it for the `$term`.
   3024  *
   3025  * If the 'slug' argument in `$args` is missing, then the 'name' will be used.
   3026  * If you set 'slug' and it isn't unique, then a WP_Error is returned.
   3027  * If you don't pass any slug, then a unique one will be created.
   3028  *
   3029  * @since 2.3.0
   3030  *
   3031  * @global wpdb $wpdb WordPress database abstraction object.
   3032  *
   3033  * @param int          $term_id  The ID of the term.
   3034  * @param string       $taxonomy The taxonomy of the term.
   3035  * @param array|string $args {
   3036  *     Optional. Array or string of arguments for updating a term.
   3037  *
   3038  *     @type string $alias_of    Slug of the term to make this term an alias of.
   3039  *                               Default empty string. Accepts a term slug.
   3040  *     @type string $description The term description. Default empty string.
   3041  *     @type int    $parent      The id of the parent term. Default 0.
   3042  *     @type string $slug        The term slug to use. Default empty string.
   3043  * }
   3044  * @return array|WP_Error An array containing the `term_id` and `term_taxonomy_id`,
   3045  *                        WP_Error otherwise.
   3046  */
   3047 function wp_update_term( $term_id, $taxonomy, $args = array() ) {
   3048 	global $wpdb;
   3049 
   3050 	if ( ! taxonomy_exists( $taxonomy ) ) {
   3051 		return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
   3052 	}
   3053 
   3054 	$term_id = (int) $term_id;
   3055 
   3056 	// First, get all of the original args.
   3057 	$term = get_term( $term_id, $taxonomy );
   3058 
   3059 	if ( is_wp_error( $term ) ) {
   3060 		return $term;
   3061 	}
   3062 
   3063 	if ( ! $term ) {
   3064 		return new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
   3065 	}
   3066 
   3067 	$term = (array) $term->data;
   3068 
   3069 	// Escape data pulled from DB.
   3070 	$term = wp_slash( $term );
   3071 
   3072 	// Merge old and new args with new args overwriting old ones.
   3073 	$args = array_merge( $term, $args );
   3074 
   3075 	$defaults    = array(
   3076 		'alias_of'    => '',
   3077 		'description' => '',
   3078 		'parent'      => 0,
   3079 		'slug'        => '',
   3080 	);
   3081 	$args        = wp_parse_args( $args, $defaults );
   3082 	$args        = sanitize_term( $args, $taxonomy, 'db' );
   3083 	$parsed_args = $args;
   3084 
   3085 	// expected_slashed ($name)
   3086 	$name        = wp_unslash( $args['name'] );
   3087 	$description = wp_unslash( $args['description'] );
   3088 
   3089 	$parsed_args['name']        = $name;
   3090 	$parsed_args['description'] = $description;
   3091 
   3092 	if ( '' === trim( $name ) ) {
   3093 		return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
   3094 	}
   3095 
   3096 	if ( (int) $parsed_args['parent'] > 0 && ! term_exists( (int) $parsed_args['parent'] ) ) {
   3097 		return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
   3098 	}
   3099 
   3100 	$empty_slug = false;
   3101 	if ( empty( $args['slug'] ) ) {
   3102 		$empty_slug = true;
   3103 		$slug       = sanitize_title( $name );
   3104 	} else {
   3105 		$slug = $args['slug'];
   3106 	}
   3107 
   3108 	$parsed_args['slug'] = $slug;
   3109 
   3110 	$term_group = isset( $parsed_args['term_group'] ) ? $parsed_args['term_group'] : 0;
   3111 	if ( $args['alias_of'] ) {
   3112 		$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
   3113 		if ( ! empty( $alias->term_group ) ) {
   3114 			// The alias we want is already in a group, so let's use that one.
   3115 			$term_group = $alias->term_group;
   3116 		} elseif ( ! empty( $alias->term_id ) ) {
   3117 			/*
   3118 			 * The alias is not in a group, so we create a new one
   3119 			 * and add the alias to it.
   3120 			 */
   3121 			$term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
   3122 
   3123 			wp_update_term(
   3124 				$alias->term_id,
   3125 				$taxonomy,
   3126 				array(
   3127 					'term_group' => $term_group,
   3128 				)
   3129 			);
   3130 		}
   3131 
   3132 		$parsed_args['term_group'] = $term_group;
   3133 	}
   3134 
   3135 	/**
   3136 	 * Filters the term parent.
   3137 	 *
   3138 	 * Hook to this filter to see if it will cause a hierarchy loop.
   3139 	 *
   3140 	 * @since 3.1.0
   3141 	 *
   3142 	 * @param int    $parent      ID of the parent term.
   3143 	 * @param int    $term_id     Term ID.
   3144 	 * @param string $taxonomy    Taxonomy slug.
   3145 	 * @param array  $parsed_args An array of potentially altered update arguments for the given term.
   3146 	 * @param array  $args        An array of update arguments for the given term.
   3147 	 */
   3148 	$parent = (int) apply_filters( 'wp_update_term_parent', $args['parent'], $term_id, $taxonomy, $parsed_args, $args );
   3149 
   3150 	// Check for duplicate slug.
   3151 	$duplicate = get_term_by( 'slug', $slug, $taxonomy );
   3152 	if ( $duplicate && $duplicate->term_id !== $term_id ) {
   3153 		// If an empty slug was passed or the parent changed, reset the slug to something unique.
   3154 		// Otherwise, bail.
   3155 		if ( $empty_slug || ( $parent !== (int) $term['parent'] ) ) {
   3156 			$slug = wp_unique_term_slug( $slug, (object) $args );
   3157 		} else {
   3158 			/* translators: %s: Taxonomy term slug. */
   3159 			return new WP_Error( 'duplicate_term_slug', sprintf( __( 'The slug &#8220;%s&#8221; is already in use by another term.' ), $slug ) );
   3160 		}
   3161 	}
   3162 
   3163 	$tt_id = (int) $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 ) );
   3164 
   3165 	// Check whether this is a shared term that needs splitting.
   3166 	$_term_id = _split_shared_term( $term_id, $tt_id );
   3167 	if ( ! is_wp_error( $_term_id ) ) {
   3168 		$term_id = $_term_id;
   3169 	}
   3170 
   3171 	/**
   3172 	 * Fires immediately before the given terms are edited.
   3173 	 *
   3174 	 * @since 2.9.0
   3175 	 *
   3176 	 * @param int    $term_id  Term ID.
   3177 	 * @param string $taxonomy Taxonomy slug.
   3178 	 */
   3179 	do_action( 'edit_terms', $term_id, $taxonomy );
   3180 
   3181 	$data = compact( 'name', 'slug', 'term_group' );
   3182 
   3183 	/**
   3184 	 * Filters term data before it is updated in the database.
   3185 	 *
   3186 	 * @since 4.7.0
   3187 	 *
   3188 	 * @param array  $data     Term data to be updated.
   3189 	 * @param int    $term_id  Term ID.
   3190 	 * @param string $taxonomy Taxonomy slug.
   3191 	 * @param array  $args     Arguments passed to wp_update_term().
   3192 	 */
   3193 	$data = apply_filters( 'wp_update_term_data', $data, $term_id, $taxonomy, $args );
   3194 
   3195 	$wpdb->update( $wpdb->terms, $data, compact( 'term_id' ) );
   3196 
   3197 	if ( empty( $slug ) ) {
   3198 		$slug = sanitize_title( $name, $term_id );
   3199 		$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
   3200 	}
   3201 
   3202 	/**
   3203 	 * Fires immediately after a term is updated in the database, but before its
   3204 	 * term-taxonomy relationship is updated.
   3205 	 *
   3206 	 * @since 2.9.0
   3207 	 *
   3208 	 * @param int    $term_id  Term ID
   3209 	 * @param string $taxonomy Taxonomy slug.
   3210 	 */
   3211 	do_action( 'edited_terms', $term_id, $taxonomy );
   3212 
   3213 	/**
   3214 	 * Fires immediate before a term-taxonomy relationship is updated.
   3215 	 *
   3216 	 * @since 2.9.0
   3217 	 *
   3218 	 * @param int    $tt_id    Term taxonomy ID.
   3219 	 * @param string $taxonomy Taxonomy slug.
   3220 	 */
   3221 	do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
   3222 
   3223 	$wpdb->update( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ), array( 'term_taxonomy_id' => $tt_id ) );
   3224 
   3225 	/**
   3226 	 * Fires immediately after a term-taxonomy relationship is updated.
   3227 	 *
   3228 	 * @since 2.9.0
   3229 	 *
   3230 	 * @param int    $tt_id    Term taxonomy ID.
   3231 	 * @param string $taxonomy Taxonomy slug.
   3232 	 */
   3233 	do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
   3234 
   3235 	/**
   3236 	 * Fires after a term has been updated, but before the term cache has been cleaned.
   3237 	 *
   3238 	 * The {@see 'edit_$taxonomy'} hook is also available for targeting a specific
   3239 	 * taxonomy.
   3240 	 *
   3241 	 * @since 2.3.0
   3242 	 *
   3243 	 * @param int    $term_id  Term ID.
   3244 	 * @param int    $tt_id    Term taxonomy ID.
   3245 	 * @param string $taxonomy Taxonomy slug.
   3246 	 */
   3247 	do_action( 'edit_term', $term_id, $tt_id, $taxonomy );
   3248 
   3249 	/**
   3250 	 * Fires after a term in a specific taxonomy has been updated, but before the term
   3251 	 * cache has been cleaned.
   3252 	 *
   3253 	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
   3254 	 *
   3255 	 * Possible hook names include:
   3256 	 *
   3257 	 *  - `edit_category`
   3258 	 *  - `edit_post_tag`
   3259 	 *
   3260 	 * @since 2.3.0
   3261 	 *
   3262 	 * @param int $term_id Term ID.
   3263 	 * @param int $tt_id   Term taxonomy ID.
   3264 	 */
   3265 	do_action( "edit_{$taxonomy}", $term_id, $tt_id );
   3266 
   3267 	/** This filter is documented in wp-includes/taxonomy.php */
   3268 	$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
   3269 
   3270 	clean_term_cache( $term_id, $taxonomy );
   3271 
   3272 	/**
   3273 	 * Fires after a term has been updated, and the term cache has been cleaned.
   3274 	 *
   3275 	 * The {@see 'edited_$taxonomy'} hook is also available for targeting a specific
   3276 	 * taxonomy.
   3277 	 *
   3278 	 * @since 2.3.0
   3279 	 *
   3280 	 * @param int    $term_id  Term ID.
   3281 	 * @param int    $tt_id    Term taxonomy ID.
   3282 	 * @param string $taxonomy Taxonomy slug.
   3283 	 */
   3284 	do_action( 'edited_term', $term_id, $tt_id, $taxonomy );
   3285 
   3286 	/**
   3287 	 * Fires after a term for a specific taxonomy has been updated, and the term
   3288 	 * cache has been cleaned.
   3289 	 *
   3290 	 * The dynamic portion of the hook name, `$taxonomy`, refers to the taxonomy slug.
   3291 	 *
   3292 	 * Possible hook names include:
   3293 	 *
   3294 	 *  - `edited_category`
   3295 	 *  - `edited_post_tag`
   3296 	 *
   3297 	 * @since 2.3.0
   3298 	 *
   3299 	 * @param int $term_id Term ID.
   3300 	 * @param int $tt_id   Term taxonomy ID.
   3301 	 */
   3302 	do_action( "edited_{$taxonomy}", $term_id, $tt_id );
   3303 
   3304 	/** This action is documented in wp-includes/taxonomy.php */
   3305 	do_action( 'saved_term', $term_id, $tt_id, $taxonomy, true );
   3306 
   3307 	/** This action is documented in wp-includes/taxonomy.php */
   3308 	do_action( "saved_{$taxonomy}", $term_id, $tt_id, true );
   3309 
   3310 	return array(
   3311 		'term_id'          => $term_id,
   3312 		'term_taxonomy_id' => $tt_id,
   3313 	);
   3314 }
   3315 
   3316 /**
   3317  * Enable or disable term counting.
   3318  *
   3319  * @since 2.5.0
   3320  *
   3321  * @param bool $defer Optional. Enable if true, disable if false.
   3322  * @return bool Whether term counting is enabled or disabled.
   3323  */
   3324 function wp_defer_term_counting( $defer = null ) {
   3325 	static $_defer = false;
   3326 
   3327 	if ( is_bool( $defer ) ) {
   3328 		$_defer = $defer;
   3329 		// Flush any deferred counts.
   3330 		if ( ! $defer ) {
   3331 			wp_update_term_count( null, null, true );
   3332 		}
   3333 	}
   3334 
   3335 	return $_defer;
   3336 }
   3337 
   3338 /**
   3339  * Updates the amount of terms in taxonomy.
   3340  *
   3341  * If there is a taxonomy callback applied, then it will be called for updating
   3342  * the count.
   3343  *
   3344  * The default action is to count what the amount of terms have the relationship
   3345  * of term ID. Once that is done, then update the database.
   3346  *
   3347  * @since 2.3.0
   3348  *
   3349  * @param int|array $terms       The term_taxonomy_id of the terms.
   3350  * @param string    $taxonomy    The context of the term.
   3351  * @param bool      $do_deferred Whether to flush the deferred term counts too. Default false.
   3352  * @return bool If no terms will return false, and if successful will return true.
   3353  */
   3354 function wp_update_term_count( $terms, $taxonomy, $do_deferred = false ) {
   3355 	static $_deferred = array();
   3356 
   3357 	if ( $do_deferred ) {
   3358 		foreach ( (array) array_keys( $_deferred ) as $tax ) {
   3359 			wp_update_term_count_now( $_deferred[ $tax ], $tax );
   3360 			unset( $_deferred[ $tax ] );
   3361 		}
   3362 	}
   3363 
   3364 	if ( empty( $terms ) ) {
   3365 		return false;
   3366 	}
   3367 
   3368 	if ( ! is_array( $terms ) ) {
   3369 		$terms = array( $terms );
   3370 	}
   3371 
   3372 	if ( wp_defer_term_counting() ) {
   3373 		if ( ! isset( $_deferred[ $taxonomy ] ) ) {
   3374 			$_deferred[ $taxonomy ] = array();
   3375 		}
   3376 		$_deferred[ $taxonomy ] = array_unique( array_merge( $_deferred[ $taxonomy ], $terms ) );
   3377 		return true;
   3378 	}
   3379 
   3380 	return wp_update_term_count_now( $terms, $taxonomy );
   3381 }
   3382 
   3383 /**
   3384  * Perform term count update immediately.
   3385  *
   3386  * @since 2.5.0
   3387  *
   3388  * @param array  $terms    The term_taxonomy_id of terms to update.
   3389  * @param string $taxonomy The context of the term.
   3390  * @return true Always true when complete.
   3391  */
   3392 function wp_update_term_count_now( $terms, $taxonomy ) {
   3393 	$terms = array_map( 'intval', $terms );
   3394 
   3395 	$taxonomy = get_taxonomy( $taxonomy );
   3396 	if ( ! empty( $taxonomy->update_count_callback ) ) {
   3397 		call_user_func( $taxonomy->update_count_callback, $terms, $taxonomy );
   3398 	} else {
   3399 		$object_types = (array) $taxonomy->object_type;
   3400 		foreach ( $object_types as &$object_type ) {
   3401 			if ( 0 === strpos( $object_type, 'attachment:' ) ) {
   3402 				list( $object_type ) = explode( ':', $object_type );
   3403 			}
   3404 		}
   3405 
   3406 		if ( array_filter( $object_types, 'post_type_exists' ) == $object_types ) {
   3407 			// Only post types are attached to this taxonomy.
   3408 			_update_post_term_count( $terms, $taxonomy );
   3409 		} else {
   3410 			// Default count updater.
   3411 			_update_generic_term_count( $terms, $taxonomy );
   3412 		}
   3413 	}
   3414 
   3415 	clean_term_cache( $terms, '', false );
   3416 
   3417 	return true;
   3418 }
   3419 
   3420 //
   3421 // Cache.
   3422 //
   3423 
   3424 /**
   3425  * Removes the taxonomy relationship to terms from the cache.
   3426  *
   3427  * Will remove the entire taxonomy relationship containing term `$object_id`. The
   3428  * term IDs have to exist within the taxonomy `$object_type` for the deletion to
   3429  * take place.
   3430  *
   3431  * @since 2.3.0
   3432  *
   3433  * @global bool $_wp_suspend_cache_invalidation
   3434  *
   3435  * @see get_object_taxonomies() for more on $object_type.
   3436  *
   3437  * @param int|array    $object_ids  Single or list of term object ID(s).
   3438  * @param array|string $object_type The taxonomy object type.
   3439  */
   3440 function clean_object_term_cache( $object_ids, $object_type ) {
   3441 	global $_wp_suspend_cache_invalidation;
   3442 
   3443 	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
   3444 		return;
   3445 	}
   3446 
   3447 	if ( ! is_array( $object_ids ) ) {
   3448 		$object_ids = array( $object_ids );
   3449 	}
   3450 
   3451 	$taxonomies = get_object_taxonomies( $object_type );
   3452 
   3453 	foreach ( $object_ids as $id ) {
   3454 		foreach ( $taxonomies as $taxonomy ) {
   3455 			wp_cache_delete( $id, "{$taxonomy}_relationships" );
   3456 		}
   3457 	}
   3458 
   3459 	/**
   3460 	 * Fires after the object term cache has been cleaned.
   3461 	 *
   3462 	 * @since 2.5.0
   3463 	 *
   3464 	 * @param array  $object_ids An array of object IDs.
   3465 	 * @param string $object_type Object type.
   3466 	 */
   3467 	do_action( 'clean_object_term_cache', $object_ids, $object_type );
   3468 }
   3469 
   3470 /**
   3471  * Will remove all of the term IDs from the cache.
   3472  *
   3473  * @since 2.3.0
   3474  *
   3475  * @global wpdb $wpdb                           WordPress database abstraction object.
   3476  * @global bool $_wp_suspend_cache_invalidation
   3477  *
   3478  * @param int|int[] $ids            Single or array of term IDs.
   3479  * @param string    $taxonomy       Optional. Taxonomy slug. Can be empty, in which case the taxonomies of the passed
   3480  *                                  term IDs will be used. Default empty.
   3481  * @param bool      $clean_taxonomy Optional. Whether to clean taxonomy wide caches (true), or just individual
   3482  *                                  term object caches (false). Default true.
   3483  */
   3484 function clean_term_cache( $ids, $taxonomy = '', $clean_taxonomy = true ) {
   3485 	global $wpdb, $_wp_suspend_cache_invalidation;
   3486 
   3487 	if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
   3488 		return;
   3489 	}
   3490 
   3491 	if ( ! is_array( $ids ) ) {
   3492 		$ids = array( $ids );
   3493 	}
   3494 
   3495 	$taxonomies = array();
   3496 	// If no taxonomy, assume tt_ids.
   3497 	if ( empty( $taxonomy ) ) {
   3498 		$tt_ids = array_map( 'intval', $ids );
   3499 		$tt_ids = implode( ', ', $tt_ids );
   3500 		$terms  = $wpdb->get_results( "SELECT term_id, taxonomy FROM $wpdb->term_taxonomy WHERE term_taxonomy_id IN ($tt_ids)" );
   3501 		$ids    = array();
   3502 
   3503 		foreach ( (array) $terms as $term ) {
   3504 			$taxonomies[] = $term->taxonomy;
   3505 			$ids[]        = $term->term_id;
   3506 			wp_cache_delete( $term->term_id, 'terms' );
   3507 		}
   3508 
   3509 		$taxonomies = array_unique( $taxonomies );
   3510 	} else {
   3511 		$taxonomies = array( $taxonomy );
   3512 
   3513 		foreach ( $taxonomies as $taxonomy ) {
   3514 			foreach ( $ids as $id ) {
   3515 				wp_cache_delete( $id, 'terms' );
   3516 			}
   3517 		}
   3518 	}
   3519 
   3520 	foreach ( $taxonomies as $taxonomy ) {
   3521 		if ( $clean_taxonomy ) {
   3522 			clean_taxonomy_cache( $taxonomy );
   3523 		}
   3524 
   3525 		/**
   3526 		 * Fires once after each taxonomy's term cache has been cleaned.
   3527 		 *
   3528 		 * @since 2.5.0
   3529 		 * @since 4.5.0 Added the `$clean_taxonomy` parameter.
   3530 		 *
   3531 		 * @param array  $ids            An array of term IDs.
   3532 		 * @param string $taxonomy       Taxonomy slug.
   3533 		 * @param bool   $clean_taxonomy Whether or not to clean taxonomy-wide caches
   3534 		 */
   3535 		do_action( 'clean_term_cache', $ids, $taxonomy, $clean_taxonomy );
   3536 	}
   3537 
   3538 	wp_cache_set( 'last_changed', microtime(), 'terms' );
   3539 }
   3540 
   3541 /**
   3542  * Clean the caches for a taxonomy.
   3543  *
   3544  * @since 4.9.0
   3545  *
   3546  * @param string $taxonomy Taxonomy slug.
   3547  */
   3548 function clean_taxonomy_cache( $taxonomy ) {
   3549 	wp_cache_delete( 'all_ids', $taxonomy );
   3550 	wp_cache_delete( 'get', $taxonomy );
   3551 
   3552 	// Regenerate cached hierarchy.
   3553 	delete_option( "{$taxonomy}_children" );
   3554 	_get_term_hierarchy( $taxonomy );
   3555 
   3556 	/**
   3557 	 * Fires after a taxonomy's caches have been cleaned.
   3558 	 *
   3559 	 * @since 4.9.0
   3560 	 *
   3561 	 * @param string $taxonomy Taxonomy slug.
   3562 	 */
   3563 	do_action( 'clean_taxonomy_cache', $taxonomy );
   3564 }
   3565 
   3566 /**
   3567  * Retrieves the cached term objects for the given object ID.
   3568  *
   3569  * Upstream functions (like get_the_terms() and is_object_in_term()) are
   3570  * responsible for populating the object-term relationship cache. The current
   3571  * function only fetches relationship data that is already in the cache.
   3572  *
   3573  * @since 2.3.0
   3574  * @since 4.7.0 Returns a `WP_Error` object if there's an error with
   3575  *              any of the matched terms.
   3576  *
   3577  * @param int    $id       Term object ID, for example a post, comment, or user ID.
   3578  * @param string $taxonomy Taxonomy name.
   3579  * @return bool|WP_Term[]|WP_Error Array of `WP_Term` objects, if cached.
   3580  *                                 False if cache is empty for `$taxonomy` and `$id`.
   3581  *                                 WP_Error if get_term() returns an error object for any term.
   3582  */
   3583 function get_object_term_cache( $id, $taxonomy ) {
   3584 	$_term_ids = wp_cache_get( $id, "{$taxonomy}_relationships" );
   3585 
   3586 	// We leave the priming of relationship caches to upstream functions.
   3587 	if ( false === $_term_ids ) {
   3588 		return false;
   3589 	}
   3590 
   3591 	// Backward compatibility for if a plugin is putting objects into the cache, rather than IDs.
   3592 	$term_ids = array();
   3593 	foreach ( $_term_ids as $term_id ) {
   3594 		if ( is_numeric( $term_id ) ) {
   3595 			$term_ids[] = (int) $term_id;
   3596 		} elseif ( isset( $term_id->term_id ) ) {
   3597 			$term_ids[] = (int) $term_id->term_id;
   3598 		}
   3599 	}
   3600 
   3601 	// Fill the term objects.
   3602 	_prime_term_caches( $term_ids );
   3603 
   3604 	$terms = array();
   3605 	foreach ( $term_ids as $term_id ) {
   3606 		$term = get_term( $term_id, $taxonomy );
   3607 		if ( is_wp_error( $term ) ) {
   3608 			return $term;
   3609 		}
   3610 
   3611 		$terms[] = $term;
   3612 	}
   3613 
   3614 	return $terms;
   3615 }
   3616 
   3617 /**
   3618  * Updates the cache for the given term object ID(s).
   3619  *
   3620  * Note: Due to performance concerns, great care should be taken to only update
   3621  * term caches when necessary. Processing time can increase exponentially depending
   3622  * on both the number of passed term IDs and the number of taxonomies those terms
   3623  * belong to.
   3624  *
   3625  * Caches will only be updated for terms not already cached.
   3626  *
   3627  * @since 2.3.0
   3628  *
   3629  * @param string|int[]    $object_ids  Comma-separated list or array of term object IDs.
   3630  * @param string|string[] $object_type The taxonomy object type or array of the same.
   3631  * @return void|false Void on success or if the `$object_ids` parameter is empty,
   3632  *                    false if all of the terms in `$object_ids` are already cached.
   3633  */
   3634 function update_object_term_cache( $object_ids, $object_type ) {
   3635 	if ( empty( $object_ids ) ) {
   3636 		return;
   3637 	}
   3638 
   3639 	if ( ! is_array( $object_ids ) ) {
   3640 		$object_ids = explode( ',', $object_ids );
   3641 	}
   3642 
   3643 	$object_ids     = array_map( 'intval', $object_ids );
   3644 	$non_cached_ids = array();
   3645 
   3646 	$taxonomies = get_object_taxonomies( $object_type );
   3647 
   3648 	foreach ( $taxonomies as $taxonomy ) {
   3649 		$cache_values = wp_cache_get_multiple( (array) $object_ids, "{$taxonomy}_relationships" );
   3650 
   3651 		foreach ( $cache_values as $id => $value ) {
   3652 			if ( false === $value ) {
   3653 				$non_cached_ids[] = $id;
   3654 			}
   3655 		}
   3656 	}
   3657 
   3658 	if ( empty( $non_cached_ids ) ) {
   3659 		return false;
   3660 	}
   3661 
   3662 	$non_cached_ids = array_unique( $non_cached_ids );
   3663 
   3664 	$terms = wp_get_object_terms(
   3665 		$non_cached_ids,
   3666 		$taxonomies,
   3667 		array(
   3668 			'fields'                 => 'all_with_object_id',
   3669 			'orderby'                => 'name',
   3670 			'update_term_meta_cache' => false,
   3671 		)
   3672 	);
   3673 
   3674 	$object_terms = array();
   3675 	foreach ( (array) $terms as $term ) {
   3676 		$object_terms[ $term->object_id ][ $term->taxonomy ][] = $term->term_id;
   3677 	}
   3678 
   3679 	foreach ( $non_cached_ids as $id ) {
   3680 		foreach ( $taxonomies as $taxonomy ) {
   3681 			if ( ! isset( $object_terms[ $id ][ $taxonomy ] ) ) {
   3682 				if ( ! isset( $object_terms[ $id ] ) ) {
   3683 					$object_terms[ $id ] = array();
   3684 				}
   3685 				$object_terms[ $id ][ $taxonomy ] = array();
   3686 			}
   3687 		}
   3688 	}
   3689 
   3690 	foreach ( $object_terms as $id => $value ) {
   3691 		foreach ( $value as $taxonomy => $terms ) {
   3692 			wp_cache_add( $id, $terms, "{$taxonomy}_relationships" );
   3693 		}
   3694 	}
   3695 }
   3696 
   3697 /**
   3698  * Updates Terms to Taxonomy in cache.
   3699  *
   3700  * @since 2.3.0
   3701  *
   3702  * @param WP_Term[] $terms    Array of term objects to change.
   3703  * @param string    $taxonomy Not used.
   3704  */
   3705 function update_term_cache( $terms, $taxonomy = '' ) {
   3706 	foreach ( (array) $terms as $term ) {
   3707 		// Create a copy in case the array was passed by reference.
   3708 		$_term = clone $term;
   3709 
   3710 		// Object ID should not be cached.
   3711 		unset( $_term->object_id );
   3712 
   3713 		wp_cache_add( $term->term_id, $_term, 'terms' );
   3714 	}
   3715 }
   3716 
   3717 //
   3718 // Private.
   3719 //
   3720 
   3721 /**
   3722  * Retrieves children of taxonomy as Term IDs.
   3723  *
   3724  * @access private
   3725  * @since 2.3.0
   3726  *
   3727  * @param string $taxonomy Taxonomy name.
   3728  * @return array Empty if $taxonomy isn't hierarchical or returns children as Term IDs.
   3729  */
   3730 function _get_term_hierarchy( $taxonomy ) {
   3731 	if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
   3732 		return array();
   3733 	}
   3734 	$children = get_option( "{$taxonomy}_children" );
   3735 
   3736 	if ( is_array( $children ) ) {
   3737 		return $children;
   3738 	}
   3739 	$children = array();
   3740 	$terms    = get_terms(
   3741 		array(
   3742 			'taxonomy'               => $taxonomy,
   3743 			'get'                    => 'all',
   3744 			'orderby'                => 'id',
   3745 			'fields'                 => 'id=>parent',
   3746 			'update_term_meta_cache' => false,
   3747 		)
   3748 	);
   3749 	foreach ( $terms as $term_id => $parent ) {
   3750 		if ( $parent > 0 ) {
   3751 			$children[ $parent ][] = $term_id;
   3752 		}
   3753 	}
   3754 	update_option( "{$taxonomy}_children", $children );
   3755 
   3756 	return $children;
   3757 }
   3758 
   3759 /**
   3760  * Get the subset of $terms that are descendants of $term_id.
   3761  *
   3762  * If `$terms` is an array of objects, then _get_term_children() returns an array of objects.
   3763  * If `$terms` is an array of IDs, then _get_term_children() returns an array of IDs.
   3764  *
   3765  * @access private
   3766  * @since 2.3.0
   3767  *
   3768  * @param int    $term_id   The ancestor term: all returned terms should be descendants of `$term_id`.
   3769  * @param array  $terms     The set of terms - either an array of term objects or term IDs - from which those that
   3770  *                          are descendants of $term_id will be chosen.
   3771  * @param string $taxonomy  The taxonomy which determines the hierarchy of the terms.
   3772  * @param array  $ancestors Optional. Term ancestors that have already been identified. Passed by reference, to keep
   3773  *                          track of found terms when recursing the hierarchy. The array of located ancestors is used
   3774  *                          to prevent infinite recursion loops. For performance, `term_ids` are used as array keys,
   3775  *                          with 1 as value. Default empty array.
   3776  * @return array|WP_Error The subset of $terms that are descendants of $term_id.
   3777  */
   3778 function _get_term_children( $term_id, $terms, $taxonomy, &$ancestors = array() ) {
   3779 	$empty_array = array();
   3780 	if ( empty( $terms ) ) {
   3781 		return $empty_array;
   3782 	}
   3783 
   3784 	$term_id      = (int) $term_id;
   3785 	$term_list    = array();
   3786 	$has_children = _get_term_hierarchy( $taxonomy );
   3787 
   3788 	if ( $term_id && ! isset( $has_children[ $term_id ] ) ) {
   3789 		return $empty_array;
   3790 	}
   3791 
   3792 	// Include the term itself in the ancestors array, so we can properly detect when a loop has occurred.
   3793 	if ( empty( $ancestors ) ) {
   3794 		$ancestors[ $term_id ] = 1;
   3795 	}
   3796 
   3797 	foreach ( (array) $terms as $term ) {
   3798 		$use_id = false;
   3799 		if ( ! is_object( $term ) ) {
   3800 			$term = get_term( $term, $taxonomy );
   3801 			if ( is_wp_error( $term ) ) {
   3802 				return $term;
   3803 			}
   3804 			$use_id = true;
   3805 		}
   3806 
   3807 		// Don't recurse if we've already identified the term as a child - this indicates a loop.
   3808 		if ( isset( $ancestors[ $term->term_id ] ) ) {
   3809 			continue;
   3810 		}
   3811 
   3812 		if ( (int) $term->parent === $term_id ) {
   3813 			if ( $use_id ) {
   3814 				$term_list[] = $term->term_id;
   3815 			} else {
   3816 				$term_list[] = $term;
   3817 			}
   3818 
   3819 			if ( ! isset( $has_children[ $term->term_id ] ) ) {
   3820 				continue;
   3821 			}
   3822 
   3823 			$ancestors[ $term->term_id ] = 1;
   3824 
   3825 			$children = _get_term_children( $term->term_id, $terms, $taxonomy, $ancestors );
   3826 			if ( $children ) {
   3827 				$term_list = array_merge( $term_list, $children );
   3828 			}
   3829 		}
   3830 	}
   3831 
   3832 	return $term_list;
   3833 }
   3834 
   3835 /**
   3836  * Add count of children to parent count.
   3837  *
   3838  * Recalculates term counts by including items from child terms. Assumes all
   3839  * relevant children are already in the $terms argument.
   3840  *
   3841  * @access private
   3842  * @since 2.3.0
   3843  *
   3844  * @global wpdb $wpdb WordPress database abstraction object.
   3845  *
   3846  * @param object[]|WP_Term[] $terms    List of term objects (passed by reference).
   3847  * @param string             $taxonomy Term context.
   3848  */
   3849 function _pad_term_counts( &$terms, $taxonomy ) {
   3850 	global $wpdb;
   3851 
   3852 	// This function only works for hierarchical taxonomies like post categories.
   3853 	if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
   3854 		return;
   3855 	}
   3856 
   3857 	$term_hier = _get_term_hierarchy( $taxonomy );
   3858 
   3859 	if ( empty( $term_hier ) ) {
   3860 		return;
   3861 	}
   3862 
   3863 	$term_items  = array();
   3864 	$terms_by_id = array();
   3865 	$term_ids    = array();
   3866 
   3867 	foreach ( (array) $terms as $key => $term ) {
   3868 		$terms_by_id[ $term->term_id ]       = & $terms[ $key ];
   3869 		$term_ids[ $term->term_taxonomy_id ] = $term->term_id;
   3870 	}
   3871 
   3872 	// Get the object and term IDs and stick them in a lookup table.
   3873 	$tax_obj      = get_taxonomy( $taxonomy );
   3874 	$object_types = esc_sql( $tax_obj->object_type );
   3875 	$results      = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode( ',', array_keys( $term_ids ) ) . ") AND post_type IN ('" . implode( "', '", $object_types ) . "') AND post_status = 'publish'" );
   3876 
   3877 	foreach ( $results as $row ) {
   3878 		$id = $term_ids[ $row->term_taxonomy_id ];
   3879 
   3880 		$term_items[ $id ][ $row->object_id ] = isset( $term_items[ $id ][ $row->object_id ] ) ? ++$term_items[ $id ][ $row->object_id ] : 1;
   3881 	}
   3882 
   3883 	// Touch every ancestor's lookup row for each post in each term.
   3884 	foreach ( $term_ids as $term_id ) {
   3885 		$child     = $term_id;
   3886 		$ancestors = array();
   3887 		while ( ! empty( $terms_by_id[ $child ] ) && $parent = $terms_by_id[ $child ]->parent ) {
   3888 			$ancestors[] = $child;
   3889 
   3890 			if ( ! empty( $term_items[ $term_id ] ) ) {
   3891 				foreach ( $term_items[ $term_id ] as $item_id => $touches ) {
   3892 					$term_items[ $parent ][ $item_id ] = isset( $term_items[ $parent ][ $item_id ] ) ? ++$term_items[ $parent ][ $item_id ] : 1;
   3893 				}
   3894 			}
   3895 
   3896 			$child = $parent;
   3897 
   3898 			if ( in_array( $parent, $ancestors, true ) ) {
   3899 				break;
   3900 			}
   3901 		}
   3902 	}
   3903 
   3904 	// Transfer the touched cells.
   3905 	foreach ( (array) $term_items as $id => $items ) {
   3906 		if ( isset( $terms_by_id[ $id ] ) ) {
   3907 			$terms_by_id[ $id ]->count = count( $items );
   3908 		}
   3909 	}
   3910 }
   3911 
   3912 /**
   3913  * Adds any terms from the given IDs to the cache that do not already exist in cache.
   3914  *
   3915  * @since 4.6.0
   3916  * @access private
   3917  *
   3918  * @global wpdb $wpdb WordPress database abstraction object.
   3919  *
   3920  * @param array $term_ids          Array of term IDs.
   3921  * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
   3922  */
   3923 function _prime_term_caches( $term_ids, $update_meta_cache = true ) {
   3924 	global $wpdb;
   3925 
   3926 	$non_cached_ids = _get_non_cached_ids( $term_ids, 'terms' );
   3927 	if ( ! empty( $non_cached_ids ) ) {
   3928 		$fresh_terms = $wpdb->get_results( sprintf( "SELECT t.*, tt.* FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id WHERE t.term_id IN (%s)", implode( ',', array_map( 'intval', $non_cached_ids ) ) ) );
   3929 
   3930 		update_term_cache( $fresh_terms, $update_meta_cache );
   3931 
   3932 		if ( $update_meta_cache ) {
   3933 			update_termmeta_cache( $non_cached_ids );
   3934 		}
   3935 	}
   3936 }
   3937 
   3938 //
   3939 // Default callbacks.
   3940 //
   3941 
   3942 /**
   3943  * Will update term count based on object types of the current taxonomy.
   3944  *
   3945  * Private function for the default callback for post_tag and category
   3946  * taxonomies.
   3947  *
   3948  * @access private
   3949  * @since 2.3.0
   3950  *
   3951  * @global wpdb $wpdb WordPress database abstraction object.
   3952  *
   3953  * @param int[]       $terms    List of Term taxonomy IDs.
   3954  * @param WP_Taxonomy $taxonomy Current taxonomy object of terms.
   3955  */
   3956 function _update_post_term_count( $terms, $taxonomy ) {
   3957 	global $wpdb;
   3958 
   3959 	$object_types = (array) $taxonomy->object_type;
   3960 
   3961 	foreach ( $object_types as &$object_type ) {
   3962 		list( $object_type ) = explode( ':', $object_type );
   3963 	}
   3964 
   3965 	$object_types = array_unique( $object_types );
   3966 
   3967 	$check_attachments = array_search( 'attachment', $object_types, true );
   3968 	if ( false !== $check_attachments ) {
   3969 		unset( $object_types[ $check_attachments ] );
   3970 		$check_attachments = true;
   3971 	}
   3972 
   3973 	if ( $object_types ) {
   3974 		$object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
   3975 	}
   3976 
   3977 	$post_statuses = array( 'publish' );
   3978 
   3979 	/**
   3980 	 * Filters the post statuses for updating the term count.
   3981 	 *
   3982 	 * @since 5.7.0
   3983 	 *
   3984 	 * @param string[]    $post_statuses List of post statuses to include in the count. Default is 'publish'.
   3985 	 * @param WP_Taxonomy $taxonomy      Current taxonomy object.
   3986 	 */
   3987 	$post_statuses = esc_sql( apply_filters( 'update_post_term_count_statuses', $post_statuses, $taxonomy ) );
   3988 
   3989 	foreach ( (array) $terms as $term ) {
   3990 		$count = 0;
   3991 
   3992 		// Attachments can be 'inherit' status, we need to base count off the parent's status if so.
   3993 		if ( $check_attachments ) {
   3994 			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
   3995 			$count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status IN ('" . implode( "', '", $post_statuses ) . "') OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) IN ('" . implode( "', '", $post_statuses ) . "') ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $term ) );
   3996 		}
   3997 
   3998 		if ( $object_types ) {
   3999 			// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
   4000 			$count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status IN ('" . implode( "', '", $post_statuses ) . "') AND post_type IN ('" . implode( "', '", $object_types ) . "') AND term_taxonomy_id = %d", $term ) );
   4001 		}
   4002 
   4003 		/** This action is documented in wp-includes/taxonomy.php */
   4004 		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
   4005 		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
   4006 
   4007 		/** This action is documented in wp-includes/taxonomy.php */
   4008 		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
   4009 	}
   4010 }
   4011 
   4012 /**
   4013  * Will update term count based on number of objects.
   4014  *
   4015  * Default callback for the 'link_category' taxonomy.
   4016  *
   4017  * @since 3.3.0
   4018  *
   4019  * @global wpdb $wpdb WordPress database abstraction object.
   4020  *
   4021  * @param int[]       $terms    List of term taxonomy IDs.
   4022  * @param WP_Taxonomy $taxonomy Current taxonomy object of terms.
   4023  */
   4024 function _update_generic_term_count( $terms, $taxonomy ) {
   4025 	global $wpdb;
   4026 
   4027 	foreach ( (array) $terms as $term ) {
   4028 		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
   4029 
   4030 		/** This action is documented in wp-includes/taxonomy.php */
   4031 		do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
   4032 		$wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
   4033 
   4034 		/** This action is documented in wp-includes/taxonomy.php */
   4035 		do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
   4036 	}
   4037 }
   4038 
   4039 /**
   4040  * Create a new term for a term_taxonomy item that currently shares its term
   4041  * with another term_taxonomy.
   4042  *
   4043  * @ignore
   4044  * @since 4.2.0
   4045  * @since 4.3.0 Introduced `$record` parameter. Also, `$term_id` and
   4046  *              `$term_taxonomy_id` can now accept objects.
   4047  *
   4048  * @global wpdb $wpdb WordPress database abstraction object.
   4049  *
   4050  * @param int|object $term_id          ID of the shared term, or the shared term object.
   4051  * @param int|object $term_taxonomy_id ID of the term_taxonomy item to receive a new term, or the term_taxonomy object
   4052  *                                     (corresponding to a row from the term_taxonomy table).
   4053  * @param bool       $record           Whether to record data about the split term in the options table. The recording
   4054  *                                     process has the potential to be resource-intensive, so during batch operations
   4055  *                                     it can be beneficial to skip inline recording and do it just once, after the
   4056  *                                     batch is processed. Only set this to `false` if you know what you are doing.
   4057  *                                     Default: true.
   4058  * @return int|WP_Error When the current term does not need to be split (or cannot be split on the current
   4059  *                      database schema), `$term_id` is returned. When the term is successfully split, the
   4060  *                      new term_id is returned. A WP_Error is returned for miscellaneous errors.
   4061  */
   4062 function _split_shared_term( $term_id, $term_taxonomy_id, $record = true ) {
   4063 	global $wpdb;
   4064 
   4065 	if ( is_object( $term_id ) ) {
   4066 		$shared_term = $term_id;
   4067 		$term_id     = (int) $shared_term->term_id;
   4068 	}
   4069 
   4070 	if ( is_object( $term_taxonomy_id ) ) {
   4071 		$term_taxonomy    = $term_taxonomy_id;
   4072 		$term_taxonomy_id = (int) $term_taxonomy->term_taxonomy_id;
   4073 	}
   4074 
   4075 	// If there are no shared term_taxonomy rows, there's nothing to do here.
   4076 	$shared_tt_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy tt WHERE tt.term_id = %d AND tt.term_taxonomy_id != %d", $term_id, $term_taxonomy_id ) );
   4077 
   4078 	if ( ! $shared_tt_count ) {
   4079 		return $term_id;
   4080 	}
   4081 
   4082 	/*
   4083 	 * Verify that the term_taxonomy_id passed to the function is actually associated with the term_id.
   4084 	 * If there's a mismatch, it may mean that the term is already split. Return the actual term_id from the db.
   4085 	 */
   4086 	$check_term_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT term_id FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
   4087 	if ( $check_term_id !== $term_id ) {
   4088 		return $check_term_id;
   4089 	}
   4090 
   4091 	// Pull up data about the currently shared slug, which we'll use to populate the new one.
   4092 	if ( empty( $shared_term ) ) {
   4093 		$shared_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.* FROM $wpdb->terms t WHERE t.term_id = %d", $term_id ) );
   4094 	}
   4095 
   4096 	$new_term_data = array(
   4097 		'name'       => $shared_term->name,
   4098 		'slug'       => $shared_term->slug,
   4099 		'term_group' => $shared_term->term_group,
   4100 	);
   4101 
   4102 	if ( false === $wpdb->insert( $wpdb->terms, $new_term_data ) ) {
   4103 		return new WP_Error( 'db_insert_error', __( 'Could not split shared term.' ), $wpdb->last_error );
   4104 	}
   4105 
   4106 	$new_term_id = (int) $wpdb->insert_id;
   4107 
   4108 	// Update the existing term_taxonomy to point to the newly created term.
   4109 	$wpdb->update(
   4110 		$wpdb->term_taxonomy,
   4111 		array( 'term_id' => $new_term_id ),
   4112 		array( 'term_taxonomy_id' => $term_taxonomy_id )
   4113 	);
   4114 
   4115 	// Reassign child terms to the new parent.
   4116 	if ( empty( $term_taxonomy ) ) {
   4117 		$term_taxonomy = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->term_taxonomy WHERE term_taxonomy_id = %d", $term_taxonomy_id ) );
   4118 	}
   4119 
   4120 	$children_tt_ids = $wpdb->get_col( $wpdb->prepare( "SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE parent = %d AND taxonomy = %s", $term_id, $term_taxonomy->taxonomy ) );
   4121 	if ( ! empty( $children_tt_ids ) ) {
   4122 		foreach ( $children_tt_ids as $child_tt_id ) {
   4123 			$wpdb->update(
   4124 				$wpdb->term_taxonomy,
   4125 				array( 'parent' => $new_term_id ),
   4126 				array( 'term_taxonomy_id' => $child_tt_id )
   4127 			);
   4128 			clean_term_cache( (int) $child_tt_id, '', false );
   4129 		}
   4130 	} else {
   4131 		// If the term has no children, we must force its taxonomy cache to be rebuilt separately.
   4132 		clean_term_cache( $new_term_id, $term_taxonomy->taxonomy, false );
   4133 	}
   4134 
   4135 	clean_term_cache( $term_id, $term_taxonomy->taxonomy, false );
   4136 
   4137 	/*
   4138 	 * Taxonomy cache clearing is delayed to avoid race conditions that may occur when
   4139 	 * regenerating the taxonomy's hierarchy tree.
   4140 	 */
   4141 	$taxonomies_to_clean = array( $term_taxonomy->taxonomy );
   4142 
   4143 	// Clean the cache for term taxonomies formerly shared with the current term.
   4144 	$shared_term_taxonomies = $wpdb->get_col( $wpdb->prepare( "SELECT taxonomy FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
   4145 	$taxonomies_to_clean    = array_merge( $taxonomies_to_clean, $shared_term_taxonomies );
   4146 
   4147 	foreach ( $taxonomies_to_clean as $taxonomy_to_clean ) {
   4148 		clean_taxonomy_cache( $taxonomy_to_clean );
   4149 	}
   4150 
   4151 	// Keep a record of term_ids that have been split, keyed by old term_id. See wp_get_split_term().
   4152 	if ( $record ) {
   4153 		$split_term_data = get_option( '_split_terms', array() );
   4154 		if ( ! isset( $split_term_data[ $term_id ] ) ) {
   4155 			$split_term_data[ $term_id ] = array();
   4156 		}
   4157 
   4158 		$split_term_data[ $term_id ][ $term_taxonomy->taxonomy ] = $new_term_id;
   4159 		update_option( '_split_terms', $split_term_data );
   4160 	}
   4161 
   4162 	// If we've just split the final shared term, set the "finished" flag.
   4163 	$shared_terms_exist = $wpdb->get_results(
   4164 		"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
   4165 		 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
   4166 		 GROUP BY t.term_id
   4167 		 HAVING term_tt_count > 1
   4168 		 LIMIT 1"
   4169 	);
   4170 	if ( ! $shared_terms_exist ) {
   4171 		update_option( 'finished_splitting_shared_terms', true );
   4172 	}
   4173 
   4174 	/**
   4175 	 * Fires after a previously shared taxonomy term is split into two separate terms.
   4176 	 *
   4177 	 * @since 4.2.0
   4178 	 *
   4179 	 * @param int    $term_id          ID of the formerly shared term.
   4180 	 * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
   4181 	 * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
   4182 	 * @param string $taxonomy         Taxonomy for the split term.
   4183 	 */
   4184 	do_action( 'split_shared_term', $term_id, $new_term_id, $term_taxonomy_id, $term_taxonomy->taxonomy );
   4185 
   4186 	return $new_term_id;
   4187 }
   4188 
   4189 /**
   4190  * Splits a batch of shared taxonomy terms.
   4191  *
   4192  * @since 4.3.0
   4193  *
   4194  * @global wpdb $wpdb WordPress database abstraction object.
   4195  */
   4196 function _wp_batch_split_terms() {
   4197 	global $wpdb;
   4198 
   4199 	$lock_name = 'term_split.lock';
   4200 
   4201 	// Try to lock.
   4202 	$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'no') /* LOCK */", $lock_name, time() ) );
   4203 
   4204 	if ( ! $lock_result ) {
   4205 		$lock_result = get_option( $lock_name );
   4206 
   4207 		// Bail if we were unable to create a lock, or if the existing lock is still valid.
   4208 		if ( ! $lock_result || ( $lock_result > ( time() - HOUR_IN_SECONDS ) ) ) {
   4209 			wp_schedule_single_event( time() + ( 5 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
   4210 			return;
   4211 		}
   4212 	}
   4213 
   4214 	// Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
   4215 	update_option( $lock_name, time() );
   4216 
   4217 	// Get a list of shared terms (those with more than one associated row in term_taxonomy).
   4218 	$shared_terms = $wpdb->get_results(
   4219 		"SELECT tt.term_id, t.*, count(*) as term_tt_count FROM {$wpdb->term_taxonomy} tt
   4220 		 LEFT JOIN {$wpdb->terms} t ON t.term_id = tt.term_id
   4221 		 GROUP BY t.term_id
   4222 		 HAVING term_tt_count > 1
   4223 		 LIMIT 10"
   4224 	);
   4225 
   4226 	// No more terms, we're done here.
   4227 	if ( ! $shared_terms ) {
   4228 		update_option( 'finished_splitting_shared_terms', true );
   4229 		delete_option( $lock_name );
   4230 		return;
   4231 	}
   4232 
   4233 	// Shared terms found? We'll need to run this script again.
   4234 	wp_schedule_single_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'wp_split_shared_term_batch' );
   4235 
   4236 	// Rekey shared term array for faster lookups.
   4237 	$_shared_terms = array();
   4238 	foreach ( $shared_terms as $shared_term ) {
   4239 		$term_id                   = (int) $shared_term->term_id;
   4240 		$_shared_terms[ $term_id ] = $shared_term;
   4241 	}
   4242 	$shared_terms = $_shared_terms;
   4243 
   4244 	// Get term taxonomy data for all shared terms.
   4245 	$shared_term_ids = implode( ',', array_keys( $shared_terms ) );
   4246 	$shared_tts      = $wpdb->get_results( "SELECT * FROM {$wpdb->term_taxonomy} WHERE `term_id` IN ({$shared_term_ids})" );
   4247 
   4248 	// Split term data recording is slow, so we do it just once, outside the loop.
   4249 	$split_term_data    = get_option( '_split_terms', array() );
   4250 	$skipped_first_term = array();
   4251 	$taxonomies         = array();
   4252 	foreach ( $shared_tts as $shared_tt ) {
   4253 		$term_id = (int) $shared_tt->term_id;
   4254 
   4255 		// Don't split the first tt belonging to a given term_id.
   4256 		if ( ! isset( $skipped_first_term[ $term_id ] ) ) {
   4257 			$skipped_first_term[ $term_id ] = 1;
   4258 			continue;
   4259 		}
   4260 
   4261 		if ( ! isset( $split_term_data[ $term_id ] ) ) {
   4262 			$split_term_data[ $term_id ] = array();
   4263 		}
   4264 
   4265 		// Keep track of taxonomies whose hierarchies need flushing.
   4266 		if ( ! isset( $taxonomies[ $shared_tt->taxonomy ] ) ) {
   4267 			$taxonomies[ $shared_tt->taxonomy ] = 1;
   4268 		}
   4269 
   4270 		// Split the term.
   4271 		$split_term_data[ $term_id ][ $shared_tt->taxonomy ] = _split_shared_term( $shared_terms[ $term_id ], $shared_tt, false );
   4272 	}
   4273 
   4274 	// Rebuild the cached hierarchy for each affected taxonomy.
   4275 	foreach ( array_keys( $taxonomies ) as $tax ) {
   4276 		delete_option( "{$tax}_children" );
   4277 		_get_term_hierarchy( $tax );
   4278 	}
   4279 
   4280 	update_option( '_split_terms', $split_term_data );
   4281 
   4282 	delete_option( $lock_name );
   4283 }
   4284 
   4285 /**
   4286  * In order to avoid the _wp_batch_split_terms() job being accidentally removed,
   4287  * check that it's still scheduled while we haven't finished splitting terms.
   4288  *
   4289  * @ignore
   4290  * @since 4.3.0
   4291  */
   4292 function _wp_check_for_scheduled_split_terms() {
   4293 	if ( ! get_option( 'finished_splitting_shared_terms' ) && ! wp_next_scheduled( 'wp_split_shared_term_batch' ) ) {
   4294 		wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'wp_split_shared_term_batch' );
   4295 	}
   4296 }
   4297 
   4298 /**
   4299  * Check default categories when a term gets split to see if any of them need to be updated.
   4300  *
   4301  * @ignore
   4302  * @since 4.2.0
   4303  *
   4304  * @param int    $term_id          ID of the formerly shared term.
   4305  * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
   4306  * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
   4307  * @param string $taxonomy         Taxonomy for the split term.
   4308  */
   4309 function _wp_check_split_default_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
   4310 	if ( 'category' !== $taxonomy ) {
   4311 		return;
   4312 	}
   4313 
   4314 	foreach ( array( 'default_category', 'default_link_category', 'default_email_category' ) as $option ) {
   4315 		if ( (int) get_option( $option, -1 ) === $term_id ) {
   4316 			update_option( $option, $new_term_id );
   4317 		}
   4318 	}
   4319 }
   4320 
   4321 /**
   4322  * Check menu items when a term gets split to see if any of them need to be updated.
   4323  *
   4324  * @ignore
   4325  * @since 4.2.0
   4326  *
   4327  * @global wpdb $wpdb WordPress database abstraction object.
   4328  *
   4329  * @param int    $term_id          ID of the formerly shared term.
   4330  * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
   4331  * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
   4332  * @param string $taxonomy         Taxonomy for the split term.
   4333  */
   4334 function _wp_check_split_terms_in_menus( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
   4335 	global $wpdb;
   4336 	$post_ids = $wpdb->get_col(
   4337 		$wpdb->prepare(
   4338 			"SELECT m1.post_id
   4339 		FROM {$wpdb->postmeta} AS m1
   4340 			INNER JOIN {$wpdb->postmeta} AS m2 ON ( m2.post_id = m1.post_id )
   4341 			INNER JOIN {$wpdb->postmeta} AS m3 ON ( m3.post_id = m1.post_id )
   4342 		WHERE ( m1.meta_key = '_menu_item_type' AND m1.meta_value = 'taxonomy' )
   4343 			AND ( m2.meta_key = '_menu_item_object' AND m2.meta_value = %s )
   4344 			AND ( m3.meta_key = '_menu_item_object_id' AND m3.meta_value = %d )",
   4345 			$taxonomy,
   4346 			$term_id
   4347 		)
   4348 	);
   4349 
   4350 	if ( $post_ids ) {
   4351 		foreach ( $post_ids as $post_id ) {
   4352 			update_post_meta( $post_id, '_menu_item_object_id', $new_term_id, $term_id );
   4353 		}
   4354 	}
   4355 }
   4356 
   4357 /**
   4358  * If the term being split is a nav_menu, change associations.
   4359  *
   4360  * @ignore
   4361  * @since 4.3.0
   4362  *
   4363  * @param int    $term_id          ID of the formerly shared term.
   4364  * @param int    $new_term_id      ID of the new term created for the $term_taxonomy_id.
   4365  * @param int    $term_taxonomy_id ID for the term_taxonomy row affected by the split.
   4366  * @param string $taxonomy         Taxonomy for the split term.
   4367  */
   4368 function _wp_check_split_nav_menu_terms( $term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) {
   4369 	if ( 'nav_menu' !== $taxonomy ) {
   4370 		return;
   4371 	}
   4372 
   4373 	// Update menu locations.
   4374 	$locations = get_nav_menu_locations();
   4375 	foreach ( $locations as $location => $menu_id ) {
   4376 		if ( $term_id === $menu_id ) {
   4377 			$locations[ $location ] = $new_term_id;
   4378 		}
   4379 	}
   4380 	set_theme_mod( 'nav_menu_locations', $locations );
   4381 }
   4382 
   4383 /**
   4384  * Get data about terms that previously shared a single term_id, but have since been split.
   4385  *
   4386  * @since 4.2.0
   4387  *
   4388  * @param int $old_term_id Term ID. This is the old, pre-split term ID.
   4389  * @return array Array of new term IDs, keyed by taxonomy.
   4390  */
   4391 function wp_get_split_terms( $old_term_id ) {
   4392 	$split_terms = get_option( '_split_terms', array() );
   4393 
   4394 	$terms = array();
   4395 	if ( isset( $split_terms[ $old_term_id ] ) ) {
   4396 		$terms = $split_terms[ $old_term_id ];
   4397 	}
   4398 
   4399 	return $terms;
   4400 }
   4401 
   4402 /**
   4403  * Get the new term ID corresponding to a previously split term.
   4404  *
   4405  * @since 4.2.0
   4406  *
   4407  * @param int    $old_term_id Term ID. This is the old, pre-split term ID.
   4408  * @param string $taxonomy    Taxonomy that the term belongs to.
   4409  * @return int|false If a previously split term is found corresponding to the old term_id and taxonomy,
   4410  *                   the new term_id will be returned. If no previously split term is found matching
   4411  *                   the parameters, returns false.
   4412  */
   4413 function wp_get_split_term( $old_term_id, $taxonomy ) {
   4414 	$split_terms = wp_get_split_terms( $old_term_id );
   4415 
   4416 	$term_id = false;
   4417 	if ( isset( $split_terms[ $taxonomy ] ) ) {
   4418 		$term_id = (int) $split_terms[ $taxonomy ];
   4419 	}
   4420 
   4421 	return $term_id;
   4422 }
   4423 
   4424 /**
   4425  * Determine whether a term is shared between multiple taxonomies.
   4426  *
   4427  * Shared taxonomy terms began to be split in 4.3, but failed cron tasks or
   4428  * other delays in upgrade routines may cause shared terms to remain.
   4429  *
   4430  * @since 4.4.0
   4431  *
   4432  * @param int $term_id Term ID.
   4433  * @return bool Returns false if a term is not shared between multiple taxonomies or
   4434  *              if splitting shared taxonomy terms is finished.
   4435  */
   4436 function wp_term_is_shared( $term_id ) {
   4437 	global $wpdb;
   4438 
   4439 	if ( get_option( 'finished_splitting_shared_terms' ) ) {
   4440 		return false;
   4441 	}
   4442 
   4443 	$tt_count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term_id ) );
   4444 
   4445 	return $tt_count > 1;
   4446 }
   4447 
   4448 /**
   4449  * Generate a permalink for a taxonomy term archive.
   4450  *
   4451  * @since 2.5.0
   4452  *
   4453  * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
   4454  *
   4455  * @param WP_Term|int|string $term     The term object, ID, or slug whose link will be retrieved.
   4456  * @param string             $taxonomy Optional. Taxonomy. Default empty.
   4457  * @return string|WP_Error URL of the taxonomy term archive on success, WP_Error if term does not exist.
   4458  */
   4459 function get_term_link( $term, $taxonomy = '' ) {
   4460 	global $wp_rewrite;
   4461 
   4462 	if ( ! is_object( $term ) ) {
   4463 		if ( is_int( $term ) ) {
   4464 			$term = get_term( $term, $taxonomy );
   4465 		} else {
   4466 			$term = get_term_by( 'slug', $term, $taxonomy );
   4467 		}
   4468 	}
   4469 
   4470 	if ( ! is_object( $term ) ) {
   4471 		$term = new WP_Error( 'invalid_term', __( 'Empty Term.' ) );
   4472 	}
   4473 
   4474 	if ( is_wp_error( $term ) ) {
   4475 		return $term;
   4476 	}
   4477 
   4478 	$taxonomy = $term->taxonomy;
   4479 
   4480 	$termlink = $wp_rewrite->get_extra_permastruct( $taxonomy );
   4481 
   4482 	/**
   4483 	 * Filters the permalink structure for a term before token replacement occurs.
   4484 	 *
   4485 	 * @since 4.9.0
   4486 	 *
   4487 	 * @param string  $termlink The permalink structure for the term's taxonomy.
   4488 	 * @param WP_Term $term     The term object.
   4489 	 */
   4490 	$termlink = apply_filters( 'pre_term_link', $termlink, $term );
   4491 
   4492 	$slug = $term->slug;
   4493 	$t    = get_taxonomy( $taxonomy );
   4494 
   4495 	if ( empty( $termlink ) ) {
   4496 		if ( 'category' === $taxonomy ) {
   4497 			$termlink = '?cat=' . $term->term_id;
   4498 		} elseif ( $t->query_var ) {
   4499 			$termlink = "?$t->query_var=$slug";
   4500 		} else {
   4501 			$termlink = "?taxonomy=$taxonomy&term=$slug";
   4502 		}
   4503 		$termlink = home_url( $termlink );
   4504 	} else {
   4505 		if ( ! empty( $t->rewrite['hierarchical'] ) ) {
   4506 			$hierarchical_slugs = array();
   4507 			$ancestors          = get_ancestors( $term->term_id, $taxonomy, 'taxonomy' );
   4508 			foreach ( (array) $ancestors as $ancestor ) {
   4509 				$ancestor_term        = get_term( $ancestor, $taxonomy );
   4510 				$hierarchical_slugs[] = $ancestor_term->slug;
   4511 			}
   4512 			$hierarchical_slugs   = array_reverse( $hierarchical_slugs );
   4513 			$hierarchical_slugs[] = $slug;
   4514 			$termlink             = str_replace( "%$taxonomy%", implode( '/', $hierarchical_slugs ), $termlink );
   4515 		} else {
   4516 			$termlink = str_replace( "%$taxonomy%", $slug, $termlink );
   4517 		}
   4518 		$termlink = home_url( user_trailingslashit( $termlink, 'category' ) );
   4519 	}
   4520 
   4521 	// Back compat filters.
   4522 	if ( 'post_tag' === $taxonomy ) {
   4523 
   4524 		/**
   4525 		 * Filters the tag link.
   4526 		 *
   4527 		 * @since 2.3.0
   4528 		 * @since 2.5.0 Deprecated in favor of {@see 'term_link'} filter.
   4529 		 * @since 5.4.1 Restored (un-deprecated).
   4530 		 *
   4531 		 * @param string $termlink Tag link URL.
   4532 		 * @param int    $term_id  Term ID.
   4533 		 */
   4534 		$termlink = apply_filters( 'tag_link', $termlink, $term->term_id );
   4535 	} elseif ( 'category' === $taxonomy ) {
   4536 
   4537 		/**
   4538 		 * Filters the category link.
   4539 		 *
   4540 		 * @since 1.5.0
   4541 		 * @since 2.5.0 Deprecated in favor of {@see 'term_link'} filter.
   4542 		 * @since 5.4.1 Restored (un-deprecated).
   4543 		 *
   4544 		 * @param string $termlink Category link URL.
   4545 		 * @param int    $term_id  Term ID.
   4546 		 */
   4547 		$termlink = apply_filters( 'category_link', $termlink, $term->term_id );
   4548 	}
   4549 
   4550 	/**
   4551 	 * Filters the term link.
   4552 	 *
   4553 	 * @since 2.5.0
   4554 	 *
   4555 	 * @param string  $termlink Term link URL.
   4556 	 * @param WP_Term $term     Term object.
   4557 	 * @param string  $taxonomy Taxonomy slug.
   4558 	 */
   4559 	return apply_filters( 'term_link', $termlink, $term, $taxonomy );
   4560 }
   4561 
   4562 /**
   4563  * Display the taxonomies of a post with available options.
   4564  *
   4565  * This function can be used within the loop to display the taxonomies for a
   4566  * post without specifying the Post ID. You can also use it outside the Loop to
   4567  * display the taxonomies for a specific post.
   4568  *
   4569  * @since 2.5.0
   4570  *
   4571  * @param array $args {
   4572  *     Arguments about which post to use and how to format the output. Shares all of the arguments
   4573  *     supported by get_the_taxonomies(), in addition to the following.
   4574  *
   4575  *     @type  int|WP_Post $post   Post ID or object to get taxonomies of. Default current post.
   4576  *     @type  string      $before Displays before the taxonomies. Default empty string.
   4577  *     @type  string      $sep    Separates each taxonomy. Default is a space.
   4578  *     @type  string      $after  Displays after the taxonomies. Default empty string.
   4579  * }
   4580  */
   4581 function the_taxonomies( $args = array() ) {
   4582 	$defaults = array(
   4583 		'post'   => 0,
   4584 		'before' => '',
   4585 		'sep'    => ' ',
   4586 		'after'  => '',
   4587 	);
   4588 
   4589 	$parsed_args = wp_parse_args( $args, $defaults );
   4590 
   4591 	echo $parsed_args['before'] . implode( $parsed_args['sep'], get_the_taxonomies( $parsed_args['post'], $parsed_args ) ) . $parsed_args['after'];
   4592 }
   4593 
   4594 /**
   4595  * Retrieve all taxonomies associated with a post.
   4596  *
   4597  * This function can be used within the loop. It will also return an array of
   4598  * the taxonomies with links to the taxonomy and name.
   4599  *
   4600  * @since 2.5.0
   4601  *
   4602  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
   4603  * @param array       $args {
   4604  *           Optional. Arguments about how to format the list of taxonomies. Default empty array.
   4605  *
   4606  *     @type string $template      Template for displaying a taxonomy label and list of terms.
   4607  *                                 Default is "Label: Terms."
   4608  *     @type string $term_template Template for displaying a single term in the list. Default is the term name
   4609  *                                 linked to its archive.
   4610  * }
   4611  * @return array List of taxonomies.
   4612  */
   4613 function get_the_taxonomies( $post = 0, $args = array() ) {
   4614 	$post = get_post( $post );
   4615 
   4616 	$args = wp_parse_args(
   4617 		$args,
   4618 		array(
   4619 			/* translators: %s: Taxonomy label, %l: List of terms formatted as per $term_template. */
   4620 			'template'      => __( '%s: %l.' ),
   4621 			'term_template' => '<a href="%1$s">%2$s</a>',
   4622 		)
   4623 	);
   4624 
   4625 	$taxonomies = array();
   4626 
   4627 	if ( ! $post ) {
   4628 		return $taxonomies;
   4629 	}
   4630 
   4631 	foreach ( get_object_taxonomies( $post ) as $taxonomy ) {
   4632 		$t = (array) get_taxonomy( $taxonomy );
   4633 		if ( empty( $t['label'] ) ) {
   4634 			$t['label'] = $taxonomy;
   4635 		}
   4636 		if ( empty( $t['args'] ) ) {
   4637 			$t['args'] = array();
   4638 		}
   4639 		if ( empty( $t['template'] ) ) {
   4640 			$t['template'] = $args['template'];
   4641 		}
   4642 		if ( empty( $t['term_template'] ) ) {
   4643 			$t['term_template'] = $args['term_template'];
   4644 		}
   4645 
   4646 		$terms = get_object_term_cache( $post->ID, $taxonomy );
   4647 		if ( false === $terms ) {
   4648 			$terms = wp_get_object_terms( $post->ID, $taxonomy, $t['args'] );
   4649 		}
   4650 		$links = array();
   4651 
   4652 		foreach ( $terms as $term ) {
   4653 			$links[] = wp_sprintf( $t['term_template'], esc_attr( get_term_link( $term ) ), $term->name );
   4654 		}
   4655 		if ( $links ) {
   4656 			$taxonomies[ $taxonomy ] = wp_sprintf( $t['template'], $t['label'], $links, $terms );
   4657 		}
   4658 	}
   4659 	return $taxonomies;
   4660 }
   4661 
   4662 /**
   4663  * Retrieve all taxonomy names for the given post.
   4664  *
   4665  * @since 2.5.0
   4666  *
   4667  * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
   4668  * @return string[] An array of all taxonomy names for the given post.
   4669  */
   4670 function get_post_taxonomies( $post = 0 ) {
   4671 	$post = get_post( $post );
   4672 
   4673 	return get_object_taxonomies( $post );
   4674 }
   4675 
   4676 /**
   4677  * Determine if the given object is associated with any of the given terms.
   4678  *
   4679  * The given terms are checked against the object's terms' term_ids, names and slugs.
   4680  * Terms given as integers will only be checked against the object's terms' term_ids.
   4681  * If no terms are given, determines if object is associated with any terms in the given taxonomy.
   4682  *
   4683  * @since 2.7.0
   4684  *
   4685  * @param int                       $object_id ID of the object (post ID, link ID, ...).
   4686  * @param string                    $taxonomy  Single taxonomy name.
   4687  * @param int|string|int[]|string[] $terms     Optional. Term ID, name, slug, or array of such
   4688  *                                             to check against. Default null.
   4689  * @return bool|WP_Error WP_Error on input error.
   4690  */
   4691 function is_object_in_term( $object_id, $taxonomy, $terms = null ) {
   4692 	$object_id = (int) $object_id;
   4693 	if ( ! $object_id ) {
   4694 		return new WP_Error( 'invalid_object', __( 'Invalid object ID.' ) );
   4695 	}
   4696 
   4697 	$object_terms = get_object_term_cache( $object_id, $taxonomy );
   4698 	if ( false === $object_terms ) {
   4699 		$object_terms = wp_get_object_terms( $object_id, $taxonomy, array( 'update_term_meta_cache' => false ) );
   4700 		if ( is_wp_error( $object_terms ) ) {
   4701 			return $object_terms;
   4702 		}
   4703 
   4704 		wp_cache_set( $object_id, wp_list_pluck( $object_terms, 'term_id' ), "{$taxonomy}_relationships" );
   4705 	}
   4706 
   4707 	if ( is_wp_error( $object_terms ) ) {
   4708 		return $object_terms;
   4709 	}
   4710 	if ( empty( $object_terms ) ) {
   4711 		return false;
   4712 	}
   4713 	if ( empty( $terms ) ) {
   4714 		return ( ! empty( $object_terms ) );
   4715 	}
   4716 
   4717 	$terms = (array) $terms;
   4718 
   4719 	$ints = array_filter( $terms, 'is_int' );
   4720 	if ( $ints ) {
   4721 		$strs = array_diff( $terms, $ints );
   4722 	} else {
   4723 		$strs =& $terms;
   4724 	}
   4725 
   4726 	foreach ( $object_terms as $object_term ) {
   4727 		// If term is an int, check against term_ids only.
   4728 		if ( $ints && in_array( $object_term->term_id, $ints, true ) ) {
   4729 			return true;
   4730 		}
   4731 
   4732 		if ( $strs ) {
   4733 			// Only check numeric strings against term_id, to avoid false matches due to type juggling.
   4734 			$numeric_strs = array_map( 'intval', array_filter( $strs, 'is_numeric' ) );
   4735 			if ( in_array( $object_term->term_id, $numeric_strs, true ) ) {
   4736 				return true;
   4737 			}
   4738 
   4739 			if ( in_array( $object_term->name, $strs, true ) ) {
   4740 				return true;
   4741 			}
   4742 			if ( in_array( $object_term->slug, $strs, true ) ) {
   4743 				return true;
   4744 			}
   4745 		}
   4746 	}
   4747 
   4748 	return false;
   4749 }
   4750 
   4751 /**
   4752  * Determine if the given object type is associated with the given taxonomy.
   4753  *
   4754  * @since 3.0.0
   4755  *
   4756  * @param string $object_type Object type string.
   4757  * @param string $taxonomy    Single taxonomy name.
   4758  * @return bool True if object is associated with the taxonomy, otherwise false.
   4759  */
   4760 function is_object_in_taxonomy( $object_type, $taxonomy ) {
   4761 	$taxonomies = get_object_taxonomies( $object_type );
   4762 	if ( empty( $taxonomies ) ) {
   4763 		return false;
   4764 	}
   4765 	return in_array( $taxonomy, $taxonomies, true );
   4766 }
   4767 
   4768 /**
   4769  * Get an array of ancestor IDs for a given object.
   4770  *
   4771  * @since 3.1.0
   4772  * @since 4.1.0 Introduced the `$resource_type` argument.
   4773  *
   4774  * @param int    $object_id     Optional. The ID of the object. Default 0.
   4775  * @param string $object_type   Optional. The type of object for which we'll be retrieving
   4776  *                              ancestors. Accepts a post type or a taxonomy name. Default empty.
   4777  * @param string $resource_type Optional. Type of resource $object_type is. Accepts 'post_type'
   4778  *                              or 'taxonomy'. Default empty.
   4779  * @return int[] An array of IDs of ancestors from lowest to highest in the hierarchy.
   4780  */
   4781 function get_ancestors( $object_id = 0, $object_type = '', $resource_type = '' ) {
   4782 	$object_id = (int) $object_id;
   4783 
   4784 	$ancestors = array();
   4785 
   4786 	if ( empty( $object_id ) ) {
   4787 
   4788 		/** This filter is documented in wp-includes/taxonomy.php */
   4789 		return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
   4790 	}
   4791 
   4792 	if ( ! $resource_type ) {
   4793 		if ( is_taxonomy_hierarchical( $object_type ) ) {
   4794 			$resource_type = 'taxonomy';
   4795 		} elseif ( post_type_exists( $object_type ) ) {
   4796 			$resource_type = 'post_type';
   4797 		}
   4798 	}
   4799 
   4800 	if ( 'taxonomy' === $resource_type ) {
   4801 		$term = get_term( $object_id, $object_type );
   4802 		while ( ! is_wp_error( $term ) && ! empty( $term->parent ) && ! in_array( $term->parent, $ancestors, true ) ) {
   4803 			$ancestors[] = (int) $term->parent;
   4804 			$term        = get_term( $term->parent, $object_type );
   4805 		}
   4806 	} elseif ( 'post_type' === $resource_type ) {
   4807 		$ancestors = get_post_ancestors( $object_id );
   4808 	}
   4809 
   4810 	/**
   4811 	 * Filters a given object's ancestors.
   4812 	 *
   4813 	 * @since 3.1.0
   4814 	 * @since 4.1.1 Introduced the `$resource_type` parameter.
   4815 	 *
   4816 	 * @param int[]  $ancestors     An array of IDs of object ancestors.
   4817 	 * @param int    $object_id     Object ID.
   4818 	 * @param string $object_type   Type of object.
   4819 	 * @param string $resource_type Type of resource $object_type is.
   4820 	 */
   4821 	return apply_filters( 'get_ancestors', $ancestors, $object_id, $object_type, $resource_type );
   4822 }
   4823 
   4824 /**
   4825  * Returns the term's parent's term_ID.
   4826  *
   4827  * @since 3.1.0
   4828  *
   4829  * @param int    $term_id  Term ID.
   4830  * @param string $taxonomy Taxonomy name.
   4831  * @return int|false Parent term ID on success, false on failure.
   4832  */
   4833 function wp_get_term_taxonomy_parent_id( $term_id, $taxonomy ) {
   4834 	$term = get_term( $term_id, $taxonomy );
   4835 	if ( ! $term || is_wp_error( $term ) ) {
   4836 		return false;
   4837 	}
   4838 	return (int) $term->parent;
   4839 }
   4840 
   4841 /**
   4842  * Checks the given subset of the term hierarchy for hierarchy loops.
   4843  * Prevents loops from forming and breaks those that it finds.
   4844  *
   4845  * Attached to the {@see 'wp_update_term_parent'} filter.
   4846  *
   4847  * @since 3.1.0
   4848  *
   4849  * @param int    $parent   `term_id` of the parent for the term we're checking.
   4850  * @param int    $term_id  The term we're checking.
   4851  * @param string $taxonomy The taxonomy of the term we're checking.
   4852  * @return int The new parent for the term.
   4853  */
   4854 function wp_check_term_hierarchy_for_loops( $parent, $term_id, $taxonomy ) {
   4855 	// Nothing fancy here - bail.
   4856 	if ( ! $parent ) {
   4857 		return 0;
   4858 	}
   4859 
   4860 	// Can't be its own parent.
   4861 	if ( $parent === $term_id ) {
   4862 		return 0;
   4863 	}
   4864 
   4865 	// Now look for larger loops.
   4866 	$loop = wp_find_hierarchy_loop( 'wp_get_term_taxonomy_parent_id', $term_id, $parent, array( $taxonomy ) );
   4867 	if ( ! $loop ) {
   4868 		return $parent; // No loop.
   4869 	}
   4870 
   4871 	// Setting $parent to the given value causes a loop.
   4872 	if ( isset( $loop[ $term_id ] ) ) {
   4873 		return 0;
   4874 	}
   4875 
   4876 	// There's a loop, but it doesn't contain $term_id. Break the loop.
   4877 	foreach ( array_keys( $loop ) as $loop_member ) {
   4878 		wp_update_term( $loop_member, $taxonomy, array( 'parent' => 0 ) );
   4879 	}
   4880 
   4881 	return $parent;
   4882 }
   4883 
   4884 /**
   4885  * Determines whether a taxonomy is considered "viewable".
   4886  *
   4887  * @since 5.1.0
   4888  *
   4889  * @param string|WP_Taxonomy $taxonomy Taxonomy name or object.
   4890  * @return bool Whether the taxonomy should be considered viewable.
   4891  */
   4892 function is_taxonomy_viewable( $taxonomy ) {
   4893 	if ( is_scalar( $taxonomy ) ) {
   4894 		$taxonomy = get_taxonomy( $taxonomy );
   4895 		if ( ! $taxonomy ) {
   4896 			return false;
   4897 		}
   4898 	}
   4899 
   4900 	return $taxonomy->publicly_queryable;
   4901 }
   4902 
   4903 /**
   4904  * Sets the last changed time for the 'terms' cache group.
   4905  *
   4906  * @since 5.0.0
   4907  */
   4908 function wp_cache_set_terms_last_changed() {
   4909 	wp_cache_set( 'last_changed', microtime(), 'terms' );
   4910 }
   4911 
   4912 /**
   4913  * Aborts calls to term meta if it is not supported.
   4914  *
   4915  * @since 5.0.0
   4916  *
   4917  * @param mixed $check Skip-value for whether to proceed term meta function execution.
   4918  * @return mixed Original value of $check, or false if term meta is not supported.
   4919  */
   4920 function wp_check_term_meta_support_prefilter( $check ) {
   4921 	if ( get_option( 'db_version' ) < 34370 ) {
   4922 		return false;
   4923 	}
   4924 
   4925 	return $check;
   4926 }