PageRenderTime 51ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/sandbox/wp-includes/taxonomy.php

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