PageRenderTime 77ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-includes/taxonomy.php

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