PageRenderTime 57ms CodeModel.GetById 15ms 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

Large files files are truncated, but you can click here to view the full 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. * @pa…

Large files files are truncated, but you can click here to view the full file