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

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