class-wp-taxonomy.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. <?php
  2. /**
  3. * Taxonomy API: WP_Taxonomy class
  4. *
  5. * @package WordPress
  6. * @subpackage Taxonomy
  7. * @since 4.7.0
  8. */
  9. /**
  10. * Core class used for interacting with taxonomies.
  11. *
  12. * @since 4.7.0
  13. */
  14. #[AllowDynamicProperties]
  15. final class WP_Taxonomy {
  16. /**
  17. * Taxonomy key.
  18. *
  19. * @since 4.7.0
  20. * @var string
  21. */
  22. public $name;
  23. /**
  24. * Name of the taxonomy shown in the menu. Usually plural.
  25. *
  26. * @since 4.7.0
  27. * @var string
  28. */
  29. public $label;
  30. /**
  31. * Labels object for this taxonomy.
  32. *
  33. * If not set, tag labels are inherited for non-hierarchical types
  34. * and category labels for hierarchical ones.
  35. *
  36. * @see get_taxonomy_labels()
  37. *
  38. * @since 4.7.0
  39. * @var stdClass
  40. */
  41. public $labels;
  42. /**
  43. * Default labels.
  44. *
  45. * @since 6.0.0
  46. * @var (string|null)[][] $default_labels
  47. */
  48. protected static $default_labels = array();
  49. /**
  50. * A short descriptive summary of what the taxonomy is for.
  51. *
  52. * @since 4.7.0
  53. * @var string
  54. */
  55. public $description = '';
  56. /**
  57. * Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.
  58. *
  59. * @since 4.7.0
  60. * @var bool
  61. */
  62. public $public = true;
  63. /**
  64. * Whether the taxonomy is publicly queryable.
  65. *
  66. * @since 4.7.0
  67. * @var bool
  68. */
  69. public $publicly_queryable = true;
  70. /**
  71. * Whether the taxonomy is hierarchical.
  72. *
  73. * @since 4.7.0
  74. * @var bool
  75. */
  76. public $hierarchical = false;
  77. /**
  78. * Whether to generate and allow a UI for managing terms in this taxonomy in the admin.
  79. *
  80. * @since 4.7.0
  81. * @var bool
  82. */
  83. public $show_ui = true;
  84. /**
  85. * Whether to show the taxonomy in the admin menu.
  86. *
  87. * If true, the taxonomy is shown as a submenu of the object type menu. If false, no menu is shown.
  88. *
  89. * @since 4.7.0
  90. * @var bool
  91. */
  92. public $show_in_menu = true;
  93. /**
  94. * Whether the taxonomy is available for selection in navigation menus.
  95. *
  96. * @since 4.7.0
  97. * @var bool
  98. */
  99. public $show_in_nav_menus = true;
  100. /**
  101. * Whether to list the taxonomy in the tag cloud widget controls.
  102. *
  103. * @since 4.7.0
  104. * @var bool
  105. */
  106. public $show_tagcloud = true;
  107. /**
  108. * Whether to show the taxonomy in the quick/bulk edit panel.
  109. *
  110. * @since 4.7.0
  111. * @var bool
  112. */
  113. public $show_in_quick_edit = true;
  114. /**
  115. * Whether to display a column for the taxonomy on its post type listing screens.
  116. *
  117. * @since 4.7.0
  118. * @var bool
  119. */
  120. public $show_admin_column = false;
  121. /**
  122. * The callback function for the meta box display.
  123. *
  124. * @since 4.7.0
  125. * @var bool|callable
  126. */
  127. public $meta_box_cb = null;
  128. /**
  129. * The callback function for sanitizing taxonomy data saved from a meta box.
  130. *
  131. * @since 5.1.0
  132. * @var callable
  133. */
  134. public $meta_box_sanitize_cb = null;
  135. /**
  136. * An array of object types this taxonomy is registered for.
  137. *
  138. * @since 4.7.0
  139. * @var string[]
  140. */
  141. public $object_type = null;
  142. /**
  143. * Capabilities for this taxonomy.
  144. *
  145. * @since 4.7.0
  146. * @var stdClass
  147. */
  148. public $cap;
  149. /**
  150. * Rewrites information for this taxonomy.
  151. *
  152. * @since 4.7.0
  153. * @var array|false
  154. */
  155. public $rewrite;
  156. /**
  157. * Query var string for this taxonomy.
  158. *
  159. * @since 4.7.0
  160. * @var string|false
  161. */
  162. public $query_var;
  163. /**
  164. * Function that will be called when the count is updated.
  165. *
  166. * @since 4.7.0
  167. * @var callable
  168. */
  169. public $update_count_callback;
  170. /**
  171. * Whether this taxonomy should appear in the REST API.
  172. *
  173. * Default false. If true, standard endpoints will be registered with
  174. * respect to $rest_base and $rest_controller_class.
  175. *
  176. * @since 4.7.4
  177. * @var bool $show_in_rest
  178. */
  179. public $show_in_rest;
  180. /**
  181. * The base path for this taxonomy's REST API endpoints.
  182. *
  183. * @since 4.7.4
  184. * @var string|bool $rest_base
  185. */
  186. public $rest_base;
  187. /**
  188. * The namespace for this taxonomy's REST API endpoints.
  189. *
  190. * @since 5.9.0
  191. * @var string|bool $rest_namespace
  192. */
  193. public $rest_namespace;
  194. /**
  195. * The controller for this taxonomy's REST API endpoints.
  196. *
  197. * Custom controllers must extend WP_REST_Controller.
  198. *
  199. * @since 4.7.4
  200. * @var string|bool $rest_controller_class
  201. */
  202. public $rest_controller_class;
  203. /**
  204. * The controller instance for this taxonomy's REST API endpoints.
  205. *
  206. * Lazily computed. Should be accessed using {@see WP_Taxonomy::get_rest_controller()}.
  207. *
  208. * @since 5.5.0
  209. * @var WP_REST_Controller $rest_controller
  210. */
  211. public $rest_controller;
  212. /**
  213. * The default term name for this taxonomy. If you pass an array you have
  214. * to set 'name' and optionally 'slug' and 'description'.
  215. *
  216. * @since 5.5.0
  217. * @var array|string
  218. */
  219. public $default_term;
  220. /**
  221. * Whether terms in this taxonomy should be sorted in the order they are provided to `wp_set_object_terms()`.
  222. *
  223. * Use this in combination with `'orderby' => 'term_order'` when fetching terms.
  224. *
  225. * @since 2.5.0
  226. * @var bool|null
  227. */
  228. public $sort = null;
  229. /**
  230. * Array of arguments to automatically use inside `wp_get_object_terms()` for this taxonomy.
  231. *
  232. * @since 2.6.0
  233. * @var array|null
  234. */
  235. public $args = null;
  236. /**
  237. * Whether it is a built-in taxonomy.
  238. *
  239. * @since 4.7.0
  240. * @var bool
  241. */
  242. public $_builtin;
  243. /**
  244. * Constructor.
  245. *
  246. * See the register_taxonomy() function for accepted arguments for `$args`.
  247. *
  248. * @since 4.7.0
  249. *
  250. * @global WP $wp Current WordPress environment instance.
  251. *
  252. * @param string $taxonomy Taxonomy key, must not exceed 32 characters.
  253. * @param array|string $object_type Name of the object type for the taxonomy object.
  254. * @param array|string $args Optional. Array or query string of arguments for registering a taxonomy.
  255. * Default empty array.
  256. */
  257. public function __construct( $taxonomy, $object_type, $args = array() ) {
  258. $this->name = $taxonomy;
  259. $this->set_props( $object_type, $args );
  260. }
  261. /**
  262. * Sets taxonomy properties.
  263. *
  264. * See the register_taxonomy() function for accepted arguments for `$args`.
  265. *
  266. * @since 4.7.0
  267. *
  268. * @param string|string[] $object_type Name or array of names of the object types for the taxonomy.
  269. * @param array|string $args Array or query string of arguments for registering a taxonomy.
  270. */
  271. public function set_props( $object_type, $args ) {
  272. $args = wp_parse_args( $args );
  273. /**
  274. * Filters the arguments for registering a taxonomy.
  275. *
  276. * @since 4.4.0
  277. *
  278. * @param array $args Array of arguments for registering a taxonomy.
  279. * See the register_taxonomy() function for accepted arguments.
  280. * @param string $taxonomy Taxonomy key.
  281. * @param string[] $object_type Array of names of object types for the taxonomy.
  282. */
  283. $args = apply_filters( 'register_taxonomy_args', $args, $this->name, (array) $object_type );
  284. $taxonomy = $this->name;
  285. /**
  286. * Filters the arguments for registering a specific taxonomy.
  287. *
  288. * The dynamic portion of the filter name, `$taxonomy`, refers to the taxonomy key.
  289. *
  290. * Possible hook names include:
  291. *
  292. * - `register_category_taxonomy_args`
  293. * - `register_post_tag_taxonomy_args`
  294. *
  295. * @since 6.0.0
  296. *
  297. * @param array $args Array of arguments for registering a taxonomy.
  298. * See the register_taxonomy() function for accepted arguments.
  299. * @param string $taxonomy Taxonomy key.
  300. * @param string[] $object_type Array of names of object types for the taxonomy.
  301. */
  302. $args = apply_filters( "register_{$taxonomy}_taxonomy_args", $args, $this->name, (array) $object_type );
  303. $defaults = array(
  304. 'labels' => array(),
  305. 'description' => '',
  306. 'public' => true,
  307. 'publicly_queryable' => null,
  308. 'hierarchical' => false,
  309. 'show_ui' => null,
  310. 'show_in_menu' => null,
  311. 'show_in_nav_menus' => null,
  312. 'show_tagcloud' => null,
  313. 'show_in_quick_edit' => null,
  314. 'show_admin_column' => false,
  315. 'meta_box_cb' => null,
  316. 'meta_box_sanitize_cb' => null,
  317. 'capabilities' => array(),
  318. 'rewrite' => true,
  319. 'query_var' => $this->name,
  320. 'update_count_callback' => '',
  321. 'show_in_rest' => false,
  322. 'rest_base' => false,
  323. 'rest_namespace' => false,
  324. 'rest_controller_class' => false,
  325. 'default_term' => null,
  326. 'sort' => null,
  327. 'args' => null,
  328. '_builtin' => false,
  329. );
  330. $args = array_merge( $defaults, $args );
  331. // If not set, default to the setting for 'public'.
  332. if ( null === $args['publicly_queryable'] ) {
  333. $args['publicly_queryable'] = $args['public'];
  334. }
  335. if ( false !== $args['query_var'] && ( is_admin() || false !== $args['publicly_queryable'] ) ) {
  336. if ( true === $args['query_var'] ) {
  337. $args['query_var'] = $this->name;
  338. } else {
  339. $args['query_var'] = sanitize_title_with_dashes( $args['query_var'] );
  340. }
  341. } else {
  342. // Force 'query_var' to false for non-public taxonomies.
  343. $args['query_var'] = false;
  344. }
  345. if ( false !== $args['rewrite'] && ( is_admin() || get_option( 'permalink_structure' ) ) ) {
  346. $args['rewrite'] = wp_parse_args(
  347. $args['rewrite'],
  348. array(
  349. 'with_front' => true,
  350. 'hierarchical' => false,
  351. 'ep_mask' => EP_NONE,
  352. )
  353. );
  354. if ( empty( $args['rewrite']['slug'] ) ) {
  355. $args['rewrite']['slug'] = sanitize_title_with_dashes( $this->name );
  356. }
  357. }
  358. // If not set, default to the setting for 'public'.
  359. if ( null === $args['show_ui'] ) {
  360. $args['show_ui'] = $args['public'];
  361. }
  362. // If not set, default to the setting for 'show_ui'.
  363. if ( null === $args['show_in_menu'] || ! $args['show_ui'] ) {
  364. $args['show_in_menu'] = $args['show_ui'];
  365. }
  366. // If not set, default to the setting for 'public'.
  367. if ( null === $args['show_in_nav_menus'] ) {
  368. $args['show_in_nav_menus'] = $args['public'];
  369. }
  370. // If not set, default to the setting for 'show_ui'.
  371. if ( null === $args['show_tagcloud'] ) {
  372. $args['show_tagcloud'] = $args['show_ui'];
  373. }
  374. // If not set, default to the setting for 'show_ui'.
  375. if ( null === $args['show_in_quick_edit'] ) {
  376. $args['show_in_quick_edit'] = $args['show_ui'];
  377. }
  378. // If not set, default rest_namespace to wp/v2 if show_in_rest is true.
  379. if ( false === $args['rest_namespace'] && ! empty( $args['show_in_rest'] ) ) {
  380. $args['rest_namespace'] = 'wp/v2';
  381. }
  382. $default_caps = array(
  383. 'manage_terms' => 'manage_categories',
  384. 'edit_terms' => 'manage_categories',
  385. 'delete_terms' => 'manage_categories',
  386. 'assign_terms' => 'edit_posts',
  387. );
  388. $args['cap'] = (object) array_merge( $default_caps, $args['capabilities'] );
  389. unset( $args['capabilities'] );
  390. $args['object_type'] = array_unique( (array) $object_type );
  391. // If not set, use the default meta box.
  392. if ( null === $args['meta_box_cb'] ) {
  393. if ( $args['hierarchical'] ) {
  394. $args['meta_box_cb'] = 'post_categories_meta_box';
  395. } else {
  396. $args['meta_box_cb'] = 'post_tags_meta_box';
  397. }
  398. }
  399. $args['name'] = $this->name;
  400. // Default meta box sanitization callback depends on the value of 'meta_box_cb'.
  401. if ( null === $args['meta_box_sanitize_cb'] ) {
  402. switch ( $args['meta_box_cb'] ) {
  403. case 'post_categories_meta_box':
  404. $args['meta_box_sanitize_cb'] = 'taxonomy_meta_box_sanitize_cb_checkboxes';
  405. break;
  406. case 'post_tags_meta_box':
  407. default:
  408. $args['meta_box_sanitize_cb'] = 'taxonomy_meta_box_sanitize_cb_input';
  409. break;
  410. }
  411. }
  412. // Default taxonomy term.
  413. if ( ! empty( $args['default_term'] ) ) {
  414. if ( ! is_array( $args['default_term'] ) ) {
  415. $args['default_term'] = array( 'name' => $args['default_term'] );
  416. }
  417. $args['default_term'] = wp_parse_args(
  418. $args['default_term'],
  419. array(
  420. 'name' => '',
  421. 'slug' => '',
  422. 'description' => '',
  423. )
  424. );
  425. }
  426. foreach ( $args as $property_name => $property_value ) {
  427. $this->$property_name = $property_value;
  428. }
  429. $this->labels = get_taxonomy_labels( $this );
  430. $this->label = $this->labels->name;
  431. }
  432. /**
  433. * Adds the necessary rewrite rules for the taxonomy.
  434. *
  435. * @since 4.7.0
  436. *
  437. * @global WP $wp Current WordPress environment instance.
  438. */
  439. public function add_rewrite_rules() {
  440. /* @var WP $wp */
  441. global $wp;
  442. // Non-publicly queryable taxonomies should not register query vars, except in the admin.
  443. if ( false !== $this->query_var && $wp ) {
  444. $wp->add_query_var( $this->query_var );
  445. }
  446. if ( false !== $this->rewrite && ( is_admin() || get_option( 'permalink_structure' ) ) ) {
  447. if ( $this->hierarchical && $this->rewrite['hierarchical'] ) {
  448. $tag = '(.+?)';
  449. } else {
  450. $tag = '([^/]+)';
  451. }
  452. add_rewrite_tag( "%$this->name%", $tag, $this->query_var ? "{$this->query_var}=" : "taxonomy=$this->name&term=" );
  453. add_permastruct( $this->name, "{$this->rewrite['slug']}/%$this->name%", $this->rewrite );
  454. }
  455. }
  456. /**
  457. * Removes any rewrite rules, permastructs, and rules for the taxonomy.
  458. *
  459. * @since 4.7.0
  460. *
  461. * @global WP $wp Current WordPress environment instance.
  462. */
  463. public function remove_rewrite_rules() {
  464. /* @var WP $wp */
  465. global $wp;
  466. // Remove query var.
  467. if ( false !== $this->query_var ) {
  468. $wp->remove_query_var( $this->query_var );
  469. }
  470. // Remove rewrite tags and permastructs.
  471. if ( false !== $this->rewrite ) {
  472. remove_rewrite_tag( "%$this->name%" );
  473. remove_permastruct( $this->name );
  474. }
  475. }
  476. /**
  477. * Registers the ajax callback for the meta box.
  478. *
  479. * @since 4.7.0
  480. */
  481. public function add_hooks() {
  482. add_filter( 'wp_ajax_add-' . $this->name, '_wp_ajax_add_hierarchical_term' );
  483. }
  484. /**
  485. * Removes the ajax callback for the meta box.
  486. *
  487. * @since 4.7.0
  488. */
  489. public function remove_hooks() {
  490. remove_filter( 'wp_ajax_add-' . $this->name, '_wp_ajax_add_hierarchical_term' );
  491. }
  492. /**
  493. * Gets the REST API controller for this taxonomy.
  494. *
  495. * Will only instantiate the controller class once per request.
  496. *
  497. * @since 5.5.0
  498. *
  499. * @return WP_REST_Controller|null The controller instance, or null if the taxonomy
  500. * is set not to show in rest.
  501. */
  502. public function get_rest_controller() {
  503. if ( ! $this->show_in_rest ) {
  504. return null;
  505. }
  506. $class = $this->rest_controller_class ? $this->rest_controller_class : WP_REST_Terms_Controller::class;
  507. if ( ! class_exists( $class ) ) {
  508. return null;
  509. }
  510. if ( ! is_subclass_of( $class, WP_REST_Controller::class ) ) {
  511. return null;
  512. }
  513. if ( ! $this->rest_controller ) {
  514. $this->rest_controller = new $class( $this->name );
  515. }
  516. if ( ! ( $this->rest_controller instanceof $class ) ) {
  517. return null;
  518. }
  519. return $this->rest_controller;
  520. }
  521. /**
  522. * Returns the default labels for taxonomies.
  523. *
  524. * @since 6.0.0
  525. *
  526. * @return (string|null)[][] The default labels for taxonomies.
  527. */
  528. public static function get_default_labels() {
  529. if ( ! empty( self::$default_labels ) ) {
  530. return self::$default_labels;
  531. }
  532. $name_field_description = __( 'The name is how it appears on your site.' );
  533. $slug_field_description = __( 'The &#8220;slug&#8221; is the URL-friendly version of the name. It is usually all lowercase and contains only letters, numbers, and hyphens.' );
  534. $parent_field_description = __( 'Assign a parent term to create a hierarchy. The term Jazz, for example, would be the parent of Bebop and Big Band.' );
  535. $desc_field_description = __( 'The description is not prominent by default; however, some themes may show it.' );
  536. self::$default_labels = array(
  537. 'name' => array( _x( 'Tags', 'taxonomy general name' ), _x( 'Categories', 'taxonomy general name' ) ),
  538. 'singular_name' => array( _x( 'Tag', 'taxonomy singular name' ), _x( 'Category', 'taxonomy singular name' ) ),
  539. 'search_items' => array( __( 'Search Tags' ), __( 'Search Categories' ) ),
  540. 'popular_items' => array( __( 'Popular Tags' ), null ),
  541. 'all_items' => array( __( 'All Tags' ), __( 'All Categories' ) ),
  542. 'parent_item' => array( null, __( 'Parent Category' ) ),
  543. 'parent_item_colon' => array( null, __( 'Parent Category:' ) ),
  544. 'name_field_description' => array( $name_field_description, $name_field_description ),
  545. 'slug_field_description' => array( $slug_field_description, $slug_field_description ),
  546. 'parent_field_description' => array( null, $parent_field_description ),
  547. 'desc_field_description' => array( $desc_field_description, $desc_field_description ),
  548. 'edit_item' => array( __( 'Edit Tag' ), __( 'Edit Category' ) ),
  549. 'view_item' => array( __( 'View Tag' ), __( 'View Category' ) ),
  550. 'update_item' => array( __( 'Update Tag' ), __( 'Update Category' ) ),
  551. 'add_new_item' => array( __( 'Add New Tag' ), __( 'Add New Category' ) ),
  552. 'new_item_name' => array( __( 'New Tag Name' ), __( 'New Category Name' ) ),
  553. 'separate_items_with_commas' => array( __( 'Separate tags with commas' ), null ),
  554. 'add_or_remove_items' => array( __( 'Add or remove tags' ), null ),
  555. 'choose_from_most_used' => array( __( 'Choose from the most used tags' ), null ),
  556. 'not_found' => array( __( 'No tags found.' ), __( 'No categories found.' ) ),
  557. 'no_terms' => array( __( 'No tags' ), __( 'No categories' ) ),
  558. 'filter_by_item' => array( null, __( 'Filter by category' ) ),
  559. 'items_list_navigation' => array( __( 'Tags list navigation' ), __( 'Categories list navigation' ) ),
  560. 'items_list' => array( __( 'Tags list' ), __( 'Categories list' ) ),
  561. /* translators: Tab heading when selecting from the most used terms. */
  562. 'most_used' => array( _x( 'Most Used', 'tags' ), _x( 'Most Used', 'categories' ) ),
  563. 'back_to_items' => array( __( '&larr; Go to Tags' ), __( '&larr; Go to Categories' ) ),
  564. 'item_link' => array(
  565. _x( 'Tag Link', 'navigation link block title' ),
  566. _x( 'Category Link', 'navigation link block title' ),
  567. ),
  568. 'item_link_description' => array(
  569. _x( 'A link to a tag.', 'navigation link block description' ),
  570. _x( 'A link to a category.', 'navigation link block description' ),
  571. ),
  572. );
  573. return self::$default_labels;
  574. }
  575. /**
  576. * Resets the cache for the default labels.
  577. *
  578. * @since 6.0.0
  579. */
  580. public static function reset_default_labels() {
  581. self::$default_labels = array();
  582. }
  583. }