WP_REST_Revisions_Controller

Core class used to access revisions via the REST API.

Defined (1)

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

/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php  
  1. class WP_REST_Revisions_Controller extends WP_REST_Controller { 
  2.  
  3. /** 
  4. * Parent post type. 
  5. * @since 4.7.0 
  6. * @access private 
  7. * @var string 
  8. */ 
  9. private $parent_post_type; 
  10.  
  11. /** 
  12. * Parent controller. 
  13. * @since 4.7.0 
  14. * @access private 
  15. * @var WP_REST_Controller 
  16. */ 
  17. private $parent_controller; 
  18.  
  19. /** 
  20. * The base of the parent controller's route. 
  21. * @since 4.7.0 
  22. * @access private 
  23. * @var string 
  24. */ 
  25. private $parent_base; 
  26.  
  27. /** 
  28. * Constructor. 
  29. * @since 4.7.0 
  30. * @access public 
  31. * @param string $parent_post_type Post type of the parent. 
  32. */ 
  33. public function __construct( $parent_post_type ) { 
  34. $this->parent_post_type = $parent_post_type; 
  35. $this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); 
  36. $this->namespace = 'wp/v2'; 
  37. $this->rest_base = 'revisions'; 
  38. $post_type_object = get_post_type_object( $parent_post_type ); 
  39. $this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; 
  40.  
  41. /** 
  42. * Registers routes for revisions based on post types supporting revisions. 
  43. * @since 4.7.0 
  44. * @access public 
  45. * @see register_rest_route() 
  46. */ 
  47. public function register_routes() { 
  48.  
  49. register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array( 
  50. 'args' => array( 
  51. 'parent' => array( 
  52. 'description' => __( 'The ID for the parent of the object.' ),  
  53. 'type' => 'integer',  
  54. ),  
  55. ),  
  56. array( 
  57. 'methods' => WP_REST_Server::READABLE,  
  58. 'callback' => array( $this, 'get_items' ),  
  59. 'permission_callback' => array( $this, 'get_items_permissions_check' ),  
  60. 'args' => $this->get_collection_params(),  
  61. ),  
  62. 'schema' => array( $this, 'get_public_item_schema' ),  
  63. ) ); 
  64.  
  65. register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array( 
  66. 'args' => array( 
  67. 'parent' => array( 
  68. 'description' => __( 'The ID for the parent of the object.' ),  
  69. 'type' => 'integer',  
  70. ),  
  71. 'id' => array( 
  72. 'description' => __( 'Unique identifier for the object.' ),  
  73. 'type' => 'integer',  
  74. ),  
  75. ),  
  76. array( 
  77. 'methods' => WP_REST_Server::READABLE,  
  78. 'callback' => array( $this, 'get_item' ),  
  79. 'permission_callback' => array( $this, 'get_item_permissions_check' ),  
  80. 'args' => array( 
  81. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  82. ),  
  83. ),  
  84. array( 
  85. 'methods' => WP_REST_Server::DELETABLE,  
  86. 'callback' => array( $this, 'delete_item' ),  
  87. 'permission_callback' => array( $this, 'delete_item_permissions_check' ),  
  88. 'args' => array( 
  89. 'force' => array( 
  90. 'type' => 'boolean',  
  91. 'default' => false,  
  92. 'description' => __( 'Required to be true, as revisions do not support trashing.' ),  
  93. ),  
  94. ),  
  95. ),  
  96. 'schema' => array( $this, 'get_public_item_schema' ),  
  97. )); 
  98.  
  99.  
  100. /** 
  101. * Get the parent post, if the ID is valid. 
  102. * @since 4.7.2 
  103. * @param int $id Supplied ID. 
  104. * @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise. 
  105. */ 
  106. protected function get_parent( $parent ) { 
  107. $error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) ); 
  108. if ( (int) $parent <= 0 ) { 
  109. return $error; 
  110.  
  111. $parent = get_post( (int) $parent ); 
  112. if ( empty( $parent ) || empty( $parent->ID ) || $this->parent_post_type !== $parent->post_type ) { 
  113. return $error; 
  114.  
  115. return $parent; 
  116.  
  117. /** 
  118. * Checks if a given request has access to get revisions. 
  119. * @since 4.7.0 
  120. * @access public 
  121. * @param WP_REST_Request $request Full data about the request. 
  122. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 
  123. */ 
  124. public function get_items_permissions_check( $request ) { 
  125. $parent = $this->get_parent( $request['parent'] ); 
  126. if ( is_wp_error( $parent ) ) { 
  127. return $parent; 
  128.  
  129. $parent_post_type_obj = get_post_type_object( $parent->post_type ); 
  130. if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) { 
  131. return new WP_Error( 'rest_cannot_read', __( 'Sorry, you are not allowed to view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); 
  132.  
  133. return true; 
  134.  
  135. /** 
  136. * Get the revision, if the ID is valid. 
  137. * @since 4.7.2 
  138. * @param int $id Supplied ID. 
  139. * @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise. 
  140. */ 
  141. protected function get_revision( $id ) { 
  142. $error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) ); 
  143. if ( (int) $id <= 0 ) { 
  144. return $error; 
  145.  
  146. $revision = get_post( (int) $id ); 
  147. if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) { 
  148. return $error; 
  149.  
  150. return $revision; 
  151.  
  152. /** 
  153. * Gets a collection of revisions. 
  154. * @since 4.7.0 
  155. * @access public 
  156. * @param WP_REST_Request $request Full data about the request. 
  157. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  158. */ 
  159. public function get_items( $request ) { 
  160. $parent = $this->get_parent( $request['parent'] ); 
  161. if ( is_wp_error( $parent ) ) { 
  162. return $parent; 
  163.  
  164. $revisions = wp_get_post_revisions( $request['parent'] ); 
  165.  
  166. $response = array(); 
  167. foreach ( $revisions as $revision ) { 
  168. $data = $this->prepare_item_for_response( $revision, $request ); 
  169. $response[] = $this->prepare_response_for_collection( $data ); 
  170. return rest_ensure_response( $response ); 
  171.  
  172. /** 
  173. * Checks if a given request has access to get a specific revision. 
  174. * @since 4.7.0 
  175. * @access public 
  176. * @param WP_REST_Request $request Full data about the request. 
  177. * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise. 
  178. */ 
  179. public function get_item_permissions_check( $request ) { 
  180. return $this->get_items_permissions_check( $request ); 
  181.  
  182. /** 
  183. * Retrieves one revision from the collection. 
  184. * @since 4.7.0 
  185. * @access public 
  186. * @param WP_REST_Request $request Full data about the request. 
  187. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 
  188. */ 
  189. public function get_item( $request ) { 
  190. $parent = $this->get_parent( $request['parent'] ); 
  191. if ( is_wp_error( $parent ) ) { 
  192. return $parent; 
  193.  
  194. $revision = $this->get_revision( $request['id'] ); 
  195. if ( is_wp_error( $revision ) ) { 
  196. return $revision; 
  197.  
  198. $response = $this->prepare_item_for_response( $revision, $request ); 
  199. return rest_ensure_response( $response ); 
  200.  
  201. /** 
  202. * Checks if a given request has access to delete a revision. 
  203. * @since 4.7.0 
  204. * @access public 
  205. * @param WP_REST_Request $request Full details about the request. 
  206. * @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise. 
  207. */ 
  208. public function delete_item_permissions_check( $request ) { 
  209. $parent = $this->get_parent( $request['parent'] ); 
  210. if ( is_wp_error( $parent ) ) { 
  211. return $parent; 
  212.  
  213. $revision = $this->get_revision( $request['id'] ); 
  214. if ( is_wp_error( $revision ) ) { 
  215. return $revision; 
  216.  
  217. $response = $this->get_items_permissions_check( $request ); 
  218. if ( ! $response || is_wp_error( $response ) ) { 
  219. return $response; 
  220.  
  221. $post_type = get_post_type_object( 'revision' ); 
  222. return current_user_can( $post_type->cap->delete_post, $revision->ID ); 
  223.  
  224. /** 
  225. * Deletes a single revision. 
  226. * @since 4.7.0 
  227. * @access public 
  228. * @param WP_REST_Request $request Full details about the request. 
  229. * @return true|WP_Error True on success, or WP_Error object on failure. 
  230. */ 
  231. public function delete_item( $request ) { 
  232. $revision = $this->get_revision( $request['id'] ); 
  233. if ( is_wp_error( $revision ) ) { 
  234. return $revision; 
  235.  
  236. $force = isset( $request['force'] ) ? (bool) $request['force'] : false; 
  237.  
  238. // We don't support trashing for revisions. 
  239. if ( ! $force ) { 
  240. return new WP_Error( 'rest_trash_not_supported', __( 'Revisions do not support trashing. Set force=true to delete.' ), array( 'status' => 501 ) ); 
  241.  
  242. $previous = $this->prepare_item_for_response( $revision, $request ); 
  243.  
  244. $result = wp_delete_post( $request['id'], true ); 
  245.  
  246. /** 
  247. * Fires after a revision is deleted via the REST API. 
  248. * @since 4.7.0 
  249. * @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully) 
  250. * or false (failure). If the revision was moved to to the trash, $result represents 
  251. * its new state; if it was deleted, $result represents its state before deletion. 
  252. * @param WP_REST_Request $request The request sent to the API. 
  253. */ 
  254. do_action( 'rest_delete_revision', $result, $request ); 
  255.  
  256. if ( ! $result ) { 
  257. return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); 
  258.  
  259. $response = new WP_REST_Response(); 
  260. $response->set_data( array( 'deleted' => true, 'previous' => $previous->get_data() ) ); 
  261. return $response; 
  262.  
  263. /** 
  264. * Prepares the revision for the REST response. 
  265. * @since 4.7.0 
  266. * @access public 
  267. * @param WP_Post $post Post revision object. 
  268. * @param WP_REST_Request $request Request object. 
  269. * @return WP_REST_Response Response object. 
  270. */ 
  271. public function prepare_item_for_response( $post, $request ) { 
  272.  
  273. $schema = $this->get_item_schema(); 
  274.  
  275. $data = array(); 
  276.  
  277. if ( ! empty( $schema['properties']['author'] ) ) { 
  278. $data['author'] = (int) $post->post_author; 
  279.  
  280. if ( ! empty( $schema['properties']['date'] ) ) { 
  281. $data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); 
  282.  
  283. if ( ! empty( $schema['properties']['date_gmt'] ) ) { 
  284. $data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); 
  285.  
  286. if ( ! empty( $schema['properties']['id'] ) ) { 
  287. $data['id'] = $post->ID; 
  288.  
  289. if ( ! empty( $schema['properties']['modified'] ) ) { 
  290. $data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); 
  291.  
  292. if ( ! empty( $schema['properties']['modified_gmt'] ) ) { 
  293. $data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); 
  294.  
  295. if ( ! empty( $schema['properties']['parent'] ) ) { 
  296. $data['parent'] = (int) $post->post_parent; 
  297.  
  298. if ( ! empty( $schema['properties']['slug'] ) ) { 
  299. $data['slug'] = $post->post_name; 
  300.  
  301. if ( ! empty( $schema['properties']['guid'] ) ) { 
  302. $data['guid'] = array( 
  303. /** This filter is documented in wp-includes/post-template.php */ 
  304. 'rendered' => apply_filters( 'get_the_guid', $post->guid ),  
  305. 'raw' => $post->guid,  
  306. ); 
  307.  
  308. if ( ! empty( $schema['properties']['title'] ) ) { 
  309. $data['title'] = array( 
  310. 'raw' => $post->post_title,  
  311. 'rendered' => get_the_title( $post->ID ),  
  312. ); 
  313.  
  314. if ( ! empty( $schema['properties']['content'] ) ) { 
  315.  
  316. $data['content'] = array( 
  317. 'raw' => $post->post_content,  
  318. /** This filter is documented in wp-includes/post-template.php */ 
  319. 'rendered' => apply_filters( 'the_content', $post->post_content ),  
  320. ); 
  321.  
  322. if ( ! empty( $schema['properties']['excerpt'] ) ) { 
  323. $data['excerpt'] = array( 
  324. 'raw' => $post->post_excerpt,  
  325. 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ),  
  326. ); 
  327.  
  328. $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 
  329. $data = $this->add_additional_fields_to_object( $data, $request ); 
  330. $data = $this->filter_response_by_context( $data, $context ); 
  331. $response = rest_ensure_response( $data ); 
  332.  
  333. if ( ! empty( $data['parent'] ) ) { 
  334. $response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) ); 
  335.  
  336. /** 
  337. * Filters a revision returned from the API. 
  338. * Allows modification of the revision right before it is returned. 
  339. * @since 4.7.0 
  340. * @param WP_REST_Response $response The response object. 
  341. * @param WP_Post $post The original revision object. 
  342. * @param WP_REST_Request $request Request used to generate the response. 
  343. */ 
  344. return apply_filters( 'rest_prepare_revision', $response, $post, $request ); 
  345.  
  346. /** 
  347. * Checks the post_date_gmt or modified_gmt and prepare any post or 
  348. * modified date for single post output. 
  349. * @since 4.7.0 
  350. * @access protected 
  351. * @param string $date_gmt GMT publication time. 
  352. * @param string|null $date Optional. Local publication time. Default null. 
  353. * @return string|null ISO8601/RFC3339 formatted datetime, otherwise null. 
  354. */ 
  355. protected function prepare_date_response( $date_gmt, $date = null ) { 
  356. if ( '0000-00-00 00:00:00' === $date_gmt ) { 
  357. return null; 
  358.  
  359. if ( isset( $date ) ) { 
  360. return mysql_to_rfc3339( $date ); 
  361.  
  362. return mysql_to_rfc3339( $date_gmt ); 
  363.  
  364. /** 
  365. * Retrieves the revision's schema, conforming to JSON Schema. 
  366. * @since 4.7.0 
  367. * @access public 
  368. * @return array Item schema data. 
  369. */ 
  370. public function get_item_schema() { 
  371. $schema = array( 
  372. '$schema' => 'http://json-schema.org/schema#',  
  373. 'title' => "{$this->parent_post_type}-revision",  
  374. 'type' => 'object',  
  375. // Base properties for every Revision. 
  376. 'properties' => array( 
  377. 'author' => array( 
  378. 'description' => __( 'The ID for the author of the object.' ),  
  379. 'type' => 'integer',  
  380. 'context' => array( 'view', 'edit', 'embed' ),  
  381. ),  
  382. 'date' => array( 
  383. 'description' => __( "The date the object was published, in the site's timezone." ),  
  384. 'type' => 'string',  
  385. 'format' => 'date-time',  
  386. 'context' => array( 'view', 'edit', 'embed' ),  
  387. ),  
  388. 'date_gmt' => array( 
  389. 'description' => __( 'The date the object was published, as GMT.' ),  
  390. 'type' => 'string',  
  391. 'format' => 'date-time',  
  392. 'context' => array( 'view', 'edit' ),  
  393. ),  
  394. 'guid' => array( 
  395. 'description' => __( 'GUID for the object, as it exists in the database.' ),  
  396. 'type' => 'string',  
  397. 'context' => array( 'view', 'edit' ),  
  398. ),  
  399. 'id' => array( 
  400. 'description' => __( 'Unique identifier for the object.' ),  
  401. 'type' => 'integer',  
  402. 'context' => array( 'view', 'edit', 'embed' ),  
  403. ),  
  404. 'modified' => array( 
  405. 'description' => __( "The date the object was last modified, in the site's timezone." ),  
  406. 'type' => 'string',  
  407. 'format' => 'date-time',  
  408. 'context' => array( 'view', 'edit' ),  
  409. ),  
  410. 'modified_gmt' => array( 
  411. 'description' => __( 'The date the object was last modified, as GMT.' ),  
  412. 'type' => 'string',  
  413. 'format' => 'date-time',  
  414. 'context' => array( 'view', 'edit' ),  
  415. ),  
  416. 'parent' => array( 
  417. 'description' => __( 'The ID for the parent of the object.' ),  
  418. 'type' => 'integer',  
  419. 'context' => array( 'view', 'edit', 'embed' ),  
  420. ),  
  421. 'slug' => array( 
  422. 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),  
  423. 'type' => 'string',  
  424. 'context' => array( 'view', 'edit', 'embed' ),  
  425. ),  
  426. ),  
  427. ); 
  428.  
  429. $parent_schema = $this->parent_controller->get_item_schema(); 
  430.  
  431. if ( ! empty( $parent_schema['properties']['title'] ) ) { 
  432. $schema['properties']['title'] = $parent_schema['properties']['title']; 
  433.  
  434. if ( ! empty( $parent_schema['properties']['content'] ) ) { 
  435. $schema['properties']['content'] = $parent_schema['properties']['content']; 
  436.  
  437. if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { 
  438. $schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; 
  439.  
  440. if ( ! empty( $parent_schema['properties']['guid'] ) ) { 
  441. $schema['properties']['guid'] = $parent_schema['properties']['guid']; 
  442.  
  443. return $this->add_additional_fields_schema( $schema ); 
  444.  
  445. /** 
  446. * Retrieves the query params for collections. 
  447. * @since 4.7.0 
  448. * @access public 
  449. * @return array Collection parameters. 
  450. */ 
  451. public function get_collection_params() { 
  452. return array( 
  453. 'context' => $this->get_context_param( array( 'default' => 'view' ) ),  
  454. ); 
  455.  
  456. /** 
  457. * Checks the post excerpt and prepare it for single post output. 
  458. * @since 4.7.0 
  459. * @access protected 
  460. * @param string $excerpt The post excerpt. 
  461. * @param WP_Post $post Post revision object. 
  462. * @return string Prepared excerpt or empty string. 
  463. */ 
  464. protected function prepare_excerpt_response( $excerpt, $post ) { 
  465.  
  466. /** This filter is documented in wp-includes/post-template.php */ 
  467. $excerpt = apply_filters( 'the_excerpt', $excerpt, $post ); 
  468.  
  469. if ( empty( $excerpt ) ) { 
  470. return ''; 
  471.  
  472. return $excerpt;