[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-posts-controller.php (source)

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


Generated: Mon Nov 18 01:00:04 2019 Cross-referenced by PHPXref 0.7.1