[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Posts_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 4.7.0 8 */ 9 10 /** 11 * Core class to access posts via the REST API. 12 * 13 * @since 4.7.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Posts_Controller extends WP_REST_Controller { 18 /** 19 * Post type. 20 * 21 * @since 4.7.0 22 * @var string 23 */ 24 protected $post_type; 25 26 /** 27 * Instance of a post meta fields object. 28 * 29 * @since 4.7.0 30 * @var WP_REST_Post_Meta_Fields 31 */ 32 protected $meta; 33 34 /** 35 * Constructor. 36 * 37 * @since 4.7.0 38 * 39 * @param string $post_type Post type. 40 */ 41 public function __construct( $post_type ) { 42 $this->post_type = $post_type; 43 $this->namespace = 'wp/v2'; 44 $obj = get_post_type_object( $post_type ); 45 $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; 46 47 $this->meta = new WP_REST_Post_Meta_Fields( $this->post_type ); 48 } 49 50 /** 51 * Registers the routes for the objects of the controller. 52 * 53 * @since 4.7.0 54 * 55 * @see register_rest_route() 56 */ 57 public function register_routes() { 58 59 register_rest_route( 60 $this->namespace, 61 '/' . $this->rest_base, 62 array( 63 array( 64 'methods' => WP_REST_Server::READABLE, 65 'callback' => array( $this, 'get_items' ), 66 'permission_callback' => array( $this, 'get_items_permissions_check' ), 67 'args' => $this->get_collection_params(), 68 ), 69 array( 70 'methods' => WP_REST_Server::CREATABLE, 71 'callback' => array( $this, 'create_item' ), 72 'permission_callback' => array( $this, 'create_item_permissions_check' ), 73 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), 74 ), 75 'schema' => array( $this, 'get_public_item_schema' ), 76 ) 77 ); 78 79 $schema = $this->get_item_schema(); 80 $get_item_args = array( 81 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 82 ); 83 if ( isset( $schema['properties']['password'] ) ) { 84 $get_item_args['password'] = array( 85 'description' => __( 'The password for the post if it is password protected.' ), 86 'type' => 'string', 87 ); 88 } 89 register_rest_route( 90 $this->namespace, 91 '/' . $this->rest_base . '/(?P<id>[\d]+)', 92 array( 93 'args' => array( 94 'id' => array( 95 'description' => __( 'Unique identifier for the object.' ), 96 'type' => 'integer', 97 ), 98 ), 99 array( 100 'methods' => WP_REST_Server::READABLE, 101 'callback' => array( $this, 'get_item' ), 102 'permission_callback' => array( $this, 'get_item_permissions_check' ), 103 'args' => $get_item_args, 104 ), 105 array( 106 'methods' => WP_REST_Server::EDITABLE, 107 'callback' => array( $this, 'update_item' ), 108 'permission_callback' => array( $this, 'update_item_permissions_check' ), 109 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), 110 ), 111 array( 112 'methods' => WP_REST_Server::DELETABLE, 113 'callback' => array( $this, 'delete_item' ), 114 'permission_callback' => array( $this, 'delete_item_permissions_check' ), 115 'args' => array( 116 'force' => array( 117 'type' => 'boolean', 118 'default' => false, 119 'description' => __( 'Whether to bypass Trash and force deletion.' ), 120 ), 121 ), 122 ), 123 'schema' => array( $this, 'get_public_item_schema' ), 124 ) 125 ); 126 } 127 128 /** 129 * Checks if a given request has access to read posts. 130 * 131 * @since 4.7.0 132 * 133 * @param WP_REST_Request $request Full details about the request. 134 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 135 */ 136 public function get_items_permissions_check( $request ) { 137 138 $post_type = get_post_type_object( $this->post_type ); 139 140 if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) { 141 return new WP_Error( 142 'rest_forbidden_context', 143 __( 'Sorry, you are not allowed to edit posts in this post type.' ), 144 array( 'status' => rest_authorization_required_code() ) 145 ); 146 } 147 148 return true; 149 } 150 151 /** 152 * Retrieves a collection of posts. 153 * 154 * @since 4.7.0 155 * 156 * @param WP_REST_Request $request Full details about the request. 157 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 158 */ 159 public function get_items( $request ) { 160 161 // Ensure a search string is set in case the orderby is set to 'relevance'. 162 if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) { 163 return new WP_Error( 164 'rest_no_search_term_defined', 165 __( 'You need to define a search term to order by relevance.' ), 166 array( 'status' => 400 ) 167 ); 168 } 169 170 // Ensure an include parameter is set in case the orderby is set to 'include'. 171 if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { 172 return new WP_Error( 173 'rest_orderby_include_missing_include', 174 __( 'You need to define an include parameter to order by include.' ), 175 array( 'status' => 400 ) 176 ); 177 } 178 179 // Retrieve the list of registered collection query parameters. 180 $registered = $this->get_collection_params(); 181 $args = array(); 182 183 /* 184 * This array defines mappings between public API query parameters whose 185 * values are accepted as-passed, and their internal WP_Query parameter 186 * name equivalents (some are the same). Only values which are also 187 * present in $registered will be set. 188 */ 189 $parameter_mappings = array( 190 'author' => 'author__in', 191 'author_exclude' => 'author__not_in', 192 'exclude' => 'post__not_in', 193 'include' => 'post__in', 194 'menu_order' => 'menu_order', 195 'offset' => 'offset', 196 'order' => 'order', 197 'orderby' => 'orderby', 198 'page' => 'paged', 199 'parent' => 'post_parent__in', 200 'parent_exclude' => 'post_parent__not_in', 201 'search' => 's', 202 'slug' => 'post_name__in', 203 'status' => 'post_status', 204 ); 205 206 /* 207 * For each known parameter which is both registered and present in the request, 208 * set the parameter's value on the query $args. 209 */ 210 foreach ( $parameter_mappings as $api_param => $wp_param ) { 211 if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 212 $args[ $wp_param ] = $request[ $api_param ]; 213 } 214 } 215 216 // Check for & assign any parameters which require special handling or setting. 217 $args['date_query'] = array(); 218 219 // Set before into date query. Date query must be specified as an array of an array. 220 if ( isset( $registered['before'], $request['before'] ) ) { 221 $args['date_query'][0]['before'] = $request['before']; 222 } 223 224 // Set after into date query. Date query must be specified as an array of an array. 225 if ( isset( $registered['after'], $request['after'] ) ) { 226 $args['date_query'][0]['after'] = $request['after']; 227 } 228 229 // Ensure our per_page parameter overrides any provided posts_per_page filter. 230 if ( isset( $registered['per_page'] ) ) { 231 $args['posts_per_page'] = $request['per_page']; 232 } 233 234 if ( isset( $registered['sticky'], $request['sticky'] ) ) { 235 $sticky_posts = get_option( 'sticky_posts', array() ); 236 if ( ! is_array( $sticky_posts ) ) { 237 $sticky_posts = array(); 238 } 239 if ( $request['sticky'] ) { 240 /* 241 * As post__in will be used to only get sticky posts, 242 * we have to support the case where post__in was already 243 * specified. 244 */ 245 $args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts; 246 247 /* 248 * If we intersected, but there are no post IDs in common, 249 * WP_Query won't return "no posts" for post__in = array() 250 * so we have to fake it a bit. 251 */ 252 if ( ! $args['post__in'] ) { 253 $args['post__in'] = array( 0 ); 254 } 255 } elseif ( $sticky_posts ) { 256 /* 257 * As post___not_in will be used to only get posts that 258 * are not sticky, we have to support the case where post__not_in 259 * was already specified. 260 */ 261 $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); 262 } 263 } 264 265 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 266 267 if ( ! empty( $request['tax_relation'] ) ) { 268 $args['tax_query'] = array( 'relation' => $request['tax_relation'] ); 269 } 270 271 foreach ( $taxonomies as $taxonomy ) { 272 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 273 $tax_exclude = $base . '_exclude'; 274 275 if ( ! empty( $request[ $base ] ) ) { 276 $args['tax_query'][] = array( 277 'taxonomy' => $taxonomy->name, 278 'field' => 'term_id', 279 'terms' => $request[ $base ], 280 'include_children' => false, 281 ); 282 } 283 284 if ( ! empty( $request[ $tax_exclude ] ) ) { 285 $args['tax_query'][] = array( 286 'taxonomy' => $taxonomy->name, 287 'field' => 'term_id', 288 'terms' => $request[ $tax_exclude ], 289 'include_children' => false, 290 'operator' => 'NOT IN', 291 ); 292 } 293 } 294 295 // Force the post_type argument, since it's not a user input variable. 296 $args['post_type'] = $this->post_type; 297 298 /** 299 * Filters WP_Query arguments when querying users via the REST API. 300 * 301 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 302 * 303 * Possible filter names include: 304 * 305 * - `rest_post_query` 306 * - `rest_page_query` 307 * - `rest_attachment_query` 308 * 309 * Enables adding extra arguments or setting defaults for a post collection request. 310 * 311 * @since 4.7.0 312 * @since 5.7.0 Moved after the `tax_query` query arg is generated. 313 * 314 * @link https://developer.wordpress.org/reference/classes/wp_query/ 315 * 316 * @param array $args Array of arguments to be passed to WP_Query. 317 * @param WP_REST_Request $request The REST API request. 318 */ 319 $args = apply_filters( "rest_{$this->post_type}_query", $args, $request ); 320 $query_args = $this->prepare_items_query( $args, $request ); 321 322 $posts_query = new WP_Query(); 323 $query_result = $posts_query->query( $query_args ); 324 325 // Allow access to all password protected posts if the context is edit. 326 if ( 'edit' === $request['context'] ) { 327 add_filter( 'post_password_required', '__return_false' ); 328 } 329 330 $posts = array(); 331 332 foreach ( $query_result as $post ) { 333 if ( ! $this->check_read_permission( $post ) ) { 334 continue; 335 } 336 337 $data = $this->prepare_item_for_response( $post, $request ); 338 $posts[] = $this->prepare_response_for_collection( $data ); 339 } 340 341 // Reset filter. 342 if ( 'edit' === $request['context'] ) { 343 remove_filter( 'post_password_required', '__return_false' ); 344 } 345 346 $page = (int) $query_args['paged']; 347 $total_posts = $posts_query->found_posts; 348 349 if ( $total_posts < 1 ) { 350 // Out-of-bounds, run the query again without LIMIT for total count. 351 unset( $query_args['paged'] ); 352 353 $count_query = new WP_Query(); 354 $count_query->query( $query_args ); 355 $total_posts = $count_query->found_posts; 356 } 357 358 $max_pages = ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); 359 360 if ( $page > $max_pages && $total_posts > 0 ) { 361 return new WP_Error( 362 'rest_post_invalid_page_number', 363 __( 'The page number requested is larger than the number of pages available.' ), 364 array( 'status' => 400 ) 365 ); 366 } 367 368 $response = rest_ensure_response( $posts ); 369 370 $response->header( 'X-WP-Total', (int) $total_posts ); 371 $response->header( 'X-WP-TotalPages', (int) $max_pages ); 372 373 $request_params = $request->get_query_params(); 374 $base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); 375 376 if ( $page > 1 ) { 377 $prev_page = $page - 1; 378 379 if ( $prev_page > $max_pages ) { 380 $prev_page = $max_pages; 381 } 382 383 $prev_link = add_query_arg( 'page', $prev_page, $base ); 384 $response->link_header( 'prev', $prev_link ); 385 } 386 if ( $max_pages > $page ) { 387 $next_page = $page + 1; 388 $next_link = add_query_arg( 'page', $next_page, $base ); 389 390 $response->link_header( 'next', $next_link ); 391 } 392 393 return $response; 394 } 395 396 /** 397 * Get the post, if the ID is valid. 398 * 399 * @since 4.7.2 400 * 401 * @param int $id Supplied ID. 402 * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. 403 */ 404 protected function get_post( $id ) { 405 $error = new WP_Error( 406 'rest_post_invalid_id', 407 __( 'Invalid post ID.' ), 408 array( 'status' => 404 ) 409 ); 410 411 if ( (int) $id <= 0 ) { 412 return $error; 413 } 414 415 $post = get_post( (int) $id ); 416 if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 417 return $error; 418 } 419 420 return $post; 421 } 422 423 /** 424 * Checks if a given request has access to read a post. 425 * 426 * @since 4.7.0 427 * 428 * @param WP_REST_Request $request Full details about the request. 429 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. 430 */ 431 public function get_item_permissions_check( $request ) { 432 $post = $this->get_post( $request['id'] ); 433 if ( is_wp_error( $post ) ) { 434 return $post; 435 } 436 437 if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) { 438 return new WP_Error( 439 'rest_forbidden_context', 440 __( 'Sorry, you are not allowed to edit this post.' ), 441 array( 'status' => rest_authorization_required_code() ) 442 ); 443 } 444 445 if ( $post && ! empty( $request['password'] ) ) { 446 // Check post password, and return error if invalid. 447 if ( ! hash_equals( $post->post_password, $request['password'] ) ) { 448 return new WP_Error( 449 'rest_post_incorrect_password', 450 __( 'Incorrect post password.' ), 451 array( 'status' => 403 ) 452 ); 453 } 454 } 455 456 // Allow access to all password protected posts if the context is edit. 457 if ( 'edit' === $request['context'] ) { 458 add_filter( 'post_password_required', '__return_false' ); 459 } 460 461 if ( $post ) { 462 return $this->check_read_permission( $post ); 463 } 464 465 return true; 466 } 467 468 /** 469 * Checks if the user can access password-protected content. 470 * 471 * This method determines whether we need to override the regular password 472 * check in core with a filter. 473 * 474 * @since 4.7.0 475 * 476 * @param WP_Post $post Post to check against. 477 * @param WP_REST_Request $request Request data to check. 478 * @return bool True if the user can access password-protected content, otherwise false. 479 */ 480 public function can_access_password_content( $post, $request ) { 481 if ( empty( $post->post_password ) ) { 482 // No filter required. 483 return false; 484 } 485 486 // Edit context always gets access to password-protected posts. 487 if ( 'edit' === $request['context'] ) { 488 return true; 489 } 490 491 // No password, no auth. 492 if ( empty( $request['password'] ) ) { 493 return false; 494 } 495 496 // Double-check the request password. 497 return hash_equals( $post->post_password, $request['password'] ); 498 } 499 500 /** 501 * Retrieves a single post. 502 * 503 * @since 4.7.0 504 * 505 * @param WP_REST_Request $request Full details about the request. 506 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 507 */ 508 public function get_item( $request ) { 509 $post = $this->get_post( $request['id'] ); 510 if ( is_wp_error( $post ) ) { 511 return $post; 512 } 513 514 $data = $this->prepare_item_for_response( $post, $request ); 515 $response = rest_ensure_response( $data ); 516 517 if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) { 518 $response->link_header( 'alternate', get_permalink( $post->ID ), array( 'type' => 'text/html' ) ); 519 } 520 521 return $response; 522 } 523 524 /** 525 * Checks if a given request has access to create a post. 526 * 527 * @since 4.7.0 528 * 529 * @param WP_REST_Request $request Full details about the request. 530 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. 531 */ 532 public function create_item_permissions_check( $request ) { 533 if ( ! empty( $request['id'] ) ) { 534 return new WP_Error( 535 'rest_post_exists', 536 __( 'Cannot create existing post.' ), 537 array( 'status' => 400 ) 538 ); 539 } 540 541 $post_type = get_post_type_object( $this->post_type ); 542 543 if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 544 return new WP_Error( 545 'rest_cannot_edit_others', 546 __( 'Sorry, you are not allowed to create posts as this user.' ), 547 array( 'status' => rest_authorization_required_code() ) 548 ); 549 } 550 551 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) { 552 return new WP_Error( 553 'rest_cannot_assign_sticky', 554 __( 'Sorry, you are not allowed to make posts sticky.' ), 555 array( 'status' => rest_authorization_required_code() ) 556 ); 557 } 558 559 if ( ! current_user_can( $post_type->cap->create_posts ) ) { 560 return new WP_Error( 561 'rest_cannot_create', 562 __( 'Sorry, you are not allowed to create posts as this user.' ), 563 array( 'status' => rest_authorization_required_code() ) 564 ); 565 } 566 567 if ( ! $this->check_assign_terms_permission( $request ) ) { 568 return new WP_Error( 569 'rest_cannot_assign_term', 570 __( 'Sorry, you are not allowed to assign the provided terms.' ), 571 array( 'status' => rest_authorization_required_code() ) 572 ); 573 } 574 575 return true; 576 } 577 578 /** 579 * Creates a single post. 580 * 581 * @since 4.7.0 582 * 583 * @param WP_REST_Request $request Full details about the request. 584 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 585 */ 586 public function create_item( $request ) { 587 if ( ! empty( $request['id'] ) ) { 588 return new WP_Error( 589 'rest_post_exists', 590 __( 'Cannot create existing post.' ), 591 array( 'status' => 400 ) 592 ); 593 } 594 595 $prepared_post = $this->prepare_item_for_database( $request ); 596 597 if ( is_wp_error( $prepared_post ) ) { 598 return $prepared_post; 599 } 600 601 $prepared_post->post_type = $this->post_type; 602 603 $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false ); 604 605 if ( is_wp_error( $post_id ) ) { 606 607 if ( 'db_insert_error' === $post_id->get_error_code() ) { 608 $post_id->add_data( array( 'status' => 500 ) ); 609 } else { 610 $post_id->add_data( array( 'status' => 400 ) ); 611 } 612 613 return $post_id; 614 } 615 616 $post = get_post( $post_id ); 617 618 /** 619 * Fires after a single post is created or updated via the REST API. 620 * 621 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 622 * 623 * @since 4.7.0 624 * 625 * @param WP_Post $post Inserted or updated post object. 626 * @param WP_REST_Request $request Request object. 627 * @param bool $creating True when creating a post, false when updating. 628 */ 629 do_action( "rest_insert_{$this->post_type}", $post, $request, true ); 630 631 $schema = $this->get_item_schema(); 632 633 if ( ! empty( $schema['properties']['sticky'] ) ) { 634 if ( ! empty( $request['sticky'] ) ) { 635 stick_post( $post_id ); 636 } else { 637 unstick_post( $post_id ); 638 } 639 } 640 641 if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 642 $this->handle_featured_media( $request['featured_media'], $post_id ); 643 } 644 645 if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { 646 set_post_format( $post, $request['format'] ); 647 } 648 649 if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { 650 $this->handle_template( $request['template'], $post_id, true ); 651 } 652 653 $terms_update = $this->handle_terms( $post_id, $request ); 654 655 if ( is_wp_error( $terms_update ) ) { 656 return $terms_update; 657 } 658 659 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 660 $meta_update = $this->meta->update_value( $request['meta'], $post_id ); 661 662 if ( is_wp_error( $meta_update ) ) { 663 return $meta_update; 664 } 665 } 666 667 $post = get_post( $post_id ); 668 $fields_update = $this->update_additional_fields_for_object( $post, $request ); 669 670 if ( is_wp_error( $fields_update ) ) { 671 return $fields_update; 672 } 673 674 $request->set_param( 'context', 'edit' ); 675 676 /** 677 * Fires after a single post is completely created or updated via the REST API. 678 * 679 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 680 * 681 * @since 5.0.0 682 * 683 * @param WP_Post $post Inserted or updated post object. 684 * @param WP_REST_Request $request Request object. 685 * @param bool $creating True when creating a post, false when updating. 686 */ 687 do_action( "rest_after_insert_{$this->post_type}", $post, $request, true ); 688 689 wp_after_insert_post( $post, false, null ); 690 691 $response = $this->prepare_item_for_response( $post, $request ); 692 $response = rest_ensure_response( $response ); 693 694 $response->set_status( 201 ); 695 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); 696 697 return $response; 698 } 699 700 /** 701 * Checks if a given request has access to update a post. 702 * 703 * @since 4.7.0 704 * 705 * @param WP_REST_Request $request Full details about the request. 706 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. 707 */ 708 public function update_item_permissions_check( $request ) { 709 $post = $this->get_post( $request['id'] ); 710 if ( is_wp_error( $post ) ) { 711 return $post; 712 } 713 714 $post_type = get_post_type_object( $this->post_type ); 715 716 if ( $post && ! $this->check_update_permission( $post ) ) { 717 return new WP_Error( 718 'rest_cannot_edit', 719 __( 'Sorry, you are not allowed to edit this post.' ), 720 array( 'status' => rest_authorization_required_code() ) 721 ); 722 } 723 724 if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 725 return new WP_Error( 726 'rest_cannot_edit_others', 727 __( 'Sorry, you are not allowed to update posts as this user.' ), 728 array( 'status' => rest_authorization_required_code() ) 729 ); 730 } 731 732 if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) && ! current_user_can( $post_type->cap->publish_posts ) ) { 733 return new WP_Error( 734 'rest_cannot_assign_sticky', 735 __( 'Sorry, you are not allowed to make posts sticky.' ), 736 array( 'status' => rest_authorization_required_code() ) 737 ); 738 } 739 740 if ( ! $this->check_assign_terms_permission( $request ) ) { 741 return new WP_Error( 742 'rest_cannot_assign_term', 743 __( 'Sorry, you are not allowed to assign the provided terms.' ), 744 array( 'status' => rest_authorization_required_code() ) 745 ); 746 } 747 748 return true; 749 } 750 751 /** 752 * Updates a single post. 753 * 754 * @since 4.7.0 755 * 756 * @param WP_REST_Request $request Full details about the request. 757 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 758 */ 759 public function update_item( $request ) { 760 $valid_check = $this->get_post( $request['id'] ); 761 if ( is_wp_error( $valid_check ) ) { 762 return $valid_check; 763 } 764 765 $post_before = get_post( $request['id'] ); 766 $post = $this->prepare_item_for_database( $request ); 767 768 if ( is_wp_error( $post ) ) { 769 return $post; 770 } 771 772 // Convert the post object to an array, otherwise wp_update_post() will expect non-escaped input. 773 $post_id = wp_update_post( wp_slash( (array) $post ), true, false ); 774 775 if ( is_wp_error( $post_id ) ) { 776 if ( 'db_update_error' === $post_id->get_error_code() ) { 777 $post_id->add_data( array( 'status' => 500 ) ); 778 } else { 779 $post_id->add_data( array( 'status' => 400 ) ); 780 } 781 return $post_id; 782 } 783 784 $post = get_post( $post_id ); 785 786 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ 787 do_action( "rest_insert_{$this->post_type}", $post, $request, false ); 788 789 $schema = $this->get_item_schema(); 790 791 if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { 792 set_post_format( $post, $request['format'] ); 793 } 794 795 if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 796 $this->handle_featured_media( $request['featured_media'], $post_id ); 797 } 798 799 if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) { 800 if ( ! empty( $request['sticky'] ) ) { 801 stick_post( $post_id ); 802 } else { 803 unstick_post( $post_id ); 804 } 805 } 806 807 if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { 808 $this->handle_template( $request['template'], $post->ID ); 809 } 810 811 $terms_update = $this->handle_terms( $post->ID, $request ); 812 813 if ( is_wp_error( $terms_update ) ) { 814 return $terms_update; 815 } 816 817 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 818 $meta_update = $this->meta->update_value( $request['meta'], $post->ID ); 819 820 if ( is_wp_error( $meta_update ) ) { 821 return $meta_update; 822 } 823 } 824 825 $post = get_post( $post_id ); 826 $fields_update = $this->update_additional_fields_for_object( $post, $request ); 827 828 if ( is_wp_error( $fields_update ) ) { 829 return $fields_update; 830 } 831 832 $request->set_param( 'context', 'edit' ); 833 834 // Filter is fired in WP_REST_Attachments_Controller subclass. 835 if ( 'attachment' === $this->post_type ) { 836 $response = $this->prepare_item_for_response( $post, $request ); 837 return rest_ensure_response( $response ); 838 } 839 840 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ 841 do_action( "rest_after_insert_{$this->post_type}", $post, $request, false ); 842 843 wp_after_insert_post( $post, true, $post_before ); 844 845 $response = $this->prepare_item_for_response( $post, $request ); 846 847 return rest_ensure_response( $response ); 848 } 849 850 /** 851 * Checks if a given request has access to delete a post. 852 * 853 * @since 4.7.0 854 * 855 * @param WP_REST_Request $request Full details about the request. 856 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. 857 */ 858 public function delete_item_permissions_check( $request ) { 859 $post = $this->get_post( $request['id'] ); 860 if ( is_wp_error( $post ) ) { 861 return $post; 862 } 863 864 if ( $post && ! $this->check_delete_permission( $post ) ) { 865 return new WP_Error( 866 'rest_cannot_delete', 867 __( 'Sorry, you are not allowed to delete this post.' ), 868 array( 'status' => rest_authorization_required_code() ) 869 ); 870 } 871 872 return true; 873 } 874 875 /** 876 * Deletes a single post. 877 * 878 * @since 4.7.0 879 * 880 * @param WP_REST_Request $request Full details about the request. 881 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 882 */ 883 public function delete_item( $request ) { 884 $post = $this->get_post( $request['id'] ); 885 if ( is_wp_error( $post ) ) { 886 return $post; 887 } 888 889 $id = $post->ID; 890 $force = (bool) $request['force']; 891 892 $supports_trash = ( EMPTY_TRASH_DAYS > 0 ); 893 894 if ( 'attachment' === $post->post_type ) { 895 $supports_trash = $supports_trash && MEDIA_TRASH; 896 } 897 898 /** 899 * Filters whether a post is trashable. 900 * 901 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 902 * 903 * Pass false to disable Trash support for the post. 904 * 905 * @since 4.7.0 906 * 907 * @param bool $supports_trash Whether the post type support trashing. 908 * @param WP_Post $post The Post object being considered for trashing support. 909 */ 910 $supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post ); 911 912 if ( ! $this->check_delete_permission( $post ) ) { 913 return new WP_Error( 914 'rest_user_cannot_delete_post', 915 __( 'Sorry, you are not allowed to delete this post.' ), 916 array( 'status' => rest_authorization_required_code() ) 917 ); 918 } 919 920 $request->set_param( 'context', 'edit' ); 921 922 // If we're forcing, then delete permanently. 923 if ( $force ) { 924 $previous = $this->prepare_item_for_response( $post, $request ); 925 $result = wp_delete_post( $id, true ); 926 $response = new WP_REST_Response(); 927 $response->set_data( 928 array( 929 'deleted' => true, 930 'previous' => $previous->get_data(), 931 ) 932 ); 933 } else { 934 // If we don't support trashing for this type, error out. 935 if ( ! $supports_trash ) { 936 return new WP_Error( 937 'rest_trash_not_supported', 938 /* translators: %s: force=true */ 939 sprintf( __( "The post does not support trashing. Set '%s' to delete." ), 'force=true' ), 940 array( 'status' => 501 ) 941 ); 942 } 943 944 // Otherwise, only trash if we haven't already. 945 if ( 'trash' === $post->post_status ) { 946 return new WP_Error( 947 'rest_already_trashed', 948 __( 'The post has already been deleted.' ), 949 array( 'status' => 410 ) 950 ); 951 } 952 953 // (Note that internally this falls through to `wp_delete_post()` 954 // if the Trash is disabled.) 955 $result = wp_trash_post( $id ); 956 $post = get_post( $id ); 957 $response = $this->prepare_item_for_response( $post, $request ); 958 } 959 960 if ( ! $result ) { 961 return new WP_Error( 962 'rest_cannot_delete', 963 __( 'The post cannot be deleted.' ), 964 array( 'status' => 500 ) 965 ); 966 } 967 968 /** 969 * Fires immediately after a single post is deleted or trashed via the REST API. 970 * 971 * They dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 972 * 973 * @since 4.7.0 974 * 975 * @param WP_Post $post The deleted or trashed post. 976 * @param WP_REST_Response $response The response data. 977 * @param WP_REST_Request $request The request sent to the API. 978 */ 979 do_action( "rest_delete_{$this->post_type}", $post, $response, $request ); 980 981 return $response; 982 } 983 984 /** 985 * Determines the allowed query_vars for a get_items() response and prepares 986 * them for WP_Query. 987 * 988 * @since 4.7.0 989 * 990 * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. 991 * @param WP_REST_Request $request Optional. Full details about the request. 992 * @return array Items query arguments. 993 */ 994 protected function prepare_items_query( $prepared_args = array(), $request = null ) { 995 $query_args = array(); 996 997 foreach ( $prepared_args as $key => $value ) { 998 /** 999 * Filters the query_vars used in get_items() for the constructed query. 1000 * 1001 * The dynamic portion of the hook name, `$key`, refers to the query_var key. 1002 * 1003 * @since 4.7.0 1004 * 1005 * @param string $value The query_var value. 1006 */ 1007 $query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores 1008 } 1009 1010 if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) { 1011 $query_args['ignore_sticky_posts'] = true; 1012 } 1013 1014 // Map to proper WP_Query orderby param. 1015 if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) { 1016 $orderby_mappings = array( 1017 'id' => 'ID', 1018 'include' => 'post__in', 1019 'slug' => 'post_name', 1020 'include_slugs' => 'post_name__in', 1021 ); 1022 1023 if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { 1024 $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; 1025 } 1026 } 1027 1028 return $query_args; 1029 } 1030 1031 /** 1032 * Checks the post_date_gmt or modified_gmt and prepare any post or 1033 * modified date for single post output. 1034 * 1035 * @since 4.7.0 1036 * 1037 * @param string $date_gmt GMT publication time. 1038 * @param string|null $date Optional. Local publication time. Default null. 1039 * @return string|null ISO8601/RFC3339 formatted datetime. 1040 */ 1041 protected function prepare_date_response( $date_gmt, $date = null ) { 1042 // Use the date if passed. 1043 if ( isset( $date ) ) { 1044 return mysql_to_rfc3339( $date ); 1045 } 1046 1047 // Return null if $date_gmt is empty/zeros. 1048 if ( '0000-00-00 00:00:00' === $date_gmt ) { 1049 return null; 1050 } 1051 1052 // Return the formatted datetime. 1053 return mysql_to_rfc3339( $date_gmt ); 1054 } 1055 1056 /** 1057 * Prepares a single post for create or update. 1058 * 1059 * @since 4.7.0 1060 * 1061 * @param WP_REST_Request $request Request object. 1062 * @return stdClass|WP_Error Post object or WP_Error. 1063 */ 1064 protected function prepare_item_for_database( $request ) { 1065 $prepared_post = new stdClass(); 1066 $current_status = ''; 1067 1068 // Post ID. 1069 if ( isset( $request['id'] ) ) { 1070 $existing_post = $this->get_post( $request['id'] ); 1071 if ( is_wp_error( $existing_post ) ) { 1072 return $existing_post; 1073 } 1074 1075 $prepared_post->ID = $existing_post->ID; 1076 $current_status = $existing_post->post_status; 1077 } 1078 1079 $schema = $this->get_item_schema(); 1080 1081 // Post title. 1082 if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) { 1083 if ( is_string( $request['title'] ) ) { 1084 $prepared_post->post_title = $request['title']; 1085 } elseif ( ! empty( $request['title']['raw'] ) ) { 1086 $prepared_post->post_title = $request['title']['raw']; 1087 } 1088 } 1089 1090 // Post content. 1091 if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) { 1092 if ( is_string( $request['content'] ) ) { 1093 $prepared_post->post_content = $request['content']; 1094 } elseif ( isset( $request['content']['raw'] ) ) { 1095 $prepared_post->post_content = $request['content']['raw']; 1096 } 1097 } 1098 1099 // Post excerpt. 1100 if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) { 1101 if ( is_string( $request['excerpt'] ) ) { 1102 $prepared_post->post_excerpt = $request['excerpt']; 1103 } elseif ( isset( $request['excerpt']['raw'] ) ) { 1104 $prepared_post->post_excerpt = $request['excerpt']['raw']; 1105 } 1106 } 1107 1108 // Post type. 1109 if ( empty( $request['id'] ) ) { 1110 // Creating new post, use default type for the controller. 1111 $prepared_post->post_type = $this->post_type; 1112 } else { 1113 // Updating a post, use previous type. 1114 $prepared_post->post_type = get_post_type( $request['id'] ); 1115 } 1116 1117 $post_type = get_post_type_object( $prepared_post->post_type ); 1118 1119 // Post status. 1120 if ( 1121 ! empty( $schema['properties']['status'] ) && 1122 isset( $request['status'] ) && 1123 ( ! $current_status || $current_status !== $request['status'] ) 1124 ) { 1125 $status = $this->handle_status_param( $request['status'], $post_type ); 1126 1127 if ( is_wp_error( $status ) ) { 1128 return $status; 1129 } 1130 1131 $prepared_post->post_status = $status; 1132 } 1133 1134 // Post date. 1135 if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) { 1136 $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date : false; 1137 $date_data = rest_get_date_with_gmt( $request['date'] ); 1138 1139 if ( ! empty( $date_data ) && $current_date !== $date_data[0] ) { 1140 list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; 1141 $prepared_post->edit_date = true; 1142 } 1143 } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) { 1144 $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date_gmt : false; 1145 $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); 1146 1147 if ( ! empty( $date_data ) && $current_date !== $date_data[1] ) { 1148 list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; 1149 $prepared_post->edit_date = true; 1150 } 1151 } 1152 1153 // Sending a null date or date_gmt value resets date and date_gmt to their 1154 // default values (`0000-00-00 00:00:00`). 1155 if ( 1156 ( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) || 1157 ( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] ) 1158 ) { 1159 $prepared_post->post_date_gmt = null; 1160 $prepared_post->post_date = null; 1161 } 1162 1163 // Post slug. 1164 if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) { 1165 $prepared_post->post_name = $request['slug']; 1166 } 1167 1168 // Author. 1169 if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) { 1170 $post_author = (int) $request['author']; 1171 1172 if ( get_current_user_id() !== $post_author ) { 1173 $user_obj = get_userdata( $post_author ); 1174 1175 if ( ! $user_obj ) { 1176 return new WP_Error( 1177 'rest_invalid_author', 1178 __( 'Invalid author ID.' ), 1179 array( 'status' => 400 ) 1180 ); 1181 } 1182 } 1183 1184 $prepared_post->post_author = $post_author; 1185 } 1186 1187 // Post password. 1188 if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) { 1189 $prepared_post->post_password = $request['password']; 1190 1191 if ( '' !== $request['password'] ) { 1192 if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { 1193 return new WP_Error( 1194 'rest_invalid_field', 1195 __( 'A post can not be sticky and have a password.' ), 1196 array( 'status' => 400 ) 1197 ); 1198 } 1199 1200 if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) { 1201 return new WP_Error( 1202 'rest_invalid_field', 1203 __( 'A sticky post can not be password protected.' ), 1204 array( 'status' => 400 ) 1205 ); 1206 } 1207 } 1208 } 1209 1210 if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { 1211 if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) { 1212 return new WP_Error( 1213 'rest_invalid_field', 1214 __( 'A password protected post can not be set to sticky.' ), 1215 array( 'status' => 400 ) 1216 ); 1217 } 1218 } 1219 1220 // Parent. 1221 if ( ! empty( $schema['properties']['parent'] ) && isset( $request['parent'] ) ) { 1222 if ( 0 === (int) $request['parent'] ) { 1223 $prepared_post->post_parent = 0; 1224 } else { 1225 $parent = get_post( (int) $request['parent'] ); 1226 1227 if ( empty( $parent ) ) { 1228 return new WP_Error( 1229 'rest_post_invalid_id', 1230 __( 'Invalid post parent ID.' ), 1231 array( 'status' => 400 ) 1232 ); 1233 } 1234 1235 $prepared_post->post_parent = (int) $parent->ID; 1236 } 1237 } 1238 1239 // Menu order. 1240 if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) { 1241 $prepared_post->menu_order = (int) $request['menu_order']; 1242 } 1243 1244 // Comment status. 1245 if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) { 1246 $prepared_post->comment_status = $request['comment_status']; 1247 } 1248 1249 // Ping status. 1250 if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) { 1251 $prepared_post->ping_status = $request['ping_status']; 1252 } 1253 1254 if ( ! empty( $schema['properties']['template'] ) ) { 1255 // Force template to null so that it can be handled exclusively by the REST controller. 1256 $prepared_post->page_template = null; 1257 } 1258 1259 /** 1260 * Filters a post before it is inserted via the REST API. 1261 * 1262 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 1263 * 1264 * @since 4.7.0 1265 * 1266 * @param stdClass $prepared_post An object representing a single post prepared 1267 * for inserting or updating the database. 1268 * @param WP_REST_Request $request Request object. 1269 */ 1270 return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request ); 1271 1272 } 1273 1274 /** 1275 * Checks whether the status is valid for the given post. 1276 * 1277 * Allows for sending an update request with the current status, even if that status would not be acceptable. 1278 * 1279 * @since 5.6.0 1280 * 1281 * @param string $status The provided status. 1282 * @param WP_REST_Request $request The request object. 1283 * @param string $param The parameter name. 1284 * @return true|WP_Error True if the status is valid, or WP_Error if not. 1285 */ 1286 public function check_status( $status, $request, $param ) { 1287 if ( $request['id'] ) { 1288 $post = $this->get_post( $request['id'] ); 1289 1290 if ( ! is_wp_error( $post ) && $post->post_status === $status ) { 1291 return true; 1292 } 1293 } 1294 1295 $args = $request->get_attributes()['args'][ $param ]; 1296 1297 return rest_validate_value_from_schema( $status, $args, $param ); 1298 } 1299 1300 /** 1301 * Determines validity and normalizes the given status parameter. 1302 * 1303 * @since 4.7.0 1304 * 1305 * @param string $post_status Post status. 1306 * @param WP_Post_Type $post_type Post type. 1307 * @return string|WP_Error Post status or WP_Error if lacking the proper permission. 1308 */ 1309 protected function handle_status_param( $post_status, $post_type ) { 1310 1311 switch ( $post_status ) { 1312 case 'draft': 1313 case 'pending': 1314 break; 1315 case 'private': 1316 if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 1317 return new WP_Error( 1318 'rest_cannot_publish', 1319 __( 'Sorry, you are not allowed to create private posts in this post type.' ), 1320 array( 'status' => rest_authorization_required_code() ) 1321 ); 1322 } 1323 break; 1324 case 'publish': 1325 case 'future': 1326 if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 1327 return new WP_Error( 1328 'rest_cannot_publish', 1329 __( 'Sorry, you are not allowed to publish posts in this post type.' ), 1330 array( 'status' => rest_authorization_required_code() ) 1331 ); 1332 } 1333 break; 1334 default: 1335 if ( ! get_post_status_object( $post_status ) ) { 1336 $post_status = 'draft'; 1337 } 1338 break; 1339 } 1340 1341 return $post_status; 1342 } 1343 1344 /** 1345 * Determines the featured media based on a request param. 1346 * 1347 * @since 4.7.0 1348 * 1349 * @param int $featured_media Featured Media ID. 1350 * @param int $post_id Post ID. 1351 * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error. 1352 */ 1353 protected function handle_featured_media( $featured_media, $post_id ) { 1354 1355 $featured_media = (int) $featured_media; 1356 if ( $featured_media ) { 1357 $result = set_post_thumbnail( $post_id, $featured_media ); 1358 if ( $result ) { 1359 return true; 1360 } else { 1361 return new WP_Error( 1362 'rest_invalid_featured_media', 1363 __( 'Invalid featured media ID.' ), 1364 array( 'status' => 400 ) 1365 ); 1366 } 1367 } else { 1368 return delete_post_thumbnail( $post_id ); 1369 } 1370 1371 } 1372 1373 /** 1374 * Check whether the template is valid for the given post. 1375 * 1376 * @since 4.9.0 1377 * 1378 * @param string $template Page template filename. 1379 * @param WP_REST_Request $request Request. 1380 * @return bool|WP_Error True if template is still valid or if the same as existing value, or false if template not supported. 1381 */ 1382 public function check_template( $template, $request ) { 1383 1384 if ( ! $template ) { 1385 return true; 1386 } 1387 1388 if ( $request['id'] ) { 1389 $post = get_post( $request['id'] ); 1390 $current_template = get_page_template_slug( $request['id'] ); 1391 } else { 1392 $post = null; 1393 $current_template = ''; 1394 } 1395 1396 // Always allow for updating a post to the same template, even if that template is no longer supported. 1397 if ( $template === $current_template ) { 1398 return true; 1399 } 1400 1401 // If this is a create request, get_post() will return null and wp theme will fallback to the passed post type. 1402 $allowed_templates = wp_get_theme()->get_page_templates( $post, $this->post_type ); 1403 1404 if ( isset( $allowed_templates[ $template ] ) ) { 1405 return true; 1406 } 1407 1408 return new WP_Error( 1409 'rest_invalid_param', 1410 /* translators: 1: Parameter, 2: List of valid values. */ 1411 sprintf( __( '%1$s is not one of %2$s.' ), 'template', implode( ', ', array_keys( $allowed_templates ) ) ) 1412 ); 1413 } 1414 1415 /** 1416 * Sets the template for a post. 1417 * 1418 * @since 4.7.0 1419 * @since 4.9.0 Added the `$validate` parameter. 1420 * 1421 * @param string $template Page template filename. 1422 * @param int $post_id Post ID. 1423 * @param bool $validate Whether to validate that the template selected is valid. 1424 */ 1425 public function handle_template( $template, $post_id, $validate = false ) { 1426 1427 if ( $validate && ! array_key_exists( $template, wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) { 1428 $template = ''; 1429 } 1430 1431 update_post_meta( $post_id, '_wp_page_template', $template ); 1432 } 1433 1434 /** 1435 * Updates the post's terms from a REST request. 1436 * 1437 * @since 4.7.0 1438 * 1439 * @param int $post_id The post ID to update the terms form. 1440 * @param WP_REST_Request $request The request object with post and terms data. 1441 * @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null. 1442 */ 1443 protected function handle_terms( $post_id, $request ) { 1444 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1445 1446 foreach ( $taxonomies as $taxonomy ) { 1447 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1448 1449 if ( ! isset( $request[ $base ] ) ) { 1450 continue; 1451 } 1452 1453 $result = wp_set_object_terms( $post_id, $request[ $base ], $taxonomy->name ); 1454 1455 if ( is_wp_error( $result ) ) { 1456 return $result; 1457 } 1458 } 1459 } 1460 1461 /** 1462 * Checks whether current user can assign all terms sent with the current request. 1463 * 1464 * @since 4.7.0 1465 * 1466 * @param WP_REST_Request $request The request object with post and terms data. 1467 * @return bool Whether the current user can assign the provided terms. 1468 */ 1469 protected function check_assign_terms_permission( $request ) { 1470 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1471 foreach ( $taxonomies as $taxonomy ) { 1472 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1473 1474 if ( ! isset( $request[ $base ] ) ) { 1475 continue; 1476 } 1477 1478 foreach ( $request[ $base ] as $term_id ) { 1479 // Invalid terms will be rejected later. 1480 if ( ! get_term( $term_id, $taxonomy->name ) ) { 1481 continue; 1482 } 1483 1484 if ( ! current_user_can( 'assign_term', (int) $term_id ) ) { 1485 return false; 1486 } 1487 } 1488 } 1489 1490 return true; 1491 } 1492 1493 /** 1494 * Checks if a given post type can be viewed or managed. 1495 * 1496 * @since 4.7.0 1497 * 1498 * @param WP_Post_Type|string $post_type Post type name or object. 1499 * @return bool Whether the post type is allowed in REST. 1500 */ 1501 protected function check_is_post_type_allowed( $post_type ) { 1502 if ( ! is_object( $post_type ) ) { 1503 $post_type = get_post_type_object( $post_type ); 1504 } 1505 1506 if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) { 1507 return true; 1508 } 1509 1510 return false; 1511 } 1512 1513 /** 1514 * Checks if a post can be read. 1515 * 1516 * Correctly handles posts with the inherit status. 1517 * 1518 * @since 4.7.0 1519 * 1520 * @param WP_Post $post Post object. 1521 * @return bool Whether the post can be read. 1522 */ 1523 public function check_read_permission( $post ) { 1524 $post_type = get_post_type_object( $post->post_type ); 1525 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1526 return false; 1527 } 1528 1529 // Is the post readable? 1530 if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) { 1531 return true; 1532 } 1533 1534 $post_status_obj = get_post_status_object( $post->post_status ); 1535 if ( $post_status_obj && $post_status_obj->public ) { 1536 return true; 1537 } 1538 1539 // Can we read the parent if we're inheriting? 1540 if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) { 1541 $parent = get_post( $post->post_parent ); 1542 if ( $parent ) { 1543 return $this->check_read_permission( $parent ); 1544 } 1545 } 1546 1547 /* 1548 * If there isn't a parent, but the status is set to inherit, assume 1549 * it's published (as per get_post_status()). 1550 */ 1551 if ( 'inherit' === $post->post_status ) { 1552 return true; 1553 } 1554 1555 return false; 1556 } 1557 1558 /** 1559 * Checks if a post can be edited. 1560 * 1561 * @since 4.7.0 1562 * 1563 * @param WP_Post $post Post object. 1564 * @return bool Whether the post can be edited. 1565 */ 1566 protected function check_update_permission( $post ) { 1567 $post_type = get_post_type_object( $post->post_type ); 1568 1569 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1570 return false; 1571 } 1572 1573 return current_user_can( 'edit_post', $post->ID ); 1574 } 1575 1576 /** 1577 * Checks if a post can be created. 1578 * 1579 * @since 4.7.0 1580 * 1581 * @param WP_Post $post Post object. 1582 * @return bool Whether the post can be created. 1583 */ 1584 protected function check_create_permission( $post ) { 1585 $post_type = get_post_type_object( $post->post_type ); 1586 1587 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1588 return false; 1589 } 1590 1591 return current_user_can( $post_type->cap->create_posts ); 1592 } 1593 1594 /** 1595 * Checks if a post can be deleted. 1596 * 1597 * @since 4.7.0 1598 * 1599 * @param WP_Post $post Post object. 1600 * @return bool Whether the post can be deleted. 1601 */ 1602 protected function check_delete_permission( $post ) { 1603 $post_type = get_post_type_object( $post->post_type ); 1604 1605 if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 1606 return false; 1607 } 1608 1609 return current_user_can( 'delete_post', $post->ID ); 1610 } 1611 1612 /** 1613 * Prepares a single post output for response. 1614 * 1615 * @since 4.7.0 1616 * 1617 * @param WP_Post $post Post object. 1618 * @param WP_REST_Request $request Request object. 1619 * @return WP_REST_Response Response object. 1620 */ 1621 public function prepare_item_for_response( $post, $request ) { 1622 $GLOBALS['post'] = $post; 1623 1624 setup_postdata( $post ); 1625 1626 $fields = $this->get_fields_for_response( $request ); 1627 1628 // Base fields for every post. 1629 $data = array(); 1630 1631 if ( rest_is_field_included( 'id', $fields ) ) { 1632 $data['id'] = $post->ID; 1633 } 1634 1635 if ( rest_is_field_included( 'date', $fields ) ) { 1636 $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); 1637 } 1638 1639 if ( rest_is_field_included( 'date_gmt', $fields ) ) { 1640 /* 1641 * For drafts, `post_date_gmt` may not be set, indicating that the date 1642 * of the draft should be updated each time it is saved (see #38883). 1643 * In this case, shim the value based on the `post_date` field 1644 * with the site's timezone offset applied. 1645 */ 1646 if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) { 1647 $post_date_gmt = get_gmt_from_date( $post->post_date ); 1648 } else { 1649 $post_date_gmt = $post->post_date_gmt; 1650 } 1651 $data['date_gmt'] = $this->prepare_date_response( $post_date_gmt ); 1652 } 1653 1654 if ( rest_is_field_included( 'guid', $fields ) ) { 1655 $data['guid'] = array( 1656 /** This filter is documented in wp-includes/post-template.php */ 1657 'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ), 1658 'raw' => $post->guid, 1659 ); 1660 } 1661 1662 if ( rest_is_field_included( 'modified', $fields ) ) { 1663 $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); 1664 } 1665 1666 if ( rest_is_field_included( 'modified_gmt', $fields ) ) { 1667 /* 1668 * For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments 1669 * above). In this case, shim the value based on the `post_modified` field 1670 * with the site's timezone offset applied. 1671 */ 1672 if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { 1673 $post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - ( get_option( 'gmt_offset' ) * 3600 ) ); 1674 } else { 1675 $post_modified_gmt = $post->post_modified_gmt; 1676 } 1677 $data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt ); 1678 } 1679 1680 if ( rest_is_field_included( 'password', $fields ) ) { 1681 $data['password'] = $post->post_password; 1682 } 1683 1684 if ( rest_is_field_included( 'slug', $fields ) ) { 1685 $data['slug'] = $post->post_name; 1686 } 1687 1688 if ( rest_is_field_included( 'status', $fields ) ) { 1689 $data['status'] = $post->post_status; 1690 } 1691 1692 if ( rest_is_field_included( 'type', $fields ) ) { 1693 $data['type'] = $post->post_type; 1694 } 1695 1696 if ( rest_is_field_included( 'link', $fields ) ) { 1697 $data['link'] = get_permalink( $post->ID ); 1698 } 1699 1700 if ( rest_is_field_included( 'title', $fields ) ) { 1701 $data['title'] = array(); 1702 } 1703 if ( rest_is_field_included( 'title.raw', $fields ) ) { 1704 $data['title']['raw'] = $post->post_title; 1705 } 1706 if ( rest_is_field_included( 'title.rendered', $fields ) ) { 1707 add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 1708 1709 $data['title']['rendered'] = get_the_title( $post->ID ); 1710 1711 remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 1712 } 1713 1714 $has_password_filter = false; 1715 1716 if ( $this->can_access_password_content( $post, $request ) ) { 1717 // Allow access to the post, permissions already checked before. 1718 add_filter( 'post_password_required', '__return_false' ); 1719 1720 $has_password_filter = true; 1721 } 1722 1723 if ( rest_is_field_included( 'content', $fields ) ) { 1724 $data['content'] = array(); 1725 } 1726 if ( rest_is_field_included( 'content.raw', $fields ) ) { 1727 $data['content']['raw'] = $post->post_content; 1728 } 1729 if ( rest_is_field_included( 'content.rendered', $fields ) ) { 1730 /** This filter is documented in wp-includes/post-template.php */ 1731 $data['content']['rendered'] = post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ); 1732 } 1733 if ( rest_is_field_included( 'content.protected', $fields ) ) { 1734 $data['content']['protected'] = (bool) $post->post_password; 1735 } 1736 if ( rest_is_field_included( 'content.block_version', $fields ) ) { 1737 $data['content']['block_version'] = block_version( $post->post_content ); 1738 } 1739 1740 if ( rest_is_field_included( 'excerpt', $fields ) ) { 1741 /** This filter is documented in wp-includes/post-template.php */ 1742 $excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ); 1743 1744 /** This filter is documented in wp-includes/post-template.php */ 1745 $excerpt = apply_filters( 'the_excerpt', $excerpt ); 1746 1747 $data['excerpt'] = array( 1748 'raw' => $post->post_excerpt, 1749 'rendered' => post_password_required( $post ) ? '' : $excerpt, 1750 'protected' => (bool) $post->post_password, 1751 ); 1752 } 1753 1754 if ( $has_password_filter ) { 1755 // Reset filter. 1756 remove_filter( 'post_password_required', '__return_false' ); 1757 } 1758 1759 if ( rest_is_field_included( 'author', $fields ) ) { 1760 $data['author'] = (int) $post->post_author; 1761 } 1762 1763 if ( rest_is_field_included( 'featured_media', $fields ) ) { 1764 $data['featured_media'] = (int) get_post_thumbnail_id( $post->ID ); 1765 } 1766 1767 if ( rest_is_field_included( 'parent', $fields ) ) { 1768 $data['parent'] = (int) $post->post_parent; 1769 } 1770 1771 if ( rest_is_field_included( 'menu_order', $fields ) ) { 1772 $data['menu_order'] = (int) $post->menu_order; 1773 } 1774 1775 if ( rest_is_field_included( 'comment_status', $fields ) ) { 1776 $data['comment_status'] = $post->comment_status; 1777 } 1778 1779 if ( rest_is_field_included( 'ping_status', $fields ) ) { 1780 $data['ping_status'] = $post->ping_status; 1781 } 1782 1783 if ( rest_is_field_included( 'sticky', $fields ) ) { 1784 $data['sticky'] = is_sticky( $post->ID ); 1785 } 1786 1787 if ( rest_is_field_included( 'template', $fields ) ) { 1788 $template = get_page_template_slug( $post->ID ); 1789 if ( $template ) { 1790 $data['template'] = $template; 1791 } else { 1792 $data['template'] = ''; 1793 } 1794 } 1795 1796 if ( rest_is_field_included( 'format', $fields ) ) { 1797 $data['format'] = get_post_format( $post->ID ); 1798 1799 // Fill in blank post format. 1800 if ( empty( $data['format'] ) ) { 1801 $data['format'] = 'standard'; 1802 } 1803 } 1804 1805 if ( rest_is_field_included( 'meta', $fields ) ) { 1806 $data['meta'] = $this->meta->get_value( $post->ID, $request ); 1807 } 1808 1809 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 1810 1811 foreach ( $taxonomies as $taxonomy ) { 1812 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 1813 1814 if ( rest_is_field_included( $base, $fields ) ) { 1815 $terms = get_the_terms( $post, $taxonomy->name ); 1816 $data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); 1817 } 1818 } 1819 1820 $post_type_obj = get_post_type_object( $post->post_type ); 1821 if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { 1822 $permalink_template_requested = rest_is_field_included( 'permalink_template', $fields ); 1823 $generated_slug_requested = rest_is_field_included( 'generated_slug', $fields ); 1824 1825 if ( $permalink_template_requested || $generated_slug_requested ) { 1826 if ( ! function_exists( 'get_sample_permalink' ) ) { 1827 require_once ABSPATH . 'wp-admin/includes/post.php'; 1828 } 1829 1830 $sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' ); 1831 1832 if ( $permalink_template_requested ) { 1833 $data['permalink_template'] = $sample_permalink[0]; 1834 } 1835 1836 if ( $generated_slug_requested ) { 1837 $data['generated_slug'] = $sample_permalink[1]; 1838 } 1839 } 1840 } 1841 1842 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 1843 $data = $this->add_additional_fields_to_object( $data, $request ); 1844 $data = $this->filter_response_by_context( $data, $context ); 1845 1846 // Wrap the data in a response object. 1847 $response = rest_ensure_response( $data ); 1848 1849 $links = $this->prepare_links( $post ); 1850 $response->add_links( $links ); 1851 1852 if ( ! empty( $links['self']['href'] ) ) { 1853 $actions = $this->get_available_actions( $post, $request ); 1854 1855 $self = $links['self']['href']; 1856 1857 foreach ( $actions as $rel ) { 1858 $response->add_link( $rel, $self ); 1859 } 1860 } 1861 1862 /** 1863 * Filters the post data for a REST API response. 1864 * 1865 * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 1866 * 1867 * Possible filter names include: 1868 * 1869 * - `rest_prepare_post` 1870 * - `rest_prepare_page` 1871 * - `rest_prepare_attachment` 1872 * 1873 * @since 4.7.0 1874 * 1875 * @param WP_REST_Response $response The response object. 1876 * @param WP_Post $post Post object. 1877 * @param WP_REST_Request $request Request object. 1878 */ 1879 return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); 1880 } 1881 1882 /** 1883 * Overwrites the default protected title format. 1884 * 1885 * By default, WordPress will show password protected posts with a title of 1886 * "Protected: %s", as the REST API communicates the protected status of a post 1887 * in a machine readable format, we remove the "Protected: " prefix. 1888 * 1889 * @since 4.7.0 1890 * 1891 * @return string Protected title format. 1892 */ 1893 public function protected_title_format() { 1894 return '%s'; 1895 } 1896 1897 /** 1898 * Prepares links for the request. 1899 * 1900 * @since 4.7.0 1901 * 1902 * @param WP_Post $post Post object. 1903 * @return array Links for the given post. 1904 */ 1905 protected function prepare_links( $post ) { 1906 $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); 1907 1908 // Entity meta. 1909 $links = array( 1910 'self' => array( 1911 'href' => rest_url( trailingslashit( $base ) . $post->ID ), 1912 ), 1913 'collection' => array( 1914 'href' => rest_url( $base ), 1915 ), 1916 'about' => array( 1917 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), 1918 ), 1919 ); 1920 1921 if ( ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'author' ) ) 1922 && ! empty( $post->post_author ) ) { 1923 $links['author'] = array( 1924 'href' => rest_url( 'wp/v2/users/' . $post->post_author ), 1925 'embeddable' => true, 1926 ); 1927 } 1928 1929 if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'comments' ) ) { 1930 $replies_url = rest_url( 'wp/v2/comments' ); 1931 $replies_url = add_query_arg( 'post', $post->ID, $replies_url ); 1932 1933 $links['replies'] = array( 1934 'href' => $replies_url, 1935 'embeddable' => true, 1936 ); 1937 } 1938 1939 if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) { 1940 $revisions = wp_get_post_revisions( $post->ID, array( 'fields' => 'ids' ) ); 1941 $revisions_count = count( $revisions ); 1942 1943 $links['version-history'] = array( 1944 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ), 1945 'count' => $revisions_count, 1946 ); 1947 1948 if ( $revisions_count > 0 ) { 1949 $last_revision = array_shift( $revisions ); 1950 1951 $links['predecessor-version'] = array( 1952 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions/' . $last_revision ), 1953 'id' => $last_revision, 1954 ); 1955 } 1956 } 1957 1958 $post_type_obj = get_post_type_object( $post->post_type ); 1959 1960 if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) { 1961 $links['up'] = array( 1962 'href' => rest_url( trailingslashit( $base ) . (int) $post->post_parent ), 1963 'embeddable' => true, 1964 ); 1965 } 1966 1967 // If we have a featured media, add that. 1968 $featured_media = get_post_thumbnail_id( $post->ID ); 1969 if ( $featured_media ) { 1970 $image_url = rest_url( 'wp/v2/media/' . $featured_media ); 1971 1972 $links['https://api.w.org/featuredmedia'] = array( 1973 'href' => $image_url, 1974 'embeddable' => true, 1975 ); 1976 } 1977 1978 if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) { 1979 $attachments_url = rest_url( 'wp/v2/media' ); 1980 $attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url ); 1981 1982 $links['https://api.w.org/attachment'] = array( 1983 'href' => $attachments_url, 1984 ); 1985 } 1986 1987 $taxonomies = get_object_taxonomies( $post->post_type ); 1988 1989 if ( ! empty( $taxonomies ) ) { 1990 $links['https://api.w.org/term'] = array(); 1991 1992 foreach ( $taxonomies as $tax ) { 1993 $taxonomy_obj = get_taxonomy( $tax ); 1994 1995 // Skip taxonomies that are not public. 1996 if ( empty( $taxonomy_obj->show_in_rest ) ) { 1997 continue; 1998 } 1999 2000 $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax; 2001 2002 $terms_url = add_query_arg( 2003 'post', 2004 $post->ID, 2005 rest_url( 'wp/v2/' . $tax_base ) 2006 ); 2007 2008 $links['https://api.w.org/term'][] = array( 2009 'href' => $terms_url, 2010 'taxonomy' => $tax, 2011 'embeddable' => true, 2012 ); 2013 } 2014 } 2015 2016 return $links; 2017 } 2018 2019 /** 2020 * Get the link relations available for the post and current user. 2021 * 2022 * @since 4.9.8 2023 * 2024 * @param WP_Post $post Post object. 2025 * @param WP_REST_Request $request Request object. 2026 * @return array List of link relations. 2027 */ 2028 protected function get_available_actions( $post, $request ) { 2029 2030 if ( 'edit' !== $request['context'] ) { 2031 return array(); 2032 } 2033 2034 $rels = array(); 2035 2036 $post_type = get_post_type_object( $post->post_type ); 2037 2038 if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) { 2039 $rels[] = 'https://api.w.org/action-publish'; 2040 } 2041 2042 if ( current_user_can( 'unfiltered_html' ) ) { 2043 $rels[] = 'https://api.w.org/action-unfiltered-html'; 2044 } 2045 2046 if ( 'post' === $post_type->name ) { 2047 if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) { 2048 $rels[] = 'https://api.w.org/action-sticky'; 2049 } 2050 } 2051 2052 if ( post_type_supports( $post_type->name, 'author' ) ) { 2053 if ( current_user_can( $post_type->cap->edit_others_posts ) ) { 2054 $rels[] = 'https://api.w.org/action-assign-author'; 2055 } 2056 } 2057 2058 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 2059 2060 foreach ( $taxonomies as $tax ) { 2061 $tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; 2062 $create_cap = is_taxonomy_hierarchical( $tax->name ) ? $tax->cap->edit_terms : $tax->cap->assign_terms; 2063 2064 if ( current_user_can( $create_cap ) ) { 2065 $rels[] = 'https://api.w.org/action-create-' . $tax_base; 2066 } 2067 2068 if ( current_user_can( $tax->cap->assign_terms ) ) { 2069 $rels[] = 'https://api.w.org/action-assign-' . $tax_base; 2070 } 2071 } 2072 2073 return $rels; 2074 } 2075 2076 /** 2077 * Retrieves the post's schema, conforming to JSON Schema. 2078 * 2079 * @since 4.7.0 2080 * 2081 * @return array Item schema data. 2082 */ 2083 public function get_item_schema() { 2084 if ( $this->schema ) { 2085 return $this->add_additional_fields_schema( $this->schema ); 2086 } 2087 2088 $schema = array( 2089 '$schema' => 'http://json-schema.org/draft-04/schema#', 2090 'title' => $this->post_type, 2091 'type' => 'object', 2092 // Base properties for every Post. 2093 'properties' => array( 2094 'date' => array( 2095 'description' => __( "The date the object was published, in the site's timezone." ), 2096 'type' => array( 'string', 'null' ), 2097 'format' => 'date-time', 2098 'context' => array( 'view', 'edit', 'embed' ), 2099 ), 2100 'date_gmt' => array( 2101 'description' => __( 'The date the object was published, as GMT.' ), 2102 'type' => array( 'string', 'null' ), 2103 'format' => 'date-time', 2104 'context' => array( 'view', 'edit' ), 2105 ), 2106 'guid' => array( 2107 'description' => __( 'The globally unique identifier for the object.' ), 2108 'type' => 'object', 2109 'context' => array( 'view', 'edit' ), 2110 'readonly' => true, 2111 'properties' => array( 2112 'raw' => array( 2113 'description' => __( 'GUID for the object, as it exists in the database.' ), 2114 'type' => 'string', 2115 'context' => array( 'edit' ), 2116 'readonly' => true, 2117 ), 2118 'rendered' => array( 2119 'description' => __( 'GUID for the object, transformed for display.' ), 2120 'type' => 'string', 2121 'context' => array( 'view', 'edit' ), 2122 'readonly' => true, 2123 ), 2124 ), 2125 ), 2126 'id' => array( 2127 'description' => __( 'Unique identifier for the object.' ), 2128 'type' => 'integer', 2129 'context' => array( 'view', 'edit', 'embed' ), 2130 'readonly' => true, 2131 ), 2132 'link' => array( 2133 'description' => __( 'URL to the object.' ), 2134 'type' => 'string', 2135 'format' => 'uri', 2136 'context' => array( 'view', 'edit', 'embed' ), 2137 'readonly' => true, 2138 ), 2139 'modified' => array( 2140 'description' => __( "The date the object was last modified, in the site's timezone." ), 2141 'type' => 'string', 2142 'format' => 'date-time', 2143 'context' => array( 'view', 'edit' ), 2144 'readonly' => true, 2145 ), 2146 'modified_gmt' => array( 2147 'description' => __( 'The date the object was last modified, as GMT.' ), 2148 'type' => 'string', 2149 'format' => 'date-time', 2150 'context' => array( 'view', 'edit' ), 2151 'readonly' => true, 2152 ), 2153 'slug' => array( 2154 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), 2155 'type' => 'string', 2156 'context' => array( 'view', 'edit', 'embed' ), 2157 'arg_options' => array( 2158 'sanitize_callback' => array( $this, 'sanitize_slug' ), 2159 ), 2160 ), 2161 'status' => array( 2162 'description' => __( 'A named status for the object.' ), 2163 'type' => 'string', 2164 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 2165 'context' => array( 'view', 'edit' ), 2166 'arg_options' => array( 2167 'validate_callback' => array( $this, 'check_status' ), 2168 ), 2169 ), 2170 'type' => array( 2171 'description' => __( 'Type of Post for the object.' ), 2172 'type' => 'string', 2173 'context' => array( 'view', 'edit', 'embed' ), 2174 'readonly' => true, 2175 ), 2176 'password' => array( 2177 'description' => __( 'A password to protect access to the content and excerpt.' ), 2178 'type' => 'string', 2179 'context' => array( 'edit' ), 2180 ), 2181 ), 2182 ); 2183 2184 $post_type_obj = get_post_type_object( $this->post_type ); 2185 if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { 2186 $schema['properties']['permalink_template'] = array( 2187 'description' => __( 'Permalink template for the object.' ), 2188 'type' => 'string', 2189 'context' => array( 'edit' ), 2190 'readonly' => true, 2191 ); 2192 2193 $schema['properties']['generated_slug'] = array( 2194 'description' => __( 'Slug automatically generated from the object title.' ), 2195 'type' => 'string', 2196 'context' => array( 'edit' ), 2197 'readonly' => true, 2198 ); 2199 } 2200 2201 if ( $post_type_obj->hierarchical ) { 2202 $schema['properties']['parent'] = array( 2203 'description' => __( 'The ID for the parent of the object.' ), 2204 'type' => 'integer', 2205 'context' => array( 'view', 'edit' ), 2206 ); 2207 } 2208 2209 $post_type_attributes = array( 2210 'title', 2211 'editor', 2212 'author', 2213 'excerpt', 2214 'thumbnail', 2215 'comments', 2216 'revisions', 2217 'page-attributes', 2218 'post-formats', 2219 'custom-fields', 2220 ); 2221 $fixed_schemas = array( 2222 'post' => array( 2223 'title', 2224 'editor', 2225 'author', 2226 'excerpt', 2227 'thumbnail', 2228 'comments', 2229 'revisions', 2230 'post-formats', 2231 'custom-fields', 2232 ), 2233 'page' => array( 2234 'title', 2235 'editor', 2236 'author', 2237 'excerpt', 2238 'thumbnail', 2239 'comments', 2240 'revisions', 2241 'page-attributes', 2242 'custom-fields', 2243 ), 2244 'attachment' => array( 2245 'title', 2246 'author', 2247 'comments', 2248 'revisions', 2249 'custom-fields', 2250 ), 2251 ); 2252 2253 foreach ( $post_type_attributes as $attribute ) { 2254 if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) { 2255 continue; 2256 } elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) { 2257 continue; 2258 } 2259 2260 switch ( $attribute ) { 2261 2262 case 'title': 2263 $schema['properties']['title'] = array( 2264 'description' => __( 'The title for the object.' ), 2265 'type' => 'object', 2266 'context' => array( 'view', 'edit', 'embed' ), 2267 'arg_options' => array( 2268 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 2269 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). 2270 ), 2271 'properties' => array( 2272 'raw' => array( 2273 'description' => __( 'Title for the object, as it exists in the database.' ), 2274 'type' => 'string', 2275 'context' => array( 'edit' ), 2276 ), 2277 'rendered' => array( 2278 'description' => __( 'HTML title for the object, transformed for display.' ), 2279 'type' => 'string', 2280 'context' => array( 'view', 'edit', 'embed' ), 2281 'readonly' => true, 2282 ), 2283 ), 2284 ); 2285 break; 2286 2287 case 'editor': 2288 $schema['properties']['content'] = array( 2289 'description' => __( 'The content for the object.' ), 2290 'type' => 'object', 2291 'context' => array( 'view', 'edit' ), 2292 'arg_options' => array( 2293 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 2294 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). 2295 ), 2296 'properties' => array( 2297 'raw' => array( 2298 'description' => __( 'Content for the object, as it exists in the database.' ), 2299 'type' => 'string', 2300 'context' => array( 'edit' ), 2301 ), 2302 'rendered' => array( 2303 'description' => __( 'HTML content for the object, transformed for display.' ), 2304 'type' => 'string', 2305 'context' => array( 'view', 'edit' ), 2306 'readonly' => true, 2307 ), 2308 'block_version' => array( 2309 'description' => __( 'Version of the content block format used by the object.' ), 2310 'type' => 'integer', 2311 'context' => array( 'edit' ), 2312 'readonly' => true, 2313 ), 2314 'protected' => array( 2315 'description' => __( 'Whether the content is protected with a password.' ), 2316 'type' => 'boolean', 2317 'context' => array( 'view', 'edit', 'embed' ), 2318 'readonly' => true, 2319 ), 2320 ), 2321 ); 2322 break; 2323 2324 case 'author': 2325 $schema['properties']['author'] = array( 2326 'description' => __( 'The ID for the author of the object.' ), 2327 'type' => 'integer', 2328 'context' => array( 'view', 'edit', 'embed' ), 2329 ); 2330 break; 2331 2332 case 'excerpt': 2333 $schema['properties']['excerpt'] = array( 2334 'description' => __( 'The excerpt for the object.' ), 2335 'type' => 'object', 2336 'context' => array( 'view', 'edit', 'embed' ), 2337 'arg_options' => array( 2338 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 2339 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). 2340 ), 2341 'properties' => array( 2342 'raw' => array( 2343 'description' => __( 'Excerpt for the object, as it exists in the database.' ), 2344 'type' => 'string', 2345 'context' => array( 'edit' ), 2346 ), 2347 'rendered' => array( 2348 'description' => __( 'HTML excerpt for the object, transformed for display.' ), 2349 'type' => 'string', 2350 'context' => array( 'view', 'edit', 'embed' ), 2351 'readonly' => true, 2352 ), 2353 'protected' => array( 2354 'description' => __( 'Whether the excerpt is protected with a password.' ), 2355 'type' => 'boolean', 2356 'context' => array( 'view', 'edit', 'embed' ), 2357 'readonly' => true, 2358 ), 2359 ), 2360 ); 2361 break; 2362 2363 case 'thumbnail': 2364 $schema['properties']['featured_media'] = array( 2365 'description' => __( 'The ID of the featured media for the object.' ), 2366 'type' => 'integer', 2367 'context' => array( 'view', 'edit', 'embed' ), 2368 ); 2369 break; 2370 2371 case 'comments': 2372 $schema['properties']['comment_status'] = array( 2373 'description' => __( 'Whether or not comments are open on the object.' ), 2374 'type' => 'string', 2375 'enum' => array( 'open', 'closed' ), 2376 'context' => array( 'view', 'edit' ), 2377 ); 2378 $schema['properties']['ping_status'] = array( 2379 'description' => __( 'Whether or not the object can be pinged.' ), 2380 'type' => 'string', 2381 'enum' => array( 'open', 'closed' ), 2382 'context' => array( 'view', 'edit' ), 2383 ); 2384 break; 2385 2386 case 'page-attributes': 2387 $schema['properties']['menu_order'] = array( 2388 'description' => __( 'The order of the object in relation to other object of its type.' ), 2389 'type' => 'integer', 2390 'context' => array( 'view', 'edit' ), 2391 ); 2392 break; 2393 2394 case 'post-formats': 2395 // Get the native post formats and remove the array keys. 2396 $formats = array_values( get_post_format_slugs() ); 2397 2398 $schema['properties']['format'] = array( 2399 'description' => __( 'The format for the object.' ), 2400 'type' => 'string', 2401 'enum' => $formats, 2402 'context' => array( 'view', 'edit' ), 2403 ); 2404 break; 2405 2406 case 'custom-fields': 2407 $schema['properties']['meta'] = $this->meta->get_field_schema(); 2408 break; 2409 2410 } 2411 } 2412 2413 if ( 'post' === $this->post_type ) { 2414 $schema['properties']['sticky'] = array( 2415 'description' => __( 'Whether or not the object should be treated as sticky.' ), 2416 'type' => 'boolean', 2417 'context' => array( 'view', 'edit' ), 2418 ); 2419 } 2420 2421 $schema['properties']['template'] = array( 2422 'description' => __( 'The theme file to use to display the object.' ), 2423 'type' => 'string', 2424 'context' => array( 'view', 'edit' ), 2425 'arg_options' => array( 2426 'validate_callback' => array( $this, 'check_template' ), 2427 ), 2428 ); 2429 2430 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 2431 2432 foreach ( $taxonomies as $taxonomy ) { 2433 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 2434 2435 if ( array_key_exists( $base, $schema['properties'] ) ) { 2436 $taxonomy_field_name_with_conflict = ! empty( $taxonomy->rest_base ) ? 'rest_base' : 'name'; 2437 _doing_it_wrong( 2438 'register_taxonomy', 2439 sprintf( 2440 /* translators: 1: The taxonomy name, 2: The property name, either 'rest_base' or 'name', 3: The conflicting value. */ 2441 __( 'The "%1$s" taxonomy "%2$s" property (%3$s) conflicts with an existing property on the REST API Posts Controller. Specify a custom "rest_base" when registering the taxonomy to avoid this error.' ), 2442 $taxonomy->name, 2443 $taxonomy_field_name_with_conflict, 2444 $base 2445 ), 2446 '5.4.0' 2447 ); 2448 } 2449 2450 $schema['properties'][ $base ] = array( 2451 /* translators: %s: Taxonomy name. */ 2452 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ), 2453 'type' => 'array', 2454 'items' => array( 2455 'type' => 'integer', 2456 ), 2457 'context' => array( 'view', 'edit' ), 2458 ); 2459 } 2460 2461 $schema_links = $this->get_schema_links(); 2462 2463 if ( $schema_links ) { 2464 $schema['links'] = $schema_links; 2465 } 2466 2467 // Take a snapshot of which fields are in the schema pre-filtering. 2468 $schema_fields = array_keys( $schema['properties'] ); 2469 2470 /** 2471 * Filters the post's schema. 2472 * 2473 * The dynamic portion of the filter, `$this->post_type`, refers to the 2474 * post type slug for the controller. 2475 * 2476 * @since 5.4.0 2477 * 2478 * @param array $schema Item schema data. 2479 */ 2480 $schema = apply_filters( "rest_{$this->post_type}_item_schema", $schema ); 2481 2482 // Emit a _doing_it_wrong warning if user tries to add new properties using this filter. 2483 $new_fields = array_diff( array_keys( $schema['properties'] ), $schema_fields ); 2484 if ( count( $new_fields ) > 0 ) { 2485 _doing_it_wrong( 2486 __METHOD__, 2487 sprintf( 2488 /* translators: %s: register_rest_field */ 2489 __( 'Please use %s to add new schema properties.' ), 2490 'register_rest_field' 2491 ), 2492 '5.4.0' 2493 ); 2494 } 2495 2496 $this->schema = $schema; 2497 2498 return $this->add_additional_fields_schema( $this->schema ); 2499 } 2500 2501 /** 2502 * Retrieve Link Description Objects that should be added to the Schema for the posts collection. 2503 * 2504 * @since 4.9.8 2505 * 2506 * @return array 2507 */ 2508 protected function get_schema_links() { 2509 2510 $href = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" ); 2511 2512 $links = array(); 2513 2514 if ( 'attachment' !== $this->post_type ) { 2515 $links[] = array( 2516 'rel' => 'https://api.w.org/action-publish', 2517 'title' => __( 'The current user can publish this post.' ), 2518 'href' => $href, 2519 'targetSchema' => array( 2520 'type' => 'object', 2521 'properties' => array( 2522 'status' => array( 2523 'type' => 'string', 2524 'enum' => array( 'publish', 'future' ), 2525 ), 2526 ), 2527 ), 2528 ); 2529 } 2530 2531 $links[] = array( 2532 'rel' => 'https://api.w.org/action-unfiltered-html', 2533 'title' => __( 'The current user can post unfiltered HTML markup and JavaScript.' ), 2534 'href' => $href, 2535 'targetSchema' => array( 2536 'type' => 'object', 2537 'properties' => array( 2538 'content' => array( 2539 'raw' => array( 2540 'type' => 'string', 2541 ), 2542 ), 2543 ), 2544 ), 2545 ); 2546 2547 if ( 'post' === $this->post_type ) { 2548 $links[] = array( 2549 'rel' => 'https://api.w.org/action-sticky', 2550 'title' => __( 'The current user can sticky this post.' ), 2551 'href' => $href, 2552 'targetSchema' => array( 2553 'type' => 'object', 2554 'properties' => array( 2555 'sticky' => array( 2556 'type' => 'boolean', 2557 ), 2558 ), 2559 ), 2560 ); 2561 } 2562 2563 if ( post_type_supports( $this->post_type, 'author' ) ) { 2564 $links[] = array( 2565 'rel' => 'https://api.w.org/action-assign-author', 2566 'title' => __( 'The current user can change the author on this post.' ), 2567 'href' => $href, 2568 'targetSchema' => array( 2569 'type' => 'object', 2570 'properties' => array( 2571 'author' => array( 2572 'type' => 'integer', 2573 ), 2574 ), 2575 ), 2576 ); 2577 } 2578 2579 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 2580 2581 foreach ( $taxonomies as $tax ) { 2582 $tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; 2583 2584 /* translators: %s: Taxonomy name. */ 2585 $assign_title = sprintf( __( 'The current user can assign terms in the %s taxonomy.' ), $tax->name ); 2586 /* translators: %s: Taxonomy name. */ 2587 $create_title = sprintf( __( 'The current user can create terms in the %s taxonomy.' ), $tax->name ); 2588 2589 $links[] = array( 2590 'rel' => 'https://api.w.org/action-assign-' . $tax_base, 2591 'title' => $assign_title, 2592 'href' => $href, 2593 'targetSchema' => array( 2594 'type' => 'object', 2595 'properties' => array( 2596 $tax_base => array( 2597 'type' => 'array', 2598 'items' => array( 2599 'type' => 'integer', 2600 ), 2601 ), 2602 ), 2603 ), 2604 ); 2605 2606 $links[] = array( 2607 'rel' => 'https://api.w.org/action-create-' . $tax_base, 2608 'title' => $create_title, 2609 'href' => $href, 2610 'targetSchema' => array( 2611 'type' => 'object', 2612 'properties' => array( 2613 $tax_base => array( 2614 'type' => 'array', 2615 'items' => array( 2616 'type' => 'integer', 2617 ), 2618 ), 2619 ), 2620 ), 2621 ); 2622 } 2623 2624 return $links; 2625 } 2626 2627 /** 2628 * Retrieves the query params for the posts collection. 2629 * 2630 * @since 4.7.0 2631 * 2632 * @return array Collection parameters. 2633 */ 2634 public function get_collection_params() { 2635 $query_params = parent::get_collection_params(); 2636 2637 $query_params['context']['default'] = 'view'; 2638 2639 $query_params['after'] = array( 2640 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ), 2641 'type' => 'string', 2642 'format' => 'date-time', 2643 ); 2644 2645 if ( post_type_supports( $this->post_type, 'author' ) ) { 2646 $query_params['author'] = array( 2647 'description' => __( 'Limit result set to posts assigned to specific authors.' ), 2648 'type' => 'array', 2649 'items' => array( 2650 'type' => 'integer', 2651 ), 2652 'default' => array(), 2653 ); 2654 $query_params['author_exclude'] = array( 2655 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), 2656 'type' => 'array', 2657 'items' => array( 2658 'type' => 'integer', 2659 ), 2660 'default' => array(), 2661 ); 2662 } 2663 2664 $query_params['before'] = array( 2665 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ), 2666 'type' => 'string', 2667 'format' => 'date-time', 2668 ); 2669 2670 $query_params['exclude'] = array( 2671 'description' => __( 'Ensure result set excludes specific IDs.' ), 2672 'type' => 'array', 2673 'items' => array( 2674 'type' => 'integer', 2675 ), 2676 'default' => array(), 2677 ); 2678 2679 $query_params['include'] = array( 2680 'description' => __( 'Limit result set to specific IDs.' ), 2681 'type' => 'array', 2682 'items' => array( 2683 'type' => 'integer', 2684 ), 2685 'default' => array(), 2686 ); 2687 2688 if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { 2689 $query_params['menu_order'] = array( 2690 'description' => __( 'Limit result set to posts with a specific menu_order value.' ), 2691 'type' => 'integer', 2692 ); 2693 } 2694 2695 $query_params['offset'] = array( 2696 'description' => __( 'Offset the result set by a specific number of items.' ), 2697 'type' => 'integer', 2698 ); 2699 2700 $query_params['order'] = array( 2701 'description' => __( 'Order sort attribute ascending or descending.' ), 2702 'type' => 'string', 2703 'default' => 'desc', 2704 'enum' => array( 'asc', 'desc' ), 2705 ); 2706 2707 $query_params['orderby'] = array( 2708 'description' => __( 'Sort collection by object attribute.' ), 2709 'type' => 'string', 2710 'default' => 'date', 2711 'enum' => array( 2712 'author', 2713 'date', 2714 'id', 2715 'include', 2716 'modified', 2717 'parent', 2718 'relevance', 2719 'slug', 2720 'include_slugs', 2721 'title', 2722 ), 2723 ); 2724 2725 if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { 2726 $query_params['orderby']['enum'][] = 'menu_order'; 2727 } 2728 2729 $post_type = get_post_type_object( $this->post_type ); 2730 2731 if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { 2732 $query_params['parent'] = array( 2733 'description' => __( 'Limit result set to items with particular parent IDs.' ), 2734 'type' => 'array', 2735 'items' => array( 2736 'type' => 'integer', 2737 ), 2738 'default' => array(), 2739 ); 2740 $query_params['parent_exclude'] = array( 2741 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ), 2742 'type' => 'array', 2743 'items' => array( 2744 'type' => 'integer', 2745 ), 2746 'default' => array(), 2747 ); 2748 } 2749 2750 $query_params['slug'] = array( 2751 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), 2752 'type' => 'array', 2753 'items' => array( 2754 'type' => 'string', 2755 ), 2756 'sanitize_callback' => 'wp_parse_slug_list', 2757 ); 2758 2759 $query_params['status'] = array( 2760 'default' => 'publish', 2761 'description' => __( 'Limit result set to posts assigned one or more statuses.' ), 2762 'type' => 'array', 2763 'items' => array( 2764 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), 2765 'type' => 'string', 2766 ), 2767 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ), 2768 ); 2769 2770 $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 2771 2772 if ( ! empty( $taxonomies ) ) { 2773 $query_params['tax_relation'] = array( 2774 'description' => __( 'Limit result set based on relationship between multiple taxonomies.' ), 2775 'type' => 'string', 2776 'enum' => array( 'AND', 'OR' ), 2777 ); 2778 } 2779 2780 foreach ( $taxonomies as $taxonomy ) { 2781 $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 2782 2783 $query_params[ $base ] = array( 2784 /* translators: %s: Taxonomy name. */ 2785 'description' => sprintf( __( 'Limit result set to all items that have the specified term assigned in the %s taxonomy.' ), $base ), 2786 'type' => 'array', 2787 'items' => array( 2788 'type' => 'integer', 2789 ), 2790 'default' => array(), 2791 ); 2792 2793 $query_params[ $base . '_exclude' ] = array( 2794 /* translators: %s: Taxonomy name. */ 2795 'description' => sprintf( __( 'Limit result set to all items except those that have the specified term assigned in the %s taxonomy.' ), $base ), 2796 'type' => 'array', 2797 'items' => array( 2798 'type' => 'integer', 2799 ), 2800 'default' => array(), 2801 ); 2802 } 2803 2804 if ( 'post' === $this->post_type ) { 2805 $query_params['sticky'] = array( 2806 'description' => __( 'Limit result set to items that are sticky.' ), 2807 'type' => 'boolean', 2808 ); 2809 } 2810 2811 /** 2812 * Filters collection parameters for the posts controller. 2813 * 2814 * The dynamic part of the filter `$this->post_type` refers to the post 2815 * type slug for the controller. 2816 * 2817 * This filter registers the collection parameter, but does not map the 2818 * collection parameter to an internal WP_Query parameter. Use the 2819 * `rest_{$this->post_type}_query` filter to set WP_Query parameters. 2820 * 2821 * @since 4.7.0 2822 * 2823 * @param array $query_params JSON Schema-formatted collection parameters. 2824 * @param WP_Post_Type $post_type Post type object. 2825 */ 2826 return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); 2827 } 2828 2829 /** 2830 * Sanitizes and validates the list of post statuses, including whether the 2831 * user can query private statuses. 2832 * 2833 * @since 4.7.0 2834 * 2835 * @param string|array $statuses One or more post statuses. 2836 * @param WP_REST_Request $request Full details about the request. 2837 * @param string $parameter Additional parameter to pass to validation. 2838 * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. 2839 */ 2840 public function sanitize_post_statuses( $statuses, $request, $parameter ) { 2841 $statuses = wp_parse_slug_list( $statuses ); 2842 2843 // The default status is different in WP_REST_Attachments_Controller. 2844 $attributes = $request->get_attributes(); 2845 $default_status = $attributes['args']['status']['default']; 2846 2847 foreach ( $statuses as $status ) { 2848 if ( $status === $default_status ) { 2849 continue; 2850 } 2851 2852 $post_type_obj = get_post_type_object( $this->post_type ); 2853 2854 if ( current_user_can( $post_type_obj->cap->edit_posts ) || 'private' === $status && current_user_can( $post_type_obj->cap->read_private_posts ) ) { 2855 $result = rest_validate_request_arg( $status, $request, $parameter ); 2856 if ( is_wp_error( $result ) ) { 2857 return $result; 2858 } 2859 } else { 2860 return new WP_Error( 2861 'rest_forbidden_status', 2862 __( 'Status is forbidden.' ), 2863 array( 'status' => rest_authorization_required_code() ) 2864 ); 2865 } 2866 } 2867 2868 return $statuses; 2869 } 2870 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Mon Jan 18 08:20:02 2021 | Cross-referenced by PHPXref |