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


Generated: Thu Mar 4 01:00:04 2021 Cross-referenced by PHPXref 0.7.1