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


Generated: Thu Jul 16 01:00:03 2020 Cross-referenced by PHPXref 0.7.1