[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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


Generated : Mon Jan 18 08:20:02 2021 Cross-referenced by PHPXref