/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

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