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