[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Thu May 9 08:20:02 2024 Cross-referenced by PHPXref