[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Terms_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 4.7.0 8 */ 9 10 /** 11 * Core class used to managed terms associated with a taxonomy via the REST API. 12 * 13 * @since 4.7.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Terms_Controller extends WP_REST_Controller { 18 19 /** 20 * Taxonomy key. 21 * 22 * @since 4.7.0 23 * @var string 24 */ 25 protected $taxonomy; 26 27 /** 28 * Instance of a term meta fields object. 29 * 30 * @since 4.7.0 31 * @var WP_REST_Term_Meta_Fields 32 */ 33 protected $meta; 34 35 /** 36 * Column to have the terms be sorted by. 37 * 38 * @since 4.7.0 39 * @var string 40 */ 41 protected $sort_column; 42 43 /** 44 * Number of terms that were found. 45 * 46 * @since 4.7.0 47 * @var int 48 */ 49 protected $total_terms; 50 51 /** 52 * Whether the controller supports batching. 53 * 54 * @since 5.9.0 55 * @var array 56 */ 57 protected $allow_batch = array( 'v1' => true ); 58 59 /** 60 * Constructor. 61 * 62 * @since 4.7.0 63 * 64 * @param string $taxonomy Taxonomy key. 65 */ 66 public function __construct( $taxonomy ) { 67 $this->taxonomy = $taxonomy; 68 $tax_obj = get_taxonomy( $taxonomy ); 69 $this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name; 70 $this->namespace = ! empty( $tax_obj->rest_namespace ) ? $tax_obj->rest_namespace : 'wp/v2'; 71 72 $this->meta = new WP_REST_Term_Meta_Fields( $taxonomy ); 73 } 74 75 /** 76 * Registers the routes for terms. 77 * 78 * @since 4.7.0 79 * 80 * @see register_rest_route() 81 */ 82 public function register_routes() { 83 84 register_rest_route( 85 $this->namespace, 86 '/' . $this->rest_base, 87 array( 88 array( 89 'methods' => WP_REST_Server::READABLE, 90 'callback' => array( $this, 'get_items' ), 91 'permission_callback' => array( $this, 'get_items_permissions_check' ), 92 'args' => $this->get_collection_params(), 93 ), 94 array( 95 'methods' => WP_REST_Server::CREATABLE, 96 'callback' => array( $this, 'create_item' ), 97 'permission_callback' => array( $this, 'create_item_permissions_check' ), 98 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 99 ), 100 'allow_batch' => $this->allow_batch, 101 'schema' => array( $this, 'get_public_item_schema' ), 102 ) 103 ); 104 105 register_rest_route( 106 $this->namespace, 107 '/' . $this->rest_base . '/(?P<id>[\d]+)', 108 array( 109 'args' => array( 110 'id' => array( 111 'description' => __( 'Unique identifier for the term.' ), 112 'type' => 'integer', 113 ), 114 ), 115 array( 116 'methods' => WP_REST_Server::READABLE, 117 'callback' => array( $this, 'get_item' ), 118 'permission_callback' => array( $this, 'get_item_permissions_check' ), 119 'args' => array( 120 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 121 ), 122 ), 123 array( 124 'methods' => WP_REST_Server::EDITABLE, 125 'callback' => array( $this, 'update_item' ), 126 'permission_callback' => array( $this, 'update_item_permissions_check' ), 127 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 128 ), 129 array( 130 'methods' => WP_REST_Server::DELETABLE, 131 'callback' => array( $this, 'delete_item' ), 132 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 133 'args' => array( 134 'force' => array( 135 'type' => 'boolean', 136 'default' => false, 137 'description' => __( 'Required to be true, as terms do not support trashing.' ), 138 ), 139 ), 140 ), 141 'allow_batch' => $this->allow_batch, 142 'schema' => array( $this, 'get_public_item_schema' ), 143 ) 144 ); 145 } 146 147 /** 148 * Checks if the terms for a post can be read. 149 * 150 * @since 6.0.3 151 * 152 * @param WP_Post $post Post object. 153 * @param WP_REST_Request $request Full details about the request. 154 * @return bool Whether the terms for the post can be read. 155 */ 156 public function check_read_terms_permission_for_post( $post, $request ) { 157 // If the requested post isn't associated with this taxonomy, deny access. 158 if ( ! is_object_in_taxonomy( $post->post_type, $this->taxonomy ) ) { 159 return false; 160 } 161 162 // Grant access if the post is publicly viewable. 163 if ( is_post_publicly_viewable( $post ) ) { 164 return true; 165 } 166 167 // Otherwise grant access if the post is readable by the logged-in user. 168 if ( current_user_can( 'read_post', $post->ID ) ) { 169 return true; 170 } 171 172 // Otherwise, deny access. 173 return false; 174 } 175 176 /** 177 * Checks if a request has access to read terms in the specified taxonomy. 178 * 179 * @since 4.7.0 180 * 181 * @param WP_REST_Request $request Full details about the request. 182 * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object. 183 */ 184 public function get_items_permissions_check( $request ) { 185 $tax_obj = get_taxonomy( $this->taxonomy ); 186 187 if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 188 return false; 189 } 190 191 if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) { 192 return new WP_Error( 193 'rest_forbidden_context', 194 __( 'Sorry, you are not allowed to edit terms in this taxonomy.' ), 195 array( 'status' => rest_authorization_required_code() ) 196 ); 197 } 198 199 if ( ! empty( $request['post'] ) ) { 200 $post = get_post( $request['post'] ); 201 202 if ( ! $post ) { 203 return new WP_Error( 204 'rest_post_invalid_id', 205 __( 'Invalid post ID.' ), 206 array( 207 'status' => 400, 208 ) 209 ); 210 } 211 212 if ( ! $this->check_read_terms_permission_for_post( $post, $request ) ) { 213 return new WP_Error( 214 'rest_forbidden_context', 215 __( 'Sorry, you are not allowed to view terms for this post.' ), 216 array( 217 'status' => rest_authorization_required_code(), 218 ) 219 ); 220 } 221 } 222 223 return true; 224 } 225 226 /** 227 * Retrieves terms associated with a taxonomy. 228 * 229 * @since 4.7.0 230 * @since 6.8.0 Respect default query arguments set for the taxonomy upon registration. 231 * 232 * @param WP_REST_Request $request Full details about the request. 233 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 234 */ 235 public function get_items( $request ) { 236 237 // Retrieve the list of registered collection query parameters. 238 $registered = $this->get_collection_params(); 239 240 /* 241 * This array defines mappings between public API query parameters whose 242 * values are accepted as-passed, and their internal WP_Query parameter 243 * name equivalents (some are the same). Only values which are also 244 * present in $registered will be set. 245 */ 246 $parameter_mappings = array( 247 'exclude' => 'exclude', 248 'include' => 'include', 249 'order' => 'order', 250 'orderby' => 'orderby', 251 'post' => 'post', 252 'hide_empty' => 'hide_empty', 253 'per_page' => 'number', 254 'search' => 'search', 255 'slug' => 'slug', 256 ); 257 258 $prepared_args = array( 'taxonomy' => $this->taxonomy ); 259 260 /* 261 * For each known parameter which is both registered and present in the request, 262 * set the parameter's value on the query $prepared_args. 263 */ 264 foreach ( $parameter_mappings as $api_param => $wp_param ) { 265 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 266 $prepared_args[ $wp_param ] = $request[ $api_param ]; 267 } 268 } 269 270 if ( isset( $prepared_args['orderby'] ) && isset( $request['orderby'] ) ) { 271 $orderby_mappings = array( 272 'include_slugs' => 'slug__in', 273 ); 274 275 if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { 276 $prepared_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; 277 } 278 } 279 280 if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) { 281 $prepared_args['offset'] = $request['offset']; 282 } else { 283 $prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number']; 284 } 285 286 $taxonomy_obj = get_taxonomy( $this->taxonomy ); 287 288 if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) { 289 if ( 0 === $request['parent'] ) { 290 // Only query top-level terms. 291 $prepared_args['parent'] = 0; 292 } else { 293 if ( $request['parent'] ) { 294 $prepared_args['parent'] = $request['parent']; 295 } 296 } 297 } 298 299 /* 300 * When a taxonomy is registered with an 'args' array, 301 * those params override the `$args` passed to this function. 302 * 303 * We only need to do this if no `post` argument is provided. 304 * Otherwise, terms will be fetched using `wp_get_object_terms()`, 305 * which respects the default query arguments set for the taxonomy. 306 */ 307 if ( 308 empty( $prepared_args['post'] ) && 309 isset( $taxonomy_obj->args ) && 310 is_array( $taxonomy_obj->args ) 311 ) { 312 $prepared_args = array_merge( $prepared_args, $taxonomy_obj->args ); 313 } 314 315 $is_head_request = $request->is_method( 'HEAD' ); 316 if ( $is_head_request ) { 317 // Force the 'fields' argument. For HEAD requests, only term IDs are required. 318 $prepared_args['fields'] = 'ids'; 319 // Disable priming term meta for HEAD requests to improve performance. 320 $prepared_args['update_term_meta_cache'] = false; 321 } 322 323 /** 324 * Filters get_terms() arguments when querying terms via the REST API. 325 * 326 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. 327 * 328 * Possible hook names include: 329 * 330 * - `rest_category_query` 331 * - `rest_post_tag_query` 332 * 333 * Enables adding extra arguments or setting defaults for a terms 334 * collection request. 335 * 336 * @since 4.7.0 337 * 338 * @link https://developer.wordpress.org/reference/functions/get_terms/ 339 * 340 * @param array $prepared_args Array of arguments for get_terms(). 341 * @param WP_REST_Request $request The REST API request. 342 */ 343 $prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request ); 344 345 if ( ! empty( $prepared_args['post'] ) ) { 346 $query_result = wp_get_object_terms( $prepared_args['post'], $this->taxonomy, $prepared_args ); 347 348 // Used when calling wp_count_terms() below. 349 $prepared_args['object_ids'] = $prepared_args['post']; 350 } else { 351 $query_result = get_terms( $prepared_args ); 352 } 353 354 $count_args = $prepared_args; 355 356 unset( $count_args['number'], $count_args['offset'] ); 357 358 $total_terms = wp_count_terms( $count_args ); 359 360 // wp_count_terms() can return a falsey value when the term has no children. 361 if ( ! $total_terms ) { 362 $total_terms = 0; 363 } 364 365 if ( ! $is_head_request ) { 366 $response = array(); 367 foreach ( $query_result as $term ) { 368 if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) { 369 continue; 370 } 371 372 $data = $this->prepare_item_for_response( $term, $request ); 373 $response[] = $this->prepare_response_for_collection( $data ); 374 } 375 } 376 377 $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $response ); 378 379 // Store pagination values for headers. 380 $per_page = (int) $prepared_args['number']; 381 $page = (int) ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 ); 382 383 $response->header( 'X-WP-Total', (int) $total_terms ); 384 385 $max_pages = (int) ceil( $total_terms / $per_page ); 386 387 $response->header( 'X-WP-TotalPages', $max_pages ); 388 389 $request_params = $request->get_query_params(); 390 $collection_url = rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) ); 391 $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); 392 393 if ( $page > 1 ) { 394 $prev_page = $page - 1; 395 396 if ( $prev_page > $max_pages ) { 397 $prev_page = $max_pages; 398 } 399 400 $prev_link = add_query_arg( 'page', $prev_page, $base ); 401 $response->link_header( 'prev', $prev_link ); 402 } 403 if ( $max_pages > $page ) { 404 $next_page = $page + 1; 405 $next_link = add_query_arg( 'page', $next_page, $base ); 406 407 $response->link_header( 'next', $next_link ); 408 } 409 410 return $response; 411 } 412 413 /** 414 * Get the term, if the ID is valid. 415 * 416 * @since 4.7.2 417 * 418 * @param int $id Supplied ID. 419 * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise. 420 */ 421 protected function get_term( $id ) { 422 $error = new WP_Error( 423 'rest_term_invalid', 424 __( 'Term does not exist.' ), 425 array( 'status' => 404 ) 426 ); 427 428 if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 429 return $error; 430 } 431 432 if ( (int) $id <= 0 ) { 433 return $error; 434 } 435 436 $term = get_term( (int) $id, $this->taxonomy ); 437 if ( empty( $term ) || $term->taxonomy !== $this->taxonomy ) { 438 return $error; 439 } 440 441 return $term; 442 } 443 444 /** 445 * Checks if a request has access to read or edit the specified term. 446 * 447 * @since 4.7.0 448 * 449 * @param WP_REST_Request $request Full details about the request. 450 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. 451 */ 452 public function get_item_permissions_check( $request ) { 453 $term = $this->get_term( $request['id'] ); 454 455 if ( is_wp_error( $term ) ) { 456 return $term; 457 } 458 459 if ( 'edit' === $request['context'] && ! current_user_can( 'edit_term', $term->term_id ) ) { 460 return new WP_Error( 461 'rest_forbidden_context', 462 __( 'Sorry, you are not allowed to edit this term.' ), 463 array( 'status' => rest_authorization_required_code() ) 464 ); 465 } 466 467 return true; 468 } 469 470 /** 471 * Gets a single term from a taxonomy. 472 * 473 * @since 4.7.0 474 * 475 * @param WP_REST_Request $request Full details about the request. 476 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 477 */ 478 public function get_item( $request ) { 479 $term = $this->get_term( $request['id'] ); 480 if ( is_wp_error( $term ) ) { 481 return $term; 482 } 483 484 $response = $this->prepare_item_for_response( $term, $request ); 485 486 return rest_ensure_response( $response ); 487 } 488 489 /** 490 * Checks if a request has access to create a term. 491 * 492 * @since 4.7.0 493 * 494 * @param WP_REST_Request $request Full details about the request. 495 * @return bool|WP_Error True if the request has access to create items, otherwise false or WP_Error object. 496 */ 497 public function create_item_permissions_check( $request ) { 498 499 if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) { 500 return false; 501 } 502 503 $taxonomy_obj = get_taxonomy( $this->taxonomy ); 504 505 if ( ( is_taxonomy_hierarchical( $this->taxonomy ) 506 && ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) 507 || ( ! is_taxonomy_hierarchical( $this->taxonomy ) 508 && ! current_user_can( $taxonomy_obj->cap->assign_terms ) ) ) { 509 return new WP_Error( 510 'rest_cannot_create', 511 __( 'Sorry, you are not allowed to create terms in this taxonomy.' ), 512 array( 'status' => rest_authorization_required_code() ) 513 ); 514 } 515 516 return true; 517 } 518 519 /** 520 * Creates a single term in a taxonomy. 521 * 522 * @since 4.7.0 523 * 524 * @param WP_REST_Request $request Full details about the request. 525 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 526 */ 527 public function create_item( $request ) { 528 if ( isset( $request['parent'] ) ) { 529 if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { 530 return new WP_Error( 531 'rest_taxonomy_not_hierarchical', 532 __( 'Cannot set parent term, taxonomy is not hierarchical.' ), 533 array( 'status' => 400 ) 534 ); 535 } 536 537 $parent = get_term( (int) $request['parent'], $this->taxonomy ); 538 539 if ( ! $parent ) { 540 return new WP_Error( 541 'rest_term_invalid', 542 __( 'Parent term does not exist.' ), 543 array( 'status' => 400 ) 544 ); 545 } 546 } 547 548 $prepared_term = $this->prepare_item_for_database( $request ); 549 550 $term = wp_insert_term( wp_slash( $prepared_term->name ), $this->taxonomy, wp_slash( (array) $prepared_term ) ); 551 if ( is_wp_error( $term ) ) { 552 /* 553 * If we're going to inform the client that the term already exists, 554 * give them the identifier for future use. 555 */ 556 $term_id = $term->get_error_data( 'term_exists' ); 557 if ( $term_id ) { 558 $existing_term = get_term( $term_id, $this->taxonomy ); 559 $term->add_data( $existing_term->term_id, 'term_exists' ); 560 $term->add_data( 561 array( 562 'status' => 400, 563 'term_id' => $term_id, 564 ) 565 ); 566 } 567 568 return $term; 569 } 570 571 $term = get_term( $term['term_id'], $this->taxonomy ); 572 573 /** 574 * Fires after a single term is created or updated via the REST API. 575 * 576 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. 577 * 578 * Possible hook names include: 579 * 580 * - `rest_insert_category` 581 * - `rest_insert_post_tag` 582 * 583 * @since 4.7.0 584 * 585 * @param WP_Term $term Inserted or updated term object. 586 * @param WP_REST_Request $request Request object. 587 * @param bool $creating True when creating a term, false when updating. 588 */ 589 do_action( "rest_insert_{$this->taxonomy}", $term, $request, true ); 590 591 $schema = $this->get_item_schema(); 592 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 593 $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); 594 595 if ( is_wp_error( $meta_update ) ) { 596 return $meta_update; 597 } 598 } 599 600 $fields_update = $this->update_additional_fields_for_object( $term, $request ); 601 602 if ( is_wp_error( $fields_update ) ) { 603 return $fields_update; 604 } 605 606 $request->set_param( 'context', 'edit' ); 607 608 /** 609 * Fires after a single term is completely created or updated via the REST API. 610 * 611 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. 612 * 613 * Possible hook names include: 614 * 615 * - `rest_after_insert_category` 616 * - `rest_after_insert_post_tag` 617 * 618 * @since 5.0.0 619 * 620 * @param WP_Term $term Inserted or updated term object. 621 * @param WP_REST_Request $request Request object. 622 * @param bool $creating True when creating a term, false when updating. 623 */ 624 do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true ); 625 626 $response = $this->prepare_item_for_response( $term, $request ); 627 $response = rest_ensure_response( $response ); 628 629 $response->set_status( 201 ); 630 $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) ); 631 632 return $response; 633 } 634 635 /** 636 * Checks if a request has access to update the specified term. 637 * 638 * @since 4.7.0 639 * 640 * @param WP_REST_Request $request Full details about the request. 641 * @return true|WP_Error True if the request has access to update the item, false or WP_Error object otherwise. 642 */ 643 public function update_item_permissions_check( $request ) { 644 $term = $this->get_term( $request['id'] ); 645 646 if ( is_wp_error( $term ) ) { 647 return $term; 648 } 649 650 if ( ! current_user_can( 'edit_term', $term->term_id ) ) { 651 return new WP_Error( 652 'rest_cannot_update', 653 __( 'Sorry, you are not allowed to edit this term.' ), 654 array( 'status' => rest_authorization_required_code() ) 655 ); 656 } 657 658 return true; 659 } 660 661 /** 662 * Updates a single term from a taxonomy. 663 * 664 * @since 4.7.0 665 * 666 * @param WP_REST_Request $request Full details about the request. 667 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 668 */ 669 public function update_item( $request ) { 670 $term = $this->get_term( $request['id'] ); 671 if ( is_wp_error( $term ) ) { 672 return $term; 673 } 674 675 if ( isset( $request['parent'] ) ) { 676 if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { 677 return new WP_Error( 678 'rest_taxonomy_not_hierarchical', 679 __( 'Cannot set parent term, taxonomy is not hierarchical.' ), 680 array( 'status' => 400 ) 681 ); 682 } 683 684 $parent = get_term( (int) $request['parent'], $this->taxonomy ); 685 686 if ( ! $parent ) { 687 return new WP_Error( 688 'rest_term_invalid', 689 __( 'Parent term does not exist.' ), 690 array( 'status' => 400 ) 691 ); 692 } 693 } 694 695 $prepared_term = $this->prepare_item_for_database( $request ); 696 697 // Only update the term if we have something to update. 698 if ( ! empty( $prepared_term ) ) { 699 $update = wp_update_term( $term->term_id, $term->taxonomy, wp_slash( (array) $prepared_term ) ); 700 701 if ( is_wp_error( $update ) ) { 702 return $update; 703 } 704 } 705 706 $term = get_term( $term->term_id, $this->taxonomy ); 707 708 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 709 do_action( "rest_insert_{$this->taxonomy}", $term, $request, false ); 710 711 $schema = $this->get_item_schema(); 712 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 713 $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); 714 715 if ( is_wp_error( $meta_update ) ) { 716 return $meta_update; 717 } 718 } 719 720 $fields_update = $this->update_additional_fields_for_object( $term, $request ); 721 722 if ( is_wp_error( $fields_update ) ) { 723 return $fields_update; 724 } 725 726 $request->set_param( 'context', 'edit' ); 727 728 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 729 do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false ); 730 731 $response = $this->prepare_item_for_response( $term, $request ); 732 733 return rest_ensure_response( $response ); 734 } 735 736 /** 737 * Checks if a request has access to delete the specified term. 738 * 739 * @since 4.7.0 740 * 741 * @param WP_REST_Request $request Full details about the request. 742 * @return true|WP_Error True if the request has access to delete the item, otherwise false or WP_Error object. 743 */ 744 public function delete_item_permissions_check( $request ) { 745 $term = $this->get_term( $request['id'] ); 746 747 if ( is_wp_error( $term ) ) { 748 return $term; 749 } 750 751 if ( ! current_user_can( 'delete_term', $term->term_id ) ) { 752 return new WP_Error( 753 'rest_cannot_delete', 754 __( 'Sorry, you are not allowed to delete this term.' ), 755 array( 'status' => rest_authorization_required_code() ) 756 ); 757 } 758 759 return true; 760 } 761 762 /** 763 * Deletes a single term from a taxonomy. 764 * 765 * @since 4.7.0 766 * 767 * @param WP_REST_Request $request Full details about the request. 768 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 769 */ 770 public function delete_item( $request ) { 771 $term = $this->get_term( $request['id'] ); 772 if ( is_wp_error( $term ) ) { 773 return $term; 774 } 775 776 $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 777 778 // We don't support trashing for terms. 779 if ( ! $force ) { 780 return new WP_Error( 781 'rest_trash_not_supported', 782 /* translators: %s: force=true */ 783 sprintf( __( "Terms do not support trashing. Set '%s' to delete." ), 'force=true' ), 784 array( 'status' => 501 ) 785 ); 786 } 787 788 $request->set_param( 'context', 'view' ); 789 790 $previous = $this->prepare_item_for_response( $term, $request ); 791 792 $retval = wp_delete_term( $term->term_id, $term->taxonomy ); 793 794 if ( ! $retval ) { 795 return new WP_Error( 796 'rest_cannot_delete', 797 __( 'The term cannot be deleted.' ), 798 array( 'status' => 500 ) 799 ); 800 } 801 802 $response = new WP_REST_Response(); 803 $response->set_data( 804 array( 805 'deleted' => true, 806 'previous' => $previous->get_data(), 807 ) 808 ); 809 810 /** 811 * Fires after a single term is deleted via the REST API. 812 * 813 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. 814 * 815 * Possible hook names include: 816 * 817 * - `rest_delete_category` 818 * - `rest_delete_post_tag` 819 * 820 * @since 4.7.0 821 * 822 * @param WP_Term $term The deleted term. 823 * @param WP_REST_Response $response The response data. 824 * @param WP_REST_Request $request The request sent to the API. 825 */ 826 do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request ); 827 828 return $response; 829 } 830 831 /** 832 * Prepares a single term for create or update. 833 * 834 * @since 4.7.0 835 * 836 * @param WP_REST_Request $request Request object. 837 * @return object Term object. 838 */ 839 public function prepare_item_for_database( $request ) { 840 $prepared_term = new stdClass(); 841 842 $schema = $this->get_item_schema(); 843 if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { 844 $prepared_term->name = $request['name']; 845 } 846 847 if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) { 848 $prepared_term->slug = $request['slug']; 849 } 850 851 if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) { 852 $prepared_term->taxonomy = $request['taxonomy']; 853 } 854 855 if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) { 856 $prepared_term->description = $request['description']; 857 } 858 859 if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) { 860 $parent_term_id = 0; 861 $requested_parent = (int) $request['parent']; 862 863 if ( $requested_parent ) { 864 $parent_term = get_term( $requested_parent, $this->taxonomy ); 865 866 if ( $parent_term instanceof WP_Term ) { 867 $parent_term_id = $parent_term->term_id; 868 } 869 } 870 871 $prepared_term->parent = $parent_term_id; 872 } 873 874 /** 875 * Filters term data before inserting term via the REST API. 876 * 877 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. 878 * 879 * Possible hook names include: 880 * 881 * - `rest_pre_insert_category` 882 * - `rest_pre_insert_post_tag` 883 * 884 * @since 4.7.0 885 * 886 * @param object $prepared_term Term object. 887 * @param WP_REST_Request $request Request object. 888 */ 889 return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request ); 890 } 891 892 /** 893 * Prepares a single term output for response. 894 * 895 * @since 4.7.0 896 * 897 * @param WP_Term $item Term object. 898 * @param WP_REST_Request $request Request object. 899 * @return WP_REST_Response Response object. 900 */ 901 public function prepare_item_for_response( $item, $request ) { 902 903 // Don't prepare the response body for HEAD requests. 904 if ( $request->is_method( 'HEAD' ) ) { 905 /** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 906 return apply_filters( "rest_prepare_{$this->taxonomy}", new WP_REST_Response( array() ), $item, $request ); 907 } 908 909 $fields = $this->get_fields_for_response( $request ); 910 $data = array(); 911 912 if ( in_array( 'id', $fields, true ) ) { 913 $data['id'] = (int) $item->term_id; 914 } 915 916 if ( in_array( 'count', $fields, true ) ) { 917 $data['count'] = (int) $item->count; 918 } 919 920 if ( in_array( 'description', $fields, true ) ) { 921 $data['description'] = $item->description; 922 } 923 924 if ( in_array( 'link', $fields, true ) ) { 925 $data['link'] = get_term_link( $item ); 926 } 927 928 if ( in_array( 'name', $fields, true ) ) { 929 $data['name'] = $item->name; 930 } 931 932 if ( in_array( 'slug', $fields, true ) ) { 933 $data['slug'] = $item->slug; 934 } 935 936 if ( in_array( 'taxonomy', $fields, true ) ) { 937 $data['taxonomy'] = $item->taxonomy; 938 } 939 940 if ( in_array( 'parent', $fields, true ) ) { 941 $data['parent'] = (int) $item->parent; 942 } 943 944 if ( in_array( 'meta', $fields, true ) ) { 945 $data['meta'] = $this->meta->get_value( $item->term_id, $request ); 946 } 947 948 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 949 $data = $this->add_additional_fields_to_object( $data, $request ); 950 $data = $this->filter_response_by_context( $data, $context ); 951 952 $response = rest_ensure_response( $data ); 953 954 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 955 $response->add_links( $this->prepare_links( $item ) ); 956 } 957 958 /** 959 * Filters the term data for a REST API response. 960 * 961 * The dynamic portion of the hook name, `$this->taxonomy`, refers to the taxonomy slug. 962 * 963 * Possible hook names include: 964 * 965 * - `rest_prepare_category` 966 * - `rest_prepare_post_tag` 967 * 968 * Allows modification of the term data right before it is returned. 969 * 970 * @since 4.7.0 971 * 972 * @param WP_REST_Response $response The response object. 973 * @param WP_Term $item The original term object. 974 * @param WP_REST_Request $request Request used to generate the response. 975 */ 976 return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request ); 977 } 978 979 /** 980 * Prepares links for the request. 981 * 982 * @since 4.7.0 983 * 984 * @param WP_Term $term Term object. 985 * @return array Links for the given term. 986 */ 987 protected function prepare_links( $term ) { 988 $links = array( 989 'self' => array( 990 'href' => rest_url( rest_get_route_for_term( $term ) ), 991 ), 992 'collection' => array( 993 'href' => rest_url( rest_get_route_for_taxonomy_items( $this->taxonomy ) ), 994 ), 995 'about' => array( 996 'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ), 997 ), 998 ); 999 1000 if ( $term->parent ) { 1001 $parent_term = get_term( (int) $term->parent, $term->taxonomy ); 1002 1003 if ( $parent_term ) { 1004 $links['up'] = array( 1005 'href' => rest_url( rest_get_route_for_term( $parent_term ) ), 1006 'embeddable' => true, 1007 ); 1008 } 1009 } 1010 1011 $taxonomy_obj = get_taxonomy( $term->taxonomy ); 1012 1013 if ( empty( $taxonomy_obj->object_type ) ) { 1014 return $links; 1015 } 1016 1017 $post_type_links = array(); 1018 1019 foreach ( $taxonomy_obj->object_type as $type ) { 1020 $rest_path = rest_get_route_for_post_type_items( $type ); 1021 1022 if ( empty( $rest_path ) ) { 1023 continue; 1024 } 1025 1026 $post_type_links[] = array( 1027 'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( $rest_path ) ), 1028 ); 1029 } 1030 1031 if ( ! empty( $post_type_links ) ) { 1032 $links['https://api.w.org/post_type'] = $post_type_links; 1033 } 1034 1035 return $links; 1036 } 1037 1038 /** 1039 * Retrieves the term's schema, conforming to JSON Schema. 1040 * 1041 * @since 4.7.0 1042 * 1043 * @return array Item schema data. 1044 */ 1045 public function get_item_schema() { 1046 if ( $this->schema ) { 1047 return $this->add_additional_fields_schema( $this->schema ); 1048 } 1049 1050 $schema = array( 1051 '$schema' => 'http://json-schema.org/draft-04/schema#', 1052 'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy, 1053 'type' => 'object', 1054 'properties' => array( 1055 'id' => array( 1056 'description' => __( 'Unique identifier for the term.' ), 1057 'type' => 'integer', 1058 'context' => array( 'view', 'embed', 'edit' ), 1059 'readonly' => true, 1060 ), 1061 'count' => array( 1062 'description' => __( 'Number of published posts for the term.' ), 1063 'type' => 'integer', 1064 'context' => array( 'view', 'edit' ), 1065 'readonly' => true, 1066 ), 1067 'description' => array( 1068 'description' => __( 'HTML description of the term.' ), 1069 'type' => 'string', 1070 'context' => array( 'view', 'edit' ), 1071 ), 1072 'link' => array( 1073 'description' => __( 'URL of the term.' ), 1074 'type' => 'string', 1075 'format' => 'uri', 1076 'context' => array( 'view', 'embed', 'edit' ), 1077 'readonly' => true, 1078 ), 1079 'name' => array( 1080 'description' => __( 'HTML title for the term.' ), 1081 'type' => 'string', 1082 'context' => array( 'view', 'embed', 'edit' ), 1083 'arg_options' => array( 1084 'sanitize_callback' => 'sanitize_text_field', 1085 ), 1086 'required' => true, 1087 ), 1088 'slug' => array( 1089 'description' => __( 'An alphanumeric identifier for the term unique to its type.' ), 1090 'type' => 'string', 1091 'context' => array( 'view', 'embed', 'edit' ), 1092 'arg_options' => array( 1093 'sanitize_callback' => array( $this, 'sanitize_slug' ), 1094 ), 1095 ), 1096 'taxonomy' => array( 1097 'description' => __( 'Type attribution for the term.' ), 1098 'type' => 'string', 1099 'enum' => array( $this->taxonomy ), 1100 'context' => array( 'view', 'embed', 'edit' ), 1101 'readonly' => true, 1102 ), 1103 ), 1104 ); 1105 1106 $taxonomy = get_taxonomy( $this->taxonomy ); 1107 1108 if ( $taxonomy->hierarchical ) { 1109 $schema['properties']['parent'] = array( 1110 'description' => __( 'The parent term ID.' ), 1111 'type' => 'integer', 1112 'context' => array( 'view', 'edit' ), 1113 ); 1114 } 1115 1116 $schema['properties']['meta'] = $this->meta->get_field_schema(); 1117 1118 $this->schema = $schema; 1119 1120 return $this->add_additional_fields_schema( $this->schema ); 1121 } 1122 1123 /** 1124 * Retrieves the query params for collections. 1125 * 1126 * @since 4.7.0 1127 * 1128 * @return array Collection parameters. 1129 */ 1130 public function get_collection_params() { 1131 $query_params = parent::get_collection_params(); 1132 $taxonomy = get_taxonomy( $this->taxonomy ); 1133 1134 $query_params['context']['default'] = 'view'; 1135 1136 $query_params['exclude'] = array( 1137 'description' => __( 'Ensure result set excludes specific IDs.' ), 1138 'type' => 'array', 1139 'items' => array( 1140 'type' => 'integer', 1141 ), 1142 'default' => array(), 1143 ); 1144 1145 $query_params['include'] = array( 1146 'description' => __( 'Limit result set to specific IDs.' ), 1147 'type' => 'array', 1148 'items' => array( 1149 'type' => 'integer', 1150 ), 1151 'default' => array(), 1152 ); 1153 1154 if ( ! $taxonomy->hierarchical ) { 1155 $query_params['offset'] = array( 1156 'description' => __( 'Offset the result set by a specific number of items.' ), 1157 'type' => 'integer', 1158 ); 1159 } 1160 1161 $query_params['order'] = array( 1162 'description' => __( 'Order sort attribute ascending or descending.' ), 1163 'type' => 'string', 1164 'default' => 'asc', 1165 'enum' => array( 1166 'asc', 1167 'desc', 1168 ), 1169 ); 1170 1171 $query_params['orderby'] = array( 1172 'description' => __( 'Sort collection by term attribute.' ), 1173 'type' => 'string', 1174 'default' => 'name', 1175 'enum' => array( 1176 'id', 1177 'include', 1178 'name', 1179 'slug', 1180 'include_slugs', 1181 'term_group', 1182 'description', 1183 'count', 1184 ), 1185 ); 1186 1187 $query_params['hide_empty'] = array( 1188 'description' => __( 'Whether to hide terms not assigned to any posts.' ), 1189 'type' => 'boolean', 1190 'default' => false, 1191 ); 1192 1193 if ( $taxonomy->hierarchical ) { 1194 $query_params['parent'] = array( 1195 'description' => __( 'Limit result set to terms assigned to a specific parent.' ), 1196 'type' => 'integer', 1197 ); 1198 } 1199 1200 $query_params['post'] = array( 1201 'description' => __( 'Limit result set to terms assigned to a specific post.' ), 1202 'type' => 'integer', 1203 'default' => null, 1204 ); 1205 1206 $query_params['slug'] = array( 1207 'description' => __( 'Limit result set to terms with one or more specific slugs.' ), 1208 'type' => 'array', 1209 'items' => array( 1210 'type' => 'string', 1211 ), 1212 ); 1213 1214 /** 1215 * Filters collection parameters for the terms controller. 1216 * 1217 * The dynamic part of the filter `$this->taxonomy` refers to the taxonomy 1218 * slug for the controller. 1219 * 1220 * This filter registers the collection parameter, but does not map the 1221 * collection parameter to an internal WP_Term_Query parameter. Use the 1222 * `rest_{$this->taxonomy}_query` filter to set WP_Term_Query parameters. 1223 * 1224 * @since 4.7.0 1225 * 1226 * @param array $query_params JSON Schema-formatted collection parameters. 1227 * @param WP_Taxonomy $taxonomy Taxonomy object. 1228 */ 1229 return apply_filters( "rest_{$this->taxonomy}_collection_params", $query_params, $taxonomy ); 1230 } 1231 1232 /** 1233 * Checks that the taxonomy is valid. 1234 * 1235 * @since 4.7.0 1236 * 1237 * @param string $taxonomy Taxonomy to check. 1238 * @return bool Whether the taxonomy is allowed for REST management. 1239 */ 1240 protected function check_is_taxonomy_allowed( $taxonomy ) { 1241 $taxonomy_obj = get_taxonomy( $taxonomy ); 1242 if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) { 1243 return true; 1244 } 1245 return false; 1246 } 1247 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Oct 10 08:20:03 2025 | Cross-referenced by PHPXref |