[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/ -> taxonomy.php (source)

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