WP_REST_Posts_Controller

Core class to access posts via the REST API.

Defined (1)

The class is defined in the following location(s).

/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php  
  1. class WP_REST_Posts_Controller extends WP_REST_Controller { 
  2.  
  3. /** 
  4. * Post type. 
  5. * @since 4.7.0 
  6. * @access protected 
  7. * @var string 
  8. */ 
  9. protected $post_type; 
  10.  
  11. /** 
  12. * Instance of a post meta fields object. 
  13. * @since 4.7.0 
  14. * @access protected 
  15. * @var WP_REST_Post_Meta_Fields 
  16. */ 
  17. protected $meta; 
  18.  
  19. /** 
  20. * Constructor. 
  21. * @since 4.7.0 
  22. * @access public 
  23. * @param string $post_type Post type. 
  24. */ 
  25. public function __construct( $post_type ) { 
  26. $this->post_type = $post_type; 
  27. $this->namespace = 'wp/v2'; 
  28. $obj = get_post_type_object( $post_type ); 
  29. $this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; 
  30.  
  31. $this->meta = new WP_REST_Post_Meta_Fields( $this->post_type ); 
  32.  
  33. /** 
  34. * Registers the routes for the objects of the controller. 
  35. * @since 4.7.0 
  36. * @access public 
  37. * @see register_rest_route() 
  38. */ 
  39. public function register_routes() { 
  40.  
  41. register_rest_route( $this->namespace, '/' . $this->rest_base, array( 
  42. array( 
  43. 'methods' => WP_REST_Server::READABLE,  
  44. 'callback' => array( $this, 'get_items' ),  
  45. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  46. 'args' => $this->get_collection_params(),  
  47. ),  
  48. array( 
  49. 'methods' => WP_REST_Server::CREATABLE,  
  50. 'callback' => array( $this, 'create_item' ),  
  51. 'permission_callback' => array( $this, 'create_item_permissions_check' ),  
  52. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),  
  53. ),  
  54. 'schema' => array( $this, 'get_public_item_schema' ),  
  55. ) ); 
  56.  
  57. $schema = $this->get_item_schema(); 
  58. $get_item_args = array( 
  59. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  60. ); 
  61. if ( isset( $schema['properties']['password'] ) ) { 
  62. $get_item_args['password'] = array( 
  63. 'description' => __( 'The password for the post if it is password protected.' ),  
  64. 'type' => 'string',  
  65. ); 
  66. register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  67. 'args' => array( 
  68. 'id' => array( 
  69. 'description' => __( 'Unique identifier for the object.' ),  
  70. 'type' => 'integer',  
  71. ),  
  72. ),  
  73. array( 
  74. 'methods' => WP_REST_Server::READABLE,  
  75. 'callback' => array( $this, 'get_item' ),  
  76. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  77. 'args' => $get_item_args,  
  78. ),  
  79. array( 
  80. 'methods' => WP_REST_Server::EDITABLE,  
  81. 'callback' => array( $this, 'update_item' ),  
  82. 'permission_callback' => array( $this, 'update_item_permissions_check' ),  
  83. 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),  
  84. ),  
  85. array( 
  86. 'methods' => WP_REST_Server::DELETABLE,  
  87. 'callback' => array( $this, 'delete_item' ),  
  88. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  89. 'args' => array( 
  90. 'force' => array( 
  91. 'type' => 'boolean',  
  92. 'default' => false,  
  93. 'description' => __( 'Whether to bypass trash and force deletion.' ),  
  94. ),  
  95. ),  
  96. ),  
  97. 'schema' => array( $this, 'get_public_item_schema' ),  
  98. ) ); 
  99.  
  100. /** 
  101. * Checks if a given request has access to read posts. 
  102. * @since 4.7.0 
  103. * @access public 
  104. * @param WP_REST_Request $request Full details about the request. 
  105. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 
  106. */ 
  107. public function get_items_permissions_check( $request ) { 
  108.  
  109. $post_type = get_post_type_object( $this->post_type ); 
  110.  
  111. if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) { 
  112. return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); 
  113.  
  114. return true; 
  115.  
  116. /** 
  117. * Retrieves a collection of posts. 
  118. * @since 4.7.0 
  119. * @access public 
  120. * @param WP_REST_Request $request Full details about the request. 
  121. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  122. */ 
  123. public function get_items( $request ) { 
  124.  
  125. // Ensure a search string is set in case the orderby is set to 'relevance'. 
  126. if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) { 
  127. return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) ); 
  128.  
  129. // Ensure an include parameter is set in case the orderby is set to 'include'. 
  130. if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) { 
  131. return new WP_Error( 'rest_orderby_include_missing_include', sprintf( __( 'Missing parameter(s): %s' ), 'include' ), array( 'status' => 400 ) ); 
  132.  
  133. // Retrieve the list of registered collection query parameters. 
  134. $registered = $this->get_collection_params(); 
  135. $args = array(); 
  136.  
  137. /** 
  138. * This array defines mappings between public API query parameters whose 
  139. * values are accepted as-passed, and their internal WP_Query parameter 
  140. * name equivalents (some are the same). Only values which are also 
  141. * present in $registered will be set. 
  142. */ 
  143. $parameter_mappings = array( 
  144. 'author' => 'author__in',  
  145. 'author_exclude' => 'author__not_in',  
  146. 'exclude' => 'post__not_in',  
  147. 'include' => 'post__in',  
  148. 'menu_order' => 'menu_order',  
  149. 'offset' => 'offset',  
  150. 'order' => 'order',  
  151. 'orderby' => 'orderby',  
  152. 'page' => 'paged',  
  153. 'parent' => 'post_parent__in',  
  154. 'parent_exclude' => 'post_parent__not_in',  
  155. 'search' => 's',  
  156. 'slug' => 'post_name__in',  
  157. 'status' => 'post_status',  
  158. ); 
  159.  
  160. /** 
  161. * For each known parameter which is both registered and present in the request,  
  162. * set the parameter's value on the query $args. 
  163. */ 
  164. foreach ( $parameter_mappings as $api_param => $wp_param ) { 
  165. if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) { 
  166. $args[ $wp_param ] = $request[ $api_param ]; 
  167.  
  168. // Check for & assign any parameters which require special handling or setting. 
  169. $args['date_query'] = array(); 
  170.  
  171. // Set before into date query. Date query must be specified as an array of an array. 
  172. if ( isset( $registered['before'], $request['before'] ) ) { 
  173. $args['date_query'][0]['before'] = $request['before']; 
  174.  
  175. // Set after into date query. Date query must be specified as an array of an array. 
  176. if ( isset( $registered['after'], $request['after'] ) ) { 
  177. $args['date_query'][0]['after'] = $request['after']; 
  178.  
  179. // Ensure our per_page parameter overrides any provided posts_per_page filter. 
  180. if ( isset( $registered['per_page'] ) ) { 
  181. $args['posts_per_page'] = $request['per_page']; 
  182.  
  183. if ( isset( $registered['sticky'], $request['sticky'] ) ) { 
  184. $sticky_posts = get_option( 'sticky_posts', array() ); 
  185. if ( ! is_array( $sticky_posts ) ) { 
  186. $sticky_posts = array(); 
  187. if ( $request['sticky'] ) { 
  188. /** 
  189. * As post__in will be used to only get sticky posts,  
  190. * we have to support the case where post__in was already 
  191. * specified. 
  192. */ 
  193. $args['post__in'] = $args['post__in'] ? array_intersect( $sticky_posts, $args['post__in'] ) : $sticky_posts; 
  194.  
  195. /** 
  196. * If we intersected, but there are no post ids in common,  
  197. * WP_Query won't return "no posts" for post__in = array() 
  198. * so we have to fake it a bit. 
  199. */ 
  200. if ( ! $args['post__in'] ) { 
  201. $args['post__in'] = array( 0 ); 
  202. } elseif ( $sticky_posts ) { 
  203. /** 
  204. * As post___not_in will be used to only get posts that 
  205. * are not sticky, we have to support the case where post__not_in 
  206. * was already specified. 
  207. */ 
  208. $args['post__not_in'] = array_merge( $args['post__not_in'], $sticky_posts ); 
  209.  
  210. // Force the post_type argument, since it's not a user input variable. 
  211. $args['post_type'] = $this->post_type; 
  212.  
  213. /** 
  214. * Filters the query arguments for a request. 
  215. * Enables adding extra arguments or setting defaults for a post collection request. 
  216. * @since 4.7.0 
  217. * @link https://developer.wordpress.org/reference/classes/wp_query/ 
  218. * @param array $args Key value array of query var to query value. 
  219. * @param WP_REST_Request $request The request used. 
  220. */ 
  221. $args = apply_filters( "rest_{$this->post_type}_query", $args, $request ); 
  222. $query_args = $this->prepare_items_query( $args, $request ); 
  223.  
  224. $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 
  225.  
  226. foreach ( $taxonomies as $taxonomy ) { 
  227. $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 
  228. $tax_exclude = $base . '_exclude'; 
  229.  
  230. if ( ! empty( $request[ $base ] ) ) { 
  231. $query_args['tax_query'][] = array( 
  232. 'taxonomy' => $taxonomy->name,  
  233. 'field' => 'term_id',  
  234. 'terms' => $request[ $base ],  
  235. 'include_children' => false,  
  236. ); 
  237.  
  238. if ( ! empty( $request[ $tax_exclude ] ) ) { 
  239. $query_args['tax_query'][] = array( 
  240. 'taxonomy' => $taxonomy->name,  
  241. 'field' => 'term_id',  
  242. 'terms' => $request[ $tax_exclude ],  
  243. 'include_children' => false,  
  244. 'operator' => 'NOT IN',  
  245. ); 
  246.  
  247. $posts_query = new WP_Query(); 
  248. $query_result = $posts_query->query( $query_args ); 
  249.  
  250. // Allow access to all password protected posts if the context is edit. 
  251. if ( 'edit' === $request['context'] ) { 
  252. add_filter( 'post_password_required', '__return_false' ); 
  253.  
  254. $posts = array(); 
  255.  
  256. foreach ( $query_result as $post ) { 
  257. if ( ! $this->check_read_permission( $post ) ) { 
  258. continue; 
  259.  
  260. $data = $this->prepare_item_for_response( $post, $request ); 
  261. $posts[] = $this->prepare_response_for_collection( $data ); 
  262.  
  263. // Reset filter. 
  264. if ( 'edit' === $request['context'] ) { 
  265. remove_filter( 'post_password_required', '__return_false' ); 
  266.  
  267. $page = (int) $query_args['paged']; 
  268. $total_posts = $posts_query->found_posts; 
  269.  
  270. if ( $total_posts < 1 ) { 
  271. // Out-of-bounds, run the query again without LIMIT for total count. 
  272. unset( $query_args['paged'] ); 
  273.  
  274. $count_query = new WP_Query(); 
  275. $count_query->query( $query_args ); 
  276. $total_posts = $count_query->found_posts; 
  277.  
  278. $max_pages = ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); 
  279. $response = rest_ensure_response( $posts ); 
  280.  
  281. $response->header( 'X-WP-Total', (int) $total_posts ); 
  282. $response->header( 'X-WP-TotalPages', (int) $max_pages ); 
  283.  
  284. $request_params = $request->get_query_params(); 
  285. $base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); 
  286.  
  287. if ( $page > 1 ) { 
  288. $prev_page = $page - 1; 
  289.  
  290. if ( $prev_page > $max_pages ) { 
  291. $prev_page = $max_pages; 
  292.  
  293. $prev_link = add_query_arg( 'page', $prev_page, $base ); 
  294. $response->link_header( 'prev', $prev_link ); 
  295. if ( $max_pages > $page ) { 
  296. $next_page = $page + 1; 
  297. $next_link = add_query_arg( 'page', $next_page, $base ); 
  298.  
  299. $response->link_header( 'next', $next_link ); 
  300.  
  301. return $response; 
  302.  
  303. /** 
  304. * Get the post, if the ID is valid. 
  305. * @since 4.7.2 
  306. * @param int $id Supplied ID. 
  307. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. 
  308. */ 
  309. protected function get_post( $id ) { 
  310. $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 404 ) ); 
  311. if ( (int) $id <= 0 ) { 
  312. return $error; 
  313.  
  314. $post = get_post( (int) $id ); 
  315. if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) { 
  316. return $error; 
  317.  
  318. return $post; 
  319.  
  320. /** 
  321. * Checks if a given request has access to read a post. 
  322. * @since 4.7.0 
  323. * @access public 
  324. * @param WP_REST_Request $request Full details about the request. 
  325. * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise. 
  326. */ 
  327. public function get_item_permissions_check( $request ) { 
  328. $post = $this->get_post( $request['id'] ); 
  329. if ( is_wp_error( $post ) ) { 
  330. return $post; 
  331.  
  332. if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) { 
  333. return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) ); 
  334.  
  335. if ( $post && ! empty( $request['password'] ) ) { 
  336. // Check post password, and return error if invalid. 
  337. if ( ! hash_equals( $post->post_password, $request['password'] ) ) { 
  338. return new WP_Error( 'rest_post_incorrect_password', __( 'Incorrect post password.' ), array( 'status' => 403 ) ); 
  339.  
  340. // Allow access to all password protected posts if the context is edit. 
  341. if ( 'edit' === $request['context'] ) { 
  342. add_filter( 'post_password_required', '__return_false' ); 
  343.  
  344. if ( $post ) { 
  345. return $this->check_read_permission( $post ); 
  346.  
  347. return true; 
  348.  
  349. /** 
  350. * Checks if the user can access password-protected content. 
  351. * This method determines whether we need to override the regular password 
  352. * check in core with a filter. 
  353. * @since 4.7.0 
  354. * @access public 
  355. * @param WP_Post $post Post to check against. 
  356. * @param WP_REST_Request $request Request data to check. 
  357. * @return bool True if the user can access password-protected content, otherwise false. 
  358. */ 
  359. public function can_access_password_content( $post, $request ) { 
  360. if ( empty( $post->post_password ) ) { 
  361. // No filter required. 
  362. return false; 
  363.  
  364. // Edit context always gets access to password-protected posts. 
  365. if ( 'edit' === $request['context'] ) { 
  366. return true; 
  367.  
  368. // No password, no auth. 
  369. if ( empty( $request['password'] ) ) { 
  370. return false; 
  371.  
  372. // Double-check the request password. 
  373. return hash_equals( $post->post_password, $request['password'] ); 
  374.  
  375. /** 
  376. * Retrieves a single post. 
  377. * @since 4.7.0 
  378. * @access public 
  379. * @param WP_REST_Request $request Full details about the request. 
  380. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  381. */ 
  382. public function get_item( $request ) { 
  383. $post = $this->get_post( $request['id'] ); 
  384. if ( is_wp_error( $post ) ) { 
  385. return $post; 
  386.  
  387. $data = $this->prepare_item_for_response( $post, $request ); 
  388. $response = rest_ensure_response( $data ); 
  389.  
  390. if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) { 
  391. $response->link_header( 'alternate', get_permalink( $post->ID ), array( 'type' => 'text/html' ) ); 
  392.  
  393. return $response; 
  394.  
  395. /** 
  396. * Checks if a given request has access to create a post. 
  397. * @since 4.7.0 
  398. * @access public 
  399. * @param WP_REST_Request $request Full details about the request. 
  400. * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. 
  401. */ 
  402. public function create_item_permissions_check( $request ) { 
  403. if ( ! empty( $request['id'] ) ) { 
  404. return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) ); 
  405.  
  406. $post_type = get_post_type_object( $this->post_type ); 
  407.  
  408. if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 
  409. return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); 
  410.  
  411. if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 
  412. return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); 
  413.  
  414. if ( ! current_user_can( $post_type->cap->create_posts ) ) { 
  415. return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to create posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); 
  416.  
  417. if ( ! $this->check_assign_terms_permission( $request ) ) { 
  418. return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not allowed to assign the provided terms.' ), array( 'status' => rest_authorization_required_code() ) ); 
  419.  
  420. return true; 
  421.  
  422. /** 
  423. * Creates a single post. 
  424. * @since 4.7.0 
  425. * @access public 
  426. * @param WP_REST_Request $request Full details about the request. 
  427. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  428. */ 
  429. public function create_item( $request ) { 
  430. if ( ! empty( $request['id'] ) ) { 
  431. return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) ); 
  432.  
  433. $prepared_post = $this->prepare_item_for_database( $request ); 
  434.  
  435. if ( is_wp_error( $prepared_post ) ) { 
  436. return $prepared_post; 
  437.  
  438. $prepared_post->post_type = $this->post_type; 
  439.  
  440. $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true ); 
  441.  
  442. if ( is_wp_error( $post_id ) ) { 
  443.  
  444. if ( 'db_insert_error' === $post_id->get_error_code() ) { 
  445. $post_id->add_data( array( 'status' => 500 ) ); 
  446. } else { 
  447. $post_id->add_data( array( 'status' => 400 ) ); 
  448.  
  449. return $post_id; 
  450.  
  451. $post = get_post( $post_id ); 
  452.  
  453. /** 
  454. * Fires after a single post is created or updated via the REST API. 
  455. * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 
  456. * @since 4.7.0 
  457. * @param WP_Post $post Inserted or updated post object. 
  458. * @param WP_REST_Request $request Request object. 
  459. * @param bool $creating True when creating a post, false when updating. 
  460. */ 
  461. do_action( "rest_insert_{$this->post_type}", $post, $request, true ); 
  462.  
  463. $schema = $this->get_item_schema(); 
  464.  
  465. if ( ! empty( $schema['properties']['sticky'] ) ) { 
  466. if ( ! empty( $request['sticky'] ) ) { 
  467. stick_post( $post_id ); 
  468. } else { 
  469. unstick_post( $post_id ); 
  470.  
  471. if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 
  472. $this->handle_featured_media( $request['featured_media'], $post_id ); 
  473.  
  474. if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { 
  475. set_post_format( $post, $request['format'] ); 
  476.  
  477. if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { 
  478. $this->handle_template( $request['template'], $post_id ); 
  479.  
  480. $terms_update = $this->handle_terms( $post_id, $request ); 
  481.  
  482. if ( is_wp_error( $terms_update ) ) { 
  483. return $terms_update; 
  484.  
  485. if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 
  486. $meta_update = $this->meta->update_value( $request['meta'], $post_id ); 
  487.  
  488. if ( is_wp_error( $meta_update ) ) { 
  489. return $meta_update; 
  490.  
  491. $post = get_post( $post_id ); 
  492. $fields_update = $this->update_additional_fields_for_object( $post, $request ); 
  493.  
  494. if ( is_wp_error( $fields_update ) ) { 
  495. return $fields_update; 
  496.  
  497. $request->set_param( 'context', 'edit' ); 
  498.  
  499. $response = $this->prepare_item_for_response( $post, $request ); 
  500. $response = rest_ensure_response( $response ); 
  501.  
  502. $response->set_status( 201 ); 
  503. $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); 
  504.  
  505. return $response; 
  506.  
  507. /** 
  508. * Checks if a given request has access to update a post. 
  509. * @since 4.7.0 
  510. * @access public 
  511. * @param WP_REST_Request $request Full details about the request. 
  512. * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. 
  513. */ 
  514. public function update_item_permissions_check( $request ) { 
  515. $post = $this->get_post( $request['id'] ); 
  516. if ( is_wp_error( $post ) ) { 
  517. return $post; 
  518.  
  519. $post_type = get_post_type_object( $this->post_type ); 
  520.  
  521. if ( $post && ! $this->check_update_permission( $post ) ) { 
  522. return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit this post.' ), array( 'status' => rest_authorization_required_code() ) ); 
  523.  
  524. if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 
  525. return new WP_Error( 'rest_cannot_edit_others', __( 'Sorry, you are not allowed to update posts as this user.' ), array( 'status' => rest_authorization_required_code() ) ); 
  526.  
  527. if ( ! empty( $request['sticky'] ) && ! current_user_can( $post_type->cap->edit_others_posts ) ) { 
  528. return new WP_Error( 'rest_cannot_assign_sticky', __( 'Sorry, you are not allowed to make posts sticky.' ), array( 'status' => rest_authorization_required_code() ) ); 
  529.  
  530. if ( ! $this->check_assign_terms_permission( $request ) ) { 
  531. return new WP_Error( 'rest_cannot_assign_term', __( 'Sorry, you are not allowed to assign the provided terms.' ), array( 'status' => rest_authorization_required_code() ) ); 
  532.  
  533. return true; 
  534.  
  535. /** 
  536. * Updates a single post. 
  537. * @since 4.7.0 
  538. * @access public 
  539. * @param WP_REST_Request $request Full details about the request. 
  540. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  541. */ 
  542. public function update_item( $request ) { 
  543. $valid_check = $this->get_post( $request['id'] ); 
  544. if ( is_wp_error( $valid_check ) ) { 
  545. return $valid_check; 
  546.  
  547. $post = $this->prepare_item_for_database( $request ); 
  548.  
  549. if ( is_wp_error( $post ) ) { 
  550. return $post; 
  551.  
  552. // convert the post object to an array, otherwise wp_update_post will expect non-escaped input. 
  553. $post_id = wp_update_post( wp_slash( (array) $post ), true ); 
  554.  
  555. if ( is_wp_error( $post_id ) ) { 
  556. if ( 'db_update_error' === $post_id->get_error_code() ) { 
  557. $post_id->add_data( array( 'status' => 500 ) ); 
  558. } else { 
  559. $post_id->add_data( array( 'status' => 400 ) ); 
  560. return $post_id; 
  561.  
  562. $post = get_post( $post_id ); 
  563.  
  564. /** This action is documented in lib/endpoints/class-wp-rest-controller.php */ 
  565. do_action( "rest_insert_{$this->post_type}", $post, $request, false ); 
  566.  
  567. $schema = $this->get_item_schema(); 
  568.  
  569. if ( ! empty( $schema['properties']['format'] ) && ! empty( $request['format'] ) ) { 
  570. set_post_format( $post, $request['format'] ); 
  571.  
  572. if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 
  573. $this->handle_featured_media( $request['featured_media'], $post_id ); 
  574.  
  575. if ( ! empty( $schema['properties']['sticky'] ) && isset( $request['sticky'] ) ) { 
  576. if ( ! empty( $request['sticky'] ) ) { 
  577. stick_post( $post_id ); 
  578. } else { 
  579. unstick_post( $post_id ); 
  580.  
  581. if ( ! empty( $schema['properties']['template'] ) && isset( $request['template'] ) ) { 
  582. $this->handle_template( $request['template'], $post->ID ); 
  583.  
  584. $terms_update = $this->handle_terms( $post->ID, $request ); 
  585.  
  586. if ( is_wp_error( $terms_update ) ) { 
  587. return $terms_update; 
  588.  
  589. if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 
  590. $meta_update = $this->meta->update_value( $request['meta'], $post->ID ); 
  591.  
  592. if ( is_wp_error( $meta_update ) ) { 
  593. return $meta_update; 
  594.  
  595. $post = get_post( $post_id ); 
  596. $fields_update = $this->update_additional_fields_for_object( $post, $request ); 
  597.  
  598. if ( is_wp_error( $fields_update ) ) { 
  599. return $fields_update; 
  600.  
  601. $request->set_param( 'context', 'edit' ); 
  602.  
  603. $response = $this->prepare_item_for_response( $post, $request ); 
  604.  
  605. return rest_ensure_response( $response ); 
  606.  
  607. /** 
  608. * Checks if a given request has access to delete a post. 
  609. * @since 4.7.0 
  610. * @access public 
  611. * @param WP_REST_Request $request Full details about the request. 
  612. * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. 
  613. */ 
  614. public function delete_item_permissions_check( $request ) { 
  615. $post = $this->get_post( $request['id'] ); 
  616. if ( is_wp_error( $post ) ) { 
  617. return $post; 
  618.  
  619. if ( $post && ! $this->check_delete_permission( $post ) ) { 
  620. return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) ); 
  621.  
  622. return true; 
  623.  
  624. /** 
  625. * Deletes a single post. 
  626. * @since 4.7.0 
  627. * @access public 
  628. * @param WP_REST_Request $request Full details about the request. 
  629. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  630. */ 
  631. public function delete_item( $request ) { 
  632. $post = $this->get_post( $request['id'] ); 
  633. if ( is_wp_error( $post ) ) { 
  634. return $post; 
  635.  
  636. $id = $post->ID; 
  637. $force = (bool) $request['force']; 
  638.  
  639. $supports_trash = ( EMPTY_TRASH_DAYS > 0 ); 
  640.  
  641. if ( 'attachment' === $post->post_type ) { 
  642. $supports_trash = $supports_trash && MEDIA_TRASH; 
  643.  
  644. /** 
  645. * Filters whether a post is trashable. 
  646. * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 
  647. * Pass false to disable trash support for the post. 
  648. * @since 4.7.0 
  649. * @param bool $supports_trash Whether the post type support trashing. 
  650. * @param WP_Post $post The Post object being considered for trashing support. 
  651. */ 
  652. $supports_trash = apply_filters( "rest_{$this->post_type}_trashable", $supports_trash, $post ); 
  653.  
  654. if ( ! $this->check_delete_permission( $post ) ) { 
  655. return new WP_Error( 'rest_user_cannot_delete_post', __( 'Sorry, you are not allowed to delete this post.' ), array( 'status' => rest_authorization_required_code() ) ); 
  656.  
  657. $request->set_param( 'context', 'edit' ); 
  658.  
  659.  
  660. // If we're forcing, then delete permanently. 
  661. if ( $force ) { 
  662. $previous = $this->prepare_item_for_response( $post, $request ); 
  663. $result = wp_delete_post( $id, true ); 
  664. $response = new WP_REST_Response(); 
  665. $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) ); 
  666. } else { 
  667. // If we don't support trashing for this type, error out. 
  668. if ( ! $supports_trash ) { 
  669. return new WP_Error( 'rest_trash_not_supported', __( 'The post does not support trashing. Set force=true to delete.' ), array( 'status' => 501 ) ); 
  670.  
  671. // Otherwise, only trash if we haven't already. 
  672. if ( 'trash' === $post->post_status ) { 
  673. return new WP_Error( 'rest_already_trashed', __( 'The post has already been deleted.' ), array( 'status' => 410 ) ); 
  674.  
  675. // (Note that internally this falls through to `wp_delete_post` if 
  676. // the trash is disabled.) 
  677. $result = wp_trash_post( $id ); 
  678. $post = get_post( $id ); 
  679. $response = $this->prepare_item_for_response( $post, $request ); 
  680.  
  681. if ( ! $result ) { 
  682. return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); 
  683.  
  684. /** 
  685. * Fires immediately after a single post is deleted or trashed via the REST API. 
  686. * They dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 
  687. * @since 4.7.0 
  688. * @param object $post The deleted or trashed post. 
  689. * @param WP_REST_Response $response The response data. 
  690. * @param WP_REST_Request $request The request sent to the API. 
  691. */ 
  692. do_action( "rest_delete_{$this->post_type}", $post, $response, $request ); 
  693.  
  694. return $response; 
  695.  
  696. /** 
  697. * Determines the allowed query_vars for a get_items() response and prepares 
  698. * them for WP_Query. 
  699. * @since 4.7.0 
  700. * @access protected 
  701. * @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array. 
  702. * @param WP_REST_Request $request Optional. Full details about the request. 
  703. * @return array Items query arguments. 
  704. */ 
  705. protected function prepare_items_query( $prepared_args = array(), $request = null ) { 
  706. $query_args = array(); 
  707.  
  708. foreach ( $prepared_args as $key => $value ) { 
  709. /** 
  710. * Filters the query_vars used in get_items() for the constructed query. 
  711. * The dynamic portion of the hook name, `$key`, refers to the query_var key. 
  712. * @since 4.7.0 
  713. * @param string $value The query_var value. 
  714. */ 
  715. $query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); 
  716.  
  717. if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) { 
  718. $query_args['ignore_sticky_posts'] = true; 
  719.  
  720. // Map to proper WP_Query orderby param. 
  721. if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) { 
  722. $orderby_mappings = array( 
  723. 'id' => 'ID',  
  724. 'include' => 'post__in',  
  725. 'slug' => 'post_name',  
  726. ); 
  727.  
  728. if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) { 
  729. $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ]; 
  730.  
  731. return $query_args; 
  732.  
  733. /** 
  734. * Checks the post_date_gmt or modified_gmt and prepare any post or 
  735. * modified date for single post output. 
  736. * @since 4.7.0 
  737. * @access protected 
  738. * @param string $date_gmt GMT publication time. 
  739. * @param string|null $date Optional. Local publication time. Default null. 
  740. * @return string|null ISO8601/RFC3339 formatted datetime. 
  741. */ 
  742. protected function prepare_date_response( $date_gmt, $date = null ) { 
  743. // Use the date if passed. 
  744. if ( isset( $date ) ) { 
  745. return mysql_to_rfc3339( $date ); 
  746.  
  747. // Return null if $date_gmt is empty/zeros. 
  748. if ( '0000-00-00 00:00:00' === $date_gmt ) { 
  749. return null; 
  750.  
  751. // Return the formatted datetime. 
  752. return mysql_to_rfc3339( $date_gmt ); 
  753.  
  754. /** 
  755. * Prepares a single post for create or update. 
  756. * @since 4.7.0 
  757. * @access protected 
  758. * @param WP_REST_Request $request Request object. 
  759. * @return stdClass|WP_Error Post object or WP_Error. 
  760. */ 
  761. protected function prepare_item_for_database( $request ) { 
  762. $prepared_post = new stdClass; 
  763.  
  764. // Post ID. 
  765. if ( isset( $request['id'] ) ) { 
  766. $existing_post = $this->get_post( $request['id'] ); 
  767. if ( is_wp_error( $existing_post ) ) { 
  768. return $existing_post; 
  769.  
  770. $prepared_post->ID = $existing_post->ID; 
  771.  
  772. $schema = $this->get_item_schema(); 
  773.  
  774. // Post title. 
  775. if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) { 
  776. if ( is_string( $request['title'] ) ) { 
  777. $prepared_post->post_title = $request['title']; 
  778. } elseif ( ! empty( $request['title']['raw'] ) ) { 
  779. $prepared_post->post_title = $request['title']['raw']; 
  780.  
  781. // Post content. 
  782. if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) { 
  783. if ( is_string( $request['content'] ) ) { 
  784. $prepared_post->post_content = $request['content']; 
  785. } elseif ( isset( $request['content']['raw'] ) ) { 
  786. $prepared_post->post_content = $request['content']['raw']; 
  787.  
  788. // Post excerpt. 
  789. if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) { 
  790. if ( is_string( $request['excerpt'] ) ) { 
  791. $prepared_post->post_excerpt = $request['excerpt']; 
  792. } elseif ( isset( $request['excerpt']['raw'] ) ) { 
  793. $prepared_post->post_excerpt = $request['excerpt']['raw']; 
  794.  
  795. // Post type. 
  796. if ( empty( $request['id'] ) ) { 
  797. // Creating new post, use default type for the controller. 
  798. $prepared_post->post_type = $this->post_type; 
  799. } else { 
  800. // Updating a post, use previous type. 
  801. $prepared_post->post_type = get_post_type( $request['id'] ); 
  802.  
  803. $post_type = get_post_type_object( $prepared_post->post_type ); 
  804.  
  805. // Post status. 
  806. if ( ! empty( $schema['properties']['status'] ) && isset( $request['status'] ) ) { 
  807. $status = $this->handle_status_param( $request['status'], $post_type ); 
  808.  
  809. if ( is_wp_error( $status ) ) { 
  810. return $status; 
  811.  
  812. $prepared_post->post_status = $status; 
  813.  
  814. // Post date. 
  815. if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) { 
  816. $date_data = rest_get_date_with_gmt( $request['date'] ); 
  817.  
  818. if ( ! empty( $date_data ) ) { 
  819. list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; 
  820. $prepared_post->edit_date = true; 
  821. } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) { 
  822. $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); 
  823.  
  824. if ( ! empty( $date_data ) ) { 
  825. list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; 
  826. $prepared_post->edit_date = true; 
  827.  
  828. // Post slug. 
  829. if ( ! empty( $schema['properties']['slug'] ) && isset( $request['slug'] ) ) { 
  830. $prepared_post->post_name = $request['slug']; 
  831.  
  832. // Author. 
  833. if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) { 
  834. $post_author = (int) $request['author']; 
  835.  
  836. if ( get_current_user_id() !== $post_author ) { 
  837. $user_obj = get_userdata( $post_author ); 
  838.  
  839. if ( ! $user_obj ) { 
  840. return new WP_Error( 'rest_invalid_author', __( 'Invalid author ID.' ), array( 'status' => 400 ) ); 
  841.  
  842. $prepared_post->post_author = $post_author; 
  843.  
  844. // Post password. 
  845. if ( ! empty( $schema['properties']['password'] ) && isset( $request['password'] ) ) { 
  846. $prepared_post->post_password = $request['password']; 
  847.  
  848. if ( '' !== $request['password'] ) { 
  849. if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { 
  850. return new WP_Error( 'rest_invalid_field', __( 'A post can not be sticky and have a password.' ), array( 'status' => 400 ) ); 
  851.  
  852. if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) { 
  853. return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), array( 'status' => 400 ) ); 
  854.  
  855. if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { 
  856. if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) { 
  857. return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), array( 'status' => 400 ) ); 
  858.  
  859. // Parent. 
  860. if ( ! empty( $schema['properties']['parent'] ) && isset( $request['parent'] ) ) { 
  861. if ( 0 === (int) $request['parent'] ) { 
  862. $prepared_post->post_parent = 0; 
  863. } else { 
  864. $parent = get_post( (int) $request['parent'] ); 
  865. if ( empty( $parent ) ) { 
  866. return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent ID.' ), array( 'status' => 400 ) ); 
  867. $prepared_post->post_parent = (int) $parent->ID; 
  868.  
  869. // Menu order. 
  870. if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) { 
  871. $prepared_post->menu_order = (int) $request['menu_order']; 
  872.  
  873. // Comment status. 
  874. if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) { 
  875. $prepared_post->comment_status = $request['comment_status']; 
  876.  
  877. // Ping status. 
  878. if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) { 
  879. $prepared_post->ping_status = $request['ping_status']; 
  880.  
  881. /** 
  882. * Filters a post before it is inserted via the REST API. 
  883. * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 
  884. * @since 4.7.0 
  885. * @param stdClass $prepared_post An object representing a single post prepared 
  886. * for inserting or updating the database. 
  887. * @param WP_REST_Request $request Request object. 
  888. */ 
  889. return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request ); 
  890.  
  891.  
  892. /** 
  893. * Determines validity and normalizes the given status parameter. 
  894. * @since 4.7.0 
  895. * @access protected 
  896. * @param string $post_status Post status. 
  897. * @param object $post_type Post type. 
  898. * @return string|WP_Error Post status or WP_Error if lacking the proper permission. 
  899. */ 
  900. protected function handle_status_param( $post_status, $post_type ) { 
  901.  
  902. switch ( $post_status ) { 
  903. case 'draft': 
  904. case 'pending': 
  905. break; 
  906. case 'private': 
  907. if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 
  908. return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); 
  909. break; 
  910. case 'publish': 
  911. case 'future': 
  912. if ( ! current_user_can( $post_type->cap->publish_posts ) ) { 
  913. return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) ); 
  914. break; 
  915. default: 
  916. if ( ! get_post_status_object( $post_status ) ) { 
  917. $post_status = 'draft'; 
  918. break; 
  919.  
  920. return $post_status; 
  921.  
  922. /** 
  923. * Determines the featured media based on a request param. 
  924. * @since 4.7.0 
  925. * @access protected 
  926. * @param int $featured_media Featured Media ID. 
  927. * @param int $post_id Post ID. 
  928. * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error. 
  929. */ 
  930. protected function handle_featured_media( $featured_media, $post_id ) { 
  931.  
  932. $featured_media = (int) $featured_media; 
  933. if ( $featured_media ) { 
  934. $result = set_post_thumbnail( $post_id, $featured_media ); 
  935. if ( $result ) { 
  936. return true; 
  937. } else { 
  938. return new WP_Error( 'rest_invalid_featured_media', __( 'Invalid featured media ID.' ), array( 'status' => 400 ) ); 
  939. } else { 
  940. return delete_post_thumbnail( $post_id ); 
  941.  
  942.  
  943. /** 
  944. * Sets the template for a post. 
  945. * @since 4.7.0 
  946. * @access public 
  947. * @param string $template Page template filename. 
  948. * @param integer $post_id Post ID. 
  949. */ 
  950. public function handle_template( $template, $post_id ) { 
  951. if ( in_array( $template, array_keys( wp_get_theme()->get_page_templates( get_post( $post_id ) ) ), true ) ) { 
  952. update_post_meta( $post_id, '_wp_page_template', $template ); 
  953. } else { 
  954. update_post_meta( $post_id, '_wp_page_template', '' ); 
  955.  
  956. /** 
  957. * Updates the post's terms from a REST request. 
  958. * @since 4.7.0 
  959. * @access protected 
  960. * @param int $post_id The post ID to update the terms form. 
  961. * @param WP_REST_Request $request The request object with post and terms data. 
  962. * @return null|WP_Error WP_Error on an error assigning any of the terms, otherwise null. 
  963. */ 
  964. protected function handle_terms( $post_id, $request ) { 
  965. $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 
  966.  
  967. foreach ( $taxonomies as $taxonomy ) { 
  968. $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 
  969.  
  970. if ( ! isset( $request[ $base ] ) ) { 
  971. continue; 
  972.  
  973. $result = wp_set_object_terms( $post_id, $request[ $base ], $taxonomy->name ); 
  974.  
  975. if ( is_wp_error( $result ) ) { 
  976. return $result; 
  977.  
  978. /** 
  979. * Checks whether current user can assign all terms sent with the current request. 
  980. * @since 4.7.0 
  981. * @param WP_REST_Request $request The request object with post and terms data. 
  982. * @return bool Whether the current user can assign the provided terms. 
  983. */ 
  984. protected function check_assign_terms_permission( $request ) { 
  985. $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 
  986. foreach ( $taxonomies as $taxonomy ) { 
  987. $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 
  988.  
  989. if ( ! isset( $request[ $base ] ) ) { 
  990. continue; 
  991.  
  992. foreach ( $request[ $base ] as $term_id ) { 
  993. // Invalid terms will be rejected later. 
  994. if ( ! get_term( $term_id, $taxonomy->name ) ) { 
  995. continue; 
  996.  
  997. if ( ! current_user_can( 'assign_term', (int) $term_id ) ) { 
  998. return false; 
  999.  
  1000. return true; 
  1001.  
  1002. /** 
  1003. * Checks if a given post type can be viewed or managed. 
  1004. * @since 4.7.0 
  1005. * @access protected 
  1006. * @param object|string $post_type Post type name or object. 
  1007. * @return bool Whether the post type is allowed in REST. 
  1008. */ 
  1009. protected function check_is_post_type_allowed( $post_type ) { 
  1010. if ( ! is_object( $post_type ) ) { 
  1011. $post_type = get_post_type_object( $post_type ); 
  1012.  
  1013. if ( ! empty( $post_type ) && ! empty( $post_type->show_in_rest ) ) { 
  1014. return true; 
  1015.  
  1016. return false; 
  1017.  
  1018. /** 
  1019. * Checks if a post can be read. 
  1020. * Correctly handles posts with the inherit status. 
  1021. * @since 4.7.0 
  1022. * @access public 
  1023. * @param object $post Post object. 
  1024. * @return bool Whether the post can be read. 
  1025. */ 
  1026. public function check_read_permission( $post ) { 
  1027. $post_type = get_post_type_object( $post->post_type ); 
  1028. if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 
  1029. return false; 
  1030.  
  1031. // Is the post readable? 
  1032. if ( 'publish' === $post->post_status || current_user_can( $post_type->cap->read_post, $post->ID ) ) { 
  1033. return true; 
  1034.  
  1035. $post_status_obj = get_post_status_object( $post->post_status ); 
  1036. if ( $post_status_obj && $post_status_obj->public ) { 
  1037. return true; 
  1038.  
  1039. // Can we read the parent if we're inheriting? 
  1040. if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) { 
  1041. $parent = get_post( $post->post_parent ); 
  1042. if ( $parent ) { 
  1043. return $this->check_read_permission( $parent ); 
  1044.  
  1045. /** 
  1046. * If there isn't a parent, but the status is set to inherit, assume 
  1047. * it's published (as per get_post_status()). 
  1048. */ 
  1049. if ( 'inherit' === $post->post_status ) { 
  1050. return true; 
  1051.  
  1052. return false; 
  1053.  
  1054. /** 
  1055. * Checks if a post can be edited. 
  1056. * @since 4.7.0 
  1057. * @access protected 
  1058. * @param object $post Post object. 
  1059. * @return bool Whether the post can be edited. 
  1060. */ 
  1061. protected function check_update_permission( $post ) { 
  1062. $post_type = get_post_type_object( $post->post_type ); 
  1063.  
  1064. if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 
  1065. return false; 
  1066.  
  1067. return current_user_can( $post_type->cap->edit_post, $post->ID ); 
  1068.  
  1069. /** 
  1070. * Checks if a post can be created. 
  1071. * @since 4.7.0 
  1072. * @access protected 
  1073. * @param object $post Post object. 
  1074. * @return bool Whether the post can be created. 
  1075. */ 
  1076. protected function check_create_permission( $post ) { 
  1077. $post_type = get_post_type_object( $post->post_type ); 
  1078.  
  1079. if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 
  1080. return false; 
  1081.  
  1082. return current_user_can( $post_type->cap->create_posts ); 
  1083.  
  1084. /** 
  1085. * Checks if a post can be deleted. 
  1086. * @since 4.7.0 
  1087. * @access protected 
  1088. * @param object $post Post object. 
  1089. * @return bool Whether the post can be deleted. 
  1090. */ 
  1091. protected function check_delete_permission( $post ) { 
  1092. $post_type = get_post_type_object( $post->post_type ); 
  1093.  
  1094. if ( ! $this->check_is_post_type_allowed( $post_type ) ) { 
  1095. return false; 
  1096.  
  1097. return current_user_can( $post_type->cap->delete_post, $post->ID ); 
  1098.  
  1099. /** 
  1100. * Prepares a single post output for response. 
  1101. * @since 4.7.0 
  1102. * @access public 
  1103. * @param WP_Post $post Post object. 
  1104. * @param WP_REST_Request $request Request object. 
  1105. * @return WP_REST_Response Response object. 
  1106. */ 
  1107. public function prepare_item_for_response( $post, $request ) { 
  1108. $GLOBALS['post'] = $post; 
  1109.  
  1110. setup_postdata( $post ); 
  1111.  
  1112. $schema = $this->get_item_schema(); 
  1113.  
  1114. // Base fields for every post. 
  1115. $data = array(); 
  1116.  
  1117. if ( ! empty( $schema['properties']['id'] ) ) { 
  1118. $data['id'] = $post->ID; 
  1119.  
  1120. if ( ! empty( $schema['properties']['date'] ) ) { 
  1121. $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); 
  1122.  
  1123. if ( ! empty( $schema['properties']['date_gmt'] ) ) { 
  1124. // For drafts, `post_date_gmt` may not be set, indicating that the 
  1125. // date of the draft should be updated each time it is saved (see 
  1126. // #38883). In this case, shim the value based on the `post_date` 
  1127. // field with the site's timezone offset applied. 
  1128. if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) { 
  1129. $post_date_gmt = get_gmt_from_date( $post->post_date ); 
  1130. } else { 
  1131. $post_date_gmt = $post->post_date_gmt; 
  1132. $data['date_gmt'] = $this->prepare_date_response( $post_date_gmt ); 
  1133.  
  1134. if ( ! empty( $schema['properties']['guid'] ) ) { 
  1135. $data['guid'] = array( 
  1136. /** This filter is documented in wp-includes/post-template.php */ 
  1137. 'rendered' => apply_filters( 'get_the_guid', $post->guid ),  
  1138. 'raw' => $post->guid,  
  1139. ); 
  1140.  
  1141. if ( ! empty( $schema['properties']['modified'] ) ) { 
  1142. $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); 
  1143.  
  1144. if ( ! empty( $schema['properties']['modified_gmt'] ) ) { 
  1145. // For drafts, `post_modified_gmt` may not be set (see 
  1146. // `post_date_gmt` comments above). In this case, shim the value 
  1147. // based on the `post_modified` field with the site's timezone 
  1148. // offset applied. 
  1149. if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { 
  1150. $post_modified_gmt = date( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - ( get_option( 'gmt_offset' ) * 3600 ) ); 
  1151. } else { 
  1152. $post_modified_gmt = $post->post_modified_gmt; 
  1153. $data['modified_gmt'] = $this->prepare_date_response( $post_modified_gmt ); 
  1154.  
  1155. if ( ! empty( $schema['properties']['password'] ) ) { 
  1156. $data['password'] = $post->post_password; 
  1157.  
  1158. if ( ! empty( $schema['properties']['slug'] ) ) { 
  1159. $data['slug'] = $post->post_name; 
  1160.  
  1161. if ( ! empty( $schema['properties']['status'] ) ) { 
  1162. $data['status'] = $post->post_status; 
  1163.  
  1164. if ( ! empty( $schema['properties']['type'] ) ) { 
  1165. $data['type'] = $post->post_type; 
  1166.  
  1167. if ( ! empty( $schema['properties']['link'] ) ) { 
  1168. $data['link'] = get_permalink( $post->ID ); 
  1169.  
  1170. if ( ! empty( $schema['properties']['title'] ) ) { 
  1171. add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 
  1172.  
  1173. $data['title'] = array( 
  1174. 'raw' => $post->post_title,  
  1175. 'rendered' => get_the_title( $post->ID ),  
  1176. ); 
  1177.  
  1178. remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) ); 
  1179.  
  1180. $has_password_filter = false; 
  1181.  
  1182. if ( $this->can_access_password_content( $post, $request ) ) { 
  1183. // Allow access to the post, permissions already checked before. 
  1184. add_filter( 'post_password_required', '__return_false' ); 
  1185.  
  1186. $has_password_filter = true; 
  1187.  
  1188. if ( ! empty( $schema['properties']['content'] ) ) { 
  1189. $data['content'] = array( 
  1190. 'raw' => $post->post_content,  
  1191. /** This filter is documented in wp-includes/post-template.php */ 
  1192. 'rendered' => post_password_required( $post ) ? '' : apply_filters( 'the_content', $post->post_content ),  
  1193. 'protected' => (bool) $post->post_password,  
  1194. ); 
  1195.  
  1196. if ( ! empty( $schema['properties']['excerpt'] ) ) { 
  1197. /** This filter is documented in wp-includes/post-template.php */ 
  1198. $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) ); 
  1199. $data['excerpt'] = array( 
  1200. 'raw' => $post->post_excerpt,  
  1201. 'rendered' => post_password_required( $post ) ? '' : $excerpt,  
  1202. 'protected' => (bool) $post->post_password,  
  1203. ); 
  1204.  
  1205. if ( $has_password_filter ) { 
  1206. // Reset filter. 
  1207. remove_filter( 'post_password_required', '__return_false' ); 
  1208.  
  1209. if ( ! empty( $schema['properties']['author'] ) ) { 
  1210. $data['author'] = (int) $post->post_author; 
  1211.  
  1212. if ( ! empty( $schema['properties']['featured_media'] ) ) { 
  1213. $data['featured_media'] = (int) get_post_thumbnail_id( $post->ID ); 
  1214.  
  1215. if ( ! empty( $schema['properties']['parent'] ) ) { 
  1216. $data['parent'] = (int) $post->post_parent; 
  1217.  
  1218. if ( ! empty( $schema['properties']['menu_order'] ) ) { 
  1219. $data['menu_order'] = (int) $post->menu_order; 
  1220.  
  1221. if ( ! empty( $schema['properties']['comment_status'] ) ) { 
  1222. $data['comment_status'] = $post->comment_status; 
  1223.  
  1224. if ( ! empty( $schema['properties']['ping_status'] ) ) { 
  1225. $data['ping_status'] = $post->ping_status; 
  1226.  
  1227. if ( ! empty( $schema['properties']['sticky'] ) ) { 
  1228. $data['sticky'] = is_sticky( $post->ID ); 
  1229.  
  1230. if ( ! empty( $schema['properties']['template'] ) ) { 
  1231. if ( $template = get_page_template_slug( $post->ID ) ) { 
  1232. $data['template'] = $template; 
  1233. } else { 
  1234. $data['template'] = ''; 
  1235.  
  1236. if ( ! empty( $schema['properties']['format'] ) ) { 
  1237. $data['format'] = get_post_format( $post->ID ); 
  1238.  
  1239. // Fill in blank post format. 
  1240. if ( empty( $data['format'] ) ) { 
  1241. $data['format'] = 'standard'; 
  1242.  
  1243. if ( ! empty( $schema['properties']['meta'] ) ) { 
  1244. $data['meta'] = $this->meta->get_value( $post->ID, $request ); 
  1245.  
  1246. $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 
  1247.  
  1248. foreach ( $taxonomies as $taxonomy ) { 
  1249. $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 
  1250.  
  1251. if ( ! empty( $schema['properties'][ $base ] ) ) { 
  1252. $terms = get_the_terms( $post, $taxonomy->name ); 
  1253. $data[ $base ] = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array(); 
  1254.  
  1255. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  1256. $data = $this->add_additional_fields_to_object( $data, $request ); 
  1257. $data = $this->filter_response_by_context( $data, $context ); 
  1258.  
  1259. // Wrap the data in a response object. 
  1260. $response = rest_ensure_response( $data ); 
  1261.  
  1262. $response->add_links( $this->prepare_links( $post ) ); 
  1263.  
  1264. /** 
  1265. * Filters the post data for a response. 
  1266. * The dynamic portion of the hook name, `$this->post_type`, refers to the post type slug. 
  1267. * @since 4.7.0 
  1268. * @param WP_REST_Response $response The response object. 
  1269. * @param WP_Post $post Post object. 
  1270. * @param WP_REST_Request $request Request object. 
  1271. */ 
  1272. return apply_filters( "rest_prepare_{$this->post_type}", $response, $post, $request ); 
  1273.  
  1274. /** 
  1275. * Overwrites the default protected title format. 
  1276. * By default, WordPress will show password protected posts with a title of 
  1277. * "Protected: %s", as the REST API communicates the protected status of a post 
  1278. * in a machine readable format, we remove the "Protected: " prefix. 
  1279. * @return string Protected title format. 
  1280. */ 
  1281. public function protected_title_format() { 
  1282. return '%s'; 
  1283.  
  1284. /** 
  1285. * Prepares links for the request. 
  1286. * @since 4.7.0 
  1287. * @access protected 
  1288. * @param WP_Post $post Post object. 
  1289. * @return array Links for the given post. 
  1290. */ 
  1291. protected function prepare_links( $post ) { 
  1292. $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); 
  1293.  
  1294. // Entity meta. 
  1295. $links = array( 
  1296. 'self' => array( 
  1297. 'href' => rest_url( trailingslashit( $base ) . $post->ID ),  
  1298. ),  
  1299. 'collection' => array( 
  1300. 'href' => rest_url( $base ),  
  1301. ),  
  1302. 'about' => array( 
  1303. 'href' => rest_url( 'wp/v2/types/' . $this->post_type ),  
  1304. ),  
  1305. ); 
  1306.  
  1307. if ( ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'author' ) ) 
  1308. && ! empty( $post->post_author ) ) { 
  1309. $links['author'] = array( 
  1310. 'href' => rest_url( 'wp/v2/users/' . $post->post_author ),  
  1311. 'embeddable' => true,  
  1312. ); 
  1313.  
  1314. if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'comments' ) ) { 
  1315. $replies_url = rest_url( 'wp/v2/comments' ); 
  1316. $replies_url = add_query_arg( 'post', $post->ID, $replies_url ); 
  1317.  
  1318. $links['replies'] = array( 
  1319. 'href' => $replies_url,  
  1320. 'embeddable' => true,  
  1321. ); 
  1322.  
  1323. if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) { 
  1324. $links['version-history'] = array( 
  1325. 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ),  
  1326. ); 
  1327.  
  1328. $post_type_obj = get_post_type_object( $post->post_type ); 
  1329.  
  1330. if ( $post_type_obj->hierarchical && ! empty( $post->post_parent ) ) { 
  1331. $links['up'] = array( 
  1332. 'href' => rest_url( trailingslashit( $base ) . (int) $post->post_parent ),  
  1333. 'embeddable' => true,  
  1334. ); 
  1335.  
  1336. // If we have a featured media, add that. 
  1337. if ( $featured_media = get_post_thumbnail_id( $post->ID ) ) { 
  1338. $image_url = rest_url( 'wp/v2/media/' . $featured_media ); 
  1339.  
  1340. $links['https://api.w.org/featuredmedia'] = array( 
  1341. 'href' => $image_url,  
  1342. 'embeddable' => true,  
  1343. ); 
  1344.  
  1345. if ( ! in_array( $post->post_type, array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) { 
  1346. $attachments_url = rest_url( 'wp/v2/media' ); 
  1347. $attachments_url = add_query_arg( 'parent', $post->ID, $attachments_url ); 
  1348.  
  1349. $links['https://api.w.org/attachment'] = array( 
  1350. 'href' => $attachments_url,  
  1351. ); 
  1352.  
  1353. $taxonomies = get_object_taxonomies( $post->post_type ); 
  1354.  
  1355. if ( ! empty( $taxonomies ) ) { 
  1356. $links['https://api.w.org/term'] = array(); 
  1357.  
  1358. foreach ( $taxonomies as $tax ) { 
  1359. $taxonomy_obj = get_taxonomy( $tax ); 
  1360.  
  1361. // Skip taxonomies that are not public. 
  1362. if ( empty( $taxonomy_obj->show_in_rest ) ) { 
  1363. continue; 
  1364.  
  1365. $tax_base = ! empty( $taxonomy_obj->rest_base ) ? $taxonomy_obj->rest_base : $tax; 
  1366.  
  1367. $terms_url = add_query_arg( 
  1368. 'post',  
  1369. $post->ID,  
  1370. rest_url( 'wp/v2/' . $tax_base ) 
  1371. ); 
  1372.  
  1373. $links['https://api.w.org/term'][] = array( 
  1374. 'href' => $terms_url,  
  1375. 'taxonomy' => $tax,  
  1376. 'embeddable' => true,  
  1377. ); 
  1378.  
  1379. return $links; 
  1380.  
  1381. /** 
  1382. * Retrieves the post's schema, conforming to JSON Schema. 
  1383. * @since 4.7.0 
  1384. * @access public 
  1385. * @return array Item schema data. 
  1386. */ 
  1387. public function get_item_schema() { 
  1388.  
  1389. $schema = array( 
  1390. '$schema' => 'http://json-schema.org/schema#',  
  1391. 'title' => $this->post_type,  
  1392. 'type' => 'object',  
  1393. // Base properties for every Post. 
  1394. 'properties' => array( 
  1395. 'date' => array( 
  1396. 'description' => __( "The date the object was published, in the site's timezone." ),  
  1397. 'type' => 'string',  
  1398. 'format' => 'date-time',  
  1399. 'context' => array( 'view', 'edit', 'embed' ),  
  1400. ),  
  1401. 'date_gmt' => array( 
  1402. 'description' => __( 'The date the object was published, as GMT.' ),  
  1403. 'type' => 'string',  
  1404. 'format' => 'date-time',  
  1405. 'context' => array( 'view', 'edit' ),  
  1406. ),  
  1407. 'guid' => array( 
  1408. 'description' => __( 'The globally unique identifier for the object.' ),  
  1409. 'type' => 'object',  
  1410. 'context' => array( 'view', 'edit' ),  
  1411. 'readonly' => true,  
  1412. 'properties' => array( 
  1413. 'raw' => array( 
  1414. 'description' => __( 'GUID for the object, as it exists in the database.' ),  
  1415. 'type' => 'string',  
  1416. 'context' => array( 'edit' ),  
  1417. 'readonly' => true,  
  1418. ),  
  1419. 'rendered' => array( 
  1420. 'description' => __( 'GUID for the object, transformed for display.' ),  
  1421. 'type' => 'string',  
  1422. 'context' => array( 'view', 'edit' ),  
  1423. 'readonly' => true,  
  1424. ),  
  1425. ),  
  1426. ),  
  1427. 'id' => array( 
  1428. 'description' => __( 'Unique identifier for the object.' ),  
  1429. 'type' => 'integer',  
  1430. 'context' => array( 'view', 'edit', 'embed' ),  
  1431. 'readonly' => true,  
  1432. ),  
  1433. 'link' => array( 
  1434. 'description' => __( 'URL to the object.' ),  
  1435. 'type' => 'string',  
  1436. 'format' => 'uri',  
  1437. 'context' => array( 'view', 'edit', 'embed' ),  
  1438. 'readonly' => true,  
  1439. ),  
  1440. 'modified' => array( 
  1441. 'description' => __( "The date the object was last modified, in the site's timezone." ),  
  1442. 'type' => 'string',  
  1443. 'format' => 'date-time',  
  1444. 'context' => array( 'view', 'edit' ),  
  1445. 'readonly' => true,  
  1446. ),  
  1447. 'modified_gmt' => array( 
  1448. 'description' => __( 'The date the object was last modified, as GMT.' ),  
  1449. 'type' => 'string',  
  1450. 'format' => 'date-time',  
  1451. 'context' => array( 'view', 'edit' ),  
  1452. 'readonly' => true,  
  1453. ),  
  1454. 'slug' => array( 
  1455. 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),  
  1456. 'type' => 'string',  
  1457. 'context' => array( 'view', 'edit', 'embed' ),  
  1458. 'arg_options' => array( 
  1459. 'sanitize_callback' => array( $this, 'sanitize_slug' ),  
  1460. ),  
  1461. ),  
  1462. 'status' => array( 
  1463. 'description' => __( 'A named status for the object.' ),  
  1464. 'type' => 'string',  
  1465. 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ),  
  1466. 'context' => array( 'view', 'edit' ),  
  1467. ),  
  1468. 'type' => array( 
  1469. 'description' => __( 'Type of Post for the object.' ),  
  1470. 'type' => 'string',  
  1471. 'context' => array( 'view', 'edit', 'embed' ),  
  1472. 'readonly' => true,  
  1473. ),  
  1474. 'password' => array( 
  1475. 'description' => __( 'A password to protect access to the content and excerpt.' ),  
  1476. 'type' => 'string',  
  1477. 'context' => array( 'edit' ),  
  1478. ),  
  1479. ),  
  1480. ); 
  1481.  
  1482. $post_type_obj = get_post_type_object( $this->post_type ); 
  1483.  
  1484. if ( $post_type_obj->hierarchical ) { 
  1485. $schema['properties']['parent'] = array( 
  1486. 'description' => __( 'The ID for the parent of the object.' ),  
  1487. 'type' => 'integer',  
  1488. 'context' => array( 'view', 'edit' ),  
  1489. ); 
  1490.  
  1491. $post_type_attributes = array( 
  1492. 'title',  
  1493. 'editor',  
  1494. 'author',  
  1495. 'excerpt',  
  1496. 'thumbnail',  
  1497. 'comments',  
  1498. 'revisions',  
  1499. 'page-attributes',  
  1500. 'post-formats',  
  1501. 'custom-fields',  
  1502. ); 
  1503. $fixed_schemas = array( 
  1504. 'post' => array( 
  1505. 'title',  
  1506. 'editor',  
  1507. 'author',  
  1508. 'excerpt',  
  1509. 'thumbnail',  
  1510. 'comments',  
  1511. 'revisions',  
  1512. 'post-formats',  
  1513. 'custom-fields',  
  1514. ),  
  1515. 'page' => array( 
  1516. 'title',  
  1517. 'editor',  
  1518. 'author',  
  1519. 'excerpt',  
  1520. 'thumbnail',  
  1521. 'comments',  
  1522. 'revisions',  
  1523. 'page-attributes',  
  1524. 'custom-fields',  
  1525. ),  
  1526. 'attachment' => array( 
  1527. 'title',  
  1528. 'author',  
  1529. 'comments',  
  1530. 'revisions',  
  1531. 'custom-fields',  
  1532. ),  
  1533. ); 
  1534. foreach ( $post_type_attributes as $attribute ) { 
  1535. if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ], true ) ) { 
  1536. continue; 
  1537. } elseif ( ! isset( $fixed_schemas[ $this->post_type ] ) && ! post_type_supports( $this->post_type, $attribute ) ) { 
  1538. continue; 
  1539.  
  1540. switch ( $attribute ) { 
  1541.  
  1542. case 'title': 
  1543. $schema['properties']['title'] = array( 
  1544. 'description' => __( 'The title for the object.' ),  
  1545. 'type' => 'object',  
  1546. 'context' => array( 'view', 'edit', 'embed' ),  
  1547. 'arg_options' => array( 
  1548. 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() 
  1549. ),  
  1550. 'properties' => array( 
  1551. 'raw' => array( 
  1552. 'description' => __( 'Title for the object, as it exists in the database.' ),  
  1553. 'type' => 'string',  
  1554. 'context' => array( 'edit' ),  
  1555. ),  
  1556. 'rendered' => array( 
  1557. 'description' => __( 'HTML title for the object, transformed for display.' ),  
  1558. 'type' => 'string',  
  1559. 'context' => array( 'view', 'edit', 'embed' ),  
  1560. 'readonly' => true,  
  1561. ),  
  1562. ),  
  1563. ); 
  1564. break; 
  1565.  
  1566. case 'editor': 
  1567. $schema['properties']['content'] = array( 
  1568. 'description' => __( 'The content for the object.' ),  
  1569. 'type' => 'object',  
  1570. 'context' => array( 'view', 'edit' ),  
  1571. 'arg_options' => array( 
  1572. 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() 
  1573. ),  
  1574. 'properties' => array( 
  1575. 'raw' => array( 
  1576. 'description' => __( 'Content for the object, as it exists in the database.' ),  
  1577. 'type' => 'string',  
  1578. 'context' => array( 'edit' ),  
  1579. ),  
  1580. 'rendered' => array( 
  1581. 'description' => __( 'HTML content for the object, transformed for display.' ),  
  1582. 'type' => 'string',  
  1583. 'context' => array( 'view', 'edit' ),  
  1584. 'readonly' => true,  
  1585. ),  
  1586. 'protected' => array( 
  1587. 'description' => __( 'Whether the content is protected with a password.' ),  
  1588. 'type' => 'boolean',  
  1589. 'context' => array( 'view', 'edit', 'embed' ),  
  1590. 'readonly' => true,  
  1591. ),  
  1592. ),  
  1593. ); 
  1594. break; 
  1595.  
  1596. case 'author': 
  1597. $schema['properties']['author'] = array( 
  1598. 'description' => __( 'The ID for the author of the object.' ),  
  1599. 'type' => 'integer',  
  1600. 'context' => array( 'view', 'edit', 'embed' ),  
  1601. ); 
  1602. break; 
  1603.  
  1604. case 'excerpt': 
  1605. $schema['properties']['excerpt'] = array( 
  1606. 'description' => __( 'The excerpt for the object.' ),  
  1607. 'type' => 'object',  
  1608. 'context' => array( 'view', 'edit', 'embed' ),  
  1609. 'arg_options' => array( 
  1610. 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() 
  1611. ),  
  1612. 'properties' => array( 
  1613. 'raw' => array( 
  1614. 'description' => __( 'Excerpt for the object, as it exists in the database.' ),  
  1615. 'type' => 'string',  
  1616. 'context' => array( 'edit' ),  
  1617. ),  
  1618. 'rendered' => array( 
  1619. 'description' => __( 'HTML excerpt for the object, transformed for display.' ),  
  1620. 'type' => 'string',  
  1621. 'context' => array( 'view', 'edit', 'embed' ),  
  1622. 'readonly' => true,  
  1623. ),  
  1624. 'protected' => array( 
  1625. 'description' => __( 'Whether the excerpt is protected with a password.' ),  
  1626. 'type' => 'boolean',  
  1627. 'context' => array( 'view', 'edit', 'embed' ),  
  1628. 'readonly' => true,  
  1629. ),  
  1630. ),  
  1631. ); 
  1632. break; 
  1633.  
  1634. case 'thumbnail': 
  1635. $schema['properties']['featured_media'] = array( 
  1636. 'description' => __( 'The ID of the featured media for the object.' ),  
  1637. 'type' => 'integer',  
  1638. 'context' => array( 'view', 'edit' ),  
  1639. ); 
  1640. break; 
  1641.  
  1642. case 'comments': 
  1643. $schema['properties']['comment_status'] = array( 
  1644. 'description' => __( 'Whether or not comments are open on the object.' ),  
  1645. 'type' => 'string',  
  1646. 'enum' => array( 'open', 'closed' ),  
  1647. 'context' => array( 'view', 'edit' ),  
  1648. ); 
  1649. $schema['properties']['ping_status'] = array( 
  1650. 'description' => __( 'Whether or not the object can be pinged.' ),  
  1651. 'type' => 'string',  
  1652. 'enum' => array( 'open', 'closed' ),  
  1653. 'context' => array( 'view', 'edit' ),  
  1654. ); 
  1655. break; 
  1656.  
  1657. case 'page-attributes': 
  1658. $schema['properties']['menu_order'] = array( 
  1659. 'description' => __( 'The order of the object in relation to other object of its type.' ),  
  1660. 'type' => 'integer',  
  1661. 'context' => array( 'view', 'edit' ),  
  1662. ); 
  1663. break; 
  1664.  
  1665. case 'post-formats': 
  1666. // Get the native post formats and remove the array keys. 
  1667. $formats = array_values( get_post_format_slugs() ); 
  1668.  
  1669. $schema['properties']['format'] = array( 
  1670. 'description' => __( 'The format for the object.' ),  
  1671. 'type' => 'string',  
  1672. 'enum' => $formats,  
  1673. 'context' => array( 'view', 'edit' ),  
  1674. ); 
  1675. break; 
  1676.  
  1677. case 'custom-fields': 
  1678. $schema['properties']['meta'] = $this->meta->get_field_schema(); 
  1679. break; 
  1680.  
  1681.  
  1682. if ( 'post' === $this->post_type ) { 
  1683. $schema['properties']['sticky'] = array( 
  1684. 'description' => __( 'Whether or not the object should be treated as sticky.' ),  
  1685. 'type' => 'boolean',  
  1686. 'context' => array( 'view', 'edit' ),  
  1687. ); 
  1688.  
  1689. $schema['properties']['template'] = array( 
  1690. 'description' => __( 'The theme file to use to display the object.' ),  
  1691. 'type' => 'string',  
  1692. 'enum' => array_merge( array_keys( wp_get_theme()->get_page_templates( null, $this->post_type ) ), array( '' ) ),  
  1693. 'context' => array( 'view', 'edit' ),  
  1694. ); 
  1695.  
  1696. $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 
  1697. foreach ( $taxonomies as $taxonomy ) { 
  1698. $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 
  1699. $schema['properties'][ $base ] = array( 
  1700. /** translators: %s: taxonomy name */ 
  1701. 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),  
  1702. 'type' => 'array',  
  1703. 'items' => array( 
  1704. 'type' => 'integer',  
  1705. ),  
  1706. 'context' => array( 'view', 'edit' ),  
  1707. ); 
  1708.  
  1709. return $this->add_additional_fields_schema( $schema ); 
  1710.  
  1711. /** 
  1712. * Retrieves the query params for the posts collection. 
  1713. * @since 4.7.0 
  1714. * @access public 
  1715. * @return array Collection parameters. 
  1716. */ 
  1717. public function get_collection_params() { 
  1718. $query_params = parent::get_collection_params(); 
  1719.  
  1720. $query_params['context']['default'] = 'view'; 
  1721.  
  1722. $query_params['after'] = array( 
  1723. 'description' => __( 'Limit response to posts published after a given ISO8601 compliant date.' ),  
  1724. 'type' => 'string',  
  1725. 'format' => 'date-time',  
  1726. ); 
  1727.  
  1728. if ( post_type_supports( $this->post_type, 'author' ) ) { 
  1729. $query_params['author'] = array( 
  1730. 'description' => __( 'Limit result set to posts assigned to specific authors.' ),  
  1731. 'type' => 'array',  
  1732. 'items' => array( 
  1733. 'type' => 'integer',  
  1734. ),  
  1735. 'default' => array(),  
  1736. ); 
  1737. $query_params['author_exclude'] = array( 
  1738. 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ),  
  1739. 'type' => 'array',  
  1740. 'items' => array( 
  1741. 'type' => 'integer',  
  1742. ),  
  1743. 'default' => array(),  
  1744. ); 
  1745.  
  1746. $query_params['before'] = array( 
  1747. 'description' => __( 'Limit response to posts published before a given ISO8601 compliant date.' ),  
  1748. 'type' => 'string',  
  1749. 'format' => 'date-time',  
  1750. ); 
  1751.  
  1752. $query_params['exclude'] = array( 
  1753. 'description' => __( 'Ensure result set excludes specific IDs.' ),  
  1754. 'type' => 'array',  
  1755. 'items' => array( 
  1756. 'type' => 'integer',  
  1757. ),  
  1758. 'default' => array(),  
  1759. ); 
  1760.  
  1761. $query_params['include'] = array( 
  1762. 'description' => __( 'Limit result set to specific IDs.' ),  
  1763. 'type' => 'array',  
  1764. 'items' => array( 
  1765. 'type' => 'integer',  
  1766. ),  
  1767. 'default' => array(),  
  1768. ); 
  1769.  
  1770. if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { 
  1771. $query_params['menu_order'] = array( 
  1772. 'description' => __( 'Limit result set to posts with a specific menu_order value.' ),  
  1773. 'type' => 'integer',  
  1774. ); 
  1775.  
  1776. $query_params['offset'] = array( 
  1777. 'description' => __( 'Offset the result set by a specific number of items.' ),  
  1778. 'type' => 'integer',  
  1779. ); 
  1780.  
  1781. $query_params['order'] = array( 
  1782. 'description' => __( 'Order sort attribute ascending or descending.' ),  
  1783. 'type' => 'string',  
  1784. 'default' => 'desc',  
  1785. 'enum' => array( 'asc', 'desc' ),  
  1786. ); 
  1787.  
  1788. $query_params['orderby'] = array( 
  1789. 'description' => __( 'Sort collection by object attribute.' ),  
  1790. 'type' => 'string',  
  1791. 'default' => 'date',  
  1792. 'enum' => array( 
  1793. 'date',  
  1794. 'relevance',  
  1795. 'id',  
  1796. 'include',  
  1797. 'title',  
  1798. 'slug',  
  1799. ),  
  1800. ); 
  1801.  
  1802. if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { 
  1803. $query_params['orderby']['enum'][] = 'menu_order'; 
  1804.  
  1805. $post_type = get_post_type_object( $this->post_type ); 
  1806.  
  1807. if ( $post_type->hierarchical || 'attachment' === $this->post_type ) { 
  1808. $query_params['parent'] = array( 
  1809. 'description' => __( 'Limit result set to those of particular parent IDs.' ),  
  1810. 'type' => 'array',  
  1811. 'items' => array( 
  1812. 'type' => 'integer',  
  1813. ),  
  1814. 'default' => array(),  
  1815. ); 
  1816. $query_params['parent_exclude'] = array( 
  1817. 'description' => __( 'Limit result set to all items except those of a particular parent ID.' ),  
  1818. 'type' => 'array',  
  1819. 'items' => array( 
  1820. 'type' => 'integer',  
  1821. ),  
  1822. 'default' => array(),  
  1823. ); 
  1824.  
  1825. $query_params['slug'] = array( 
  1826. 'description' => __( 'Limit result set to posts with one or more specific slugs.' ),  
  1827. 'type' => 'array',  
  1828. 'items' => array( 
  1829. 'type' => 'string',  
  1830. ),  
  1831. 'sanitize_callback' => 'wp_parse_slug_list',  
  1832. ); 
  1833.  
  1834. $query_params['status'] = array( 
  1835. 'default' => 'publish',  
  1836. 'description' => __( 'Limit result set to posts assigned one or more statuses.' ),  
  1837. 'type' => 'array',  
  1838. 'items' => array( 
  1839. 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ),  
  1840. 'type' => 'string',  
  1841. ),  
  1842. 'sanitize_callback' => array( $this, 'sanitize_post_statuses' ),  
  1843. ); 
  1844.  
  1845. $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); 
  1846.  
  1847. foreach ( $taxonomies as $taxonomy ) { 
  1848. $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; 
  1849.  
  1850. $query_params[ $base ] = array( 
  1851. /** translators: %s: taxonomy name */ 
  1852. 'description' => sprintf( __( 'Limit result set to all items that have the specified term assigned in the %s taxonomy.' ), $base ),  
  1853. 'type' => 'array',  
  1854. 'items' => array( 
  1855. 'type' => 'integer',  
  1856. ),  
  1857. 'default' => array(),  
  1858. ); 
  1859.  
  1860. $query_params[ $base . '_exclude' ] = array( 
  1861. /** translators: %s: taxonomy name */ 
  1862. 'description' => sprintf( __( 'Limit result set to all items except those that have the specified term assigned in the %s taxonomy.' ), $base ),  
  1863. 'type' => 'array',  
  1864. 'items' => array( 
  1865. 'type' => 'integer',  
  1866. ),  
  1867. 'default' => array(),  
  1868. ); 
  1869.  
  1870. if ( 'post' === $this->post_type ) { 
  1871. $query_params['sticky'] = array( 
  1872. 'description' => __( 'Limit result set to items that are sticky.' ),  
  1873. 'type' => 'boolean',  
  1874. ); 
  1875.  
  1876. /** 
  1877. * Filter collection parameters for the posts controller. 
  1878. * The dynamic part of the filter `$this->post_type` refers to the post 
  1879. * type slug for the controller. 
  1880. * This filter registers the collection parameter, but does not map the 
  1881. * collection parameter to an internal WP_Query parameter. Use the 
  1882. * `rest_{$this->post_type}_query` filter to set WP_Query parameters. 
  1883. * @since 4.7.0 
  1884. * @param array $query_params JSON Schema-formatted collection parameters. 
  1885. * @param WP_Post_Type $post_type Post type object. 
  1886. */ 
  1887. return apply_filters( "rest_{$this->post_type}_collection_params", $query_params, $post_type ); 
  1888.  
  1889. /** 
  1890. * Sanitizes and validates the list of post statuses, including whether the 
  1891. * user can query private statuses. 
  1892. * @since 4.7.0 
  1893. * @access public 
  1894. * @param string|array $statuses One or more post statuses. 
  1895. * @param WP_REST_Request $request Full details about the request. 
  1896. * @param string $parameter Additional parameter to pass to validation. 
  1897. * @return array|WP_Error A list of valid statuses, otherwise WP_Error object. 
  1898. */ 
  1899. public function sanitize_post_statuses( $statuses, $request, $parameter ) { 
  1900. $statuses = wp_parse_slug_list( $statuses ); 
  1901.  
  1902. // The default status is different in WP_REST_Attachments_Controller 
  1903. $attributes = $request->get_attributes(); 
  1904. $default_status = $attributes['args']['status']['default']; 
  1905.  
  1906. foreach ( $statuses as $status ) { 
  1907. if ( $status === $default_status ) { 
  1908. continue; 
  1909.  
  1910. $post_type_obj = get_post_type_object( $this->post_type ); 
  1911.  
  1912. if ( current_user_can( $post_type_obj->cap->edit_posts ) ) { 
  1913. $result = rest_validate_request_arg( $status, $request, $parameter ); 
  1914. if ( is_wp_error( $result ) ) { 
  1915. return $result; 
  1916. } else { 
  1917. return new WP_Error( 'rest_forbidden_status', __( 'Status is forbidden.' ), array( 'status' => rest_authorization_required_code() ) ); 
  1918.  
  1919. return $statuses;