1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
class WP_REST_Revisions_Controller extends WP_REST_Controller { |
4
|
|
|
|
5
|
|
|
private $parent_post_type; |
6
|
|
|
private $parent_controller; |
7
|
|
|
private $parent_base; |
8
|
|
|
|
9
|
|
|
public function __construct( $parent_post_type ) { |
10
|
|
|
$this->parent_post_type = $parent_post_type; |
11
|
|
|
$this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type ); |
12
|
|
|
$this->namespace = 'wp/v2'; |
13
|
|
|
$this->rest_base = 'revisions'; |
14
|
|
|
$post_type_object = get_post_type_object( $parent_post_type ); |
15
|
|
|
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; |
16
|
|
|
} |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Register routes for revisions based on post types supporting revisions |
20
|
|
|
*/ |
21
|
|
|
public function register_routes() { |
22
|
|
|
|
23
|
|
|
register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array( |
24
|
|
|
array( |
25
|
|
|
'methods' => WP_REST_Server::READABLE, |
26
|
|
|
'callback' => array( $this, 'get_items' ), |
27
|
|
|
'permission_callback' => array( $this, 'get_items_permissions_check' ), |
28
|
|
|
'args' => $this->get_collection_params(), |
29
|
|
|
), |
30
|
|
|
'schema' => array( $this, 'get_public_item_schema' ), |
31
|
|
|
) ); |
32
|
|
|
|
33
|
|
|
register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array( |
34
|
|
|
array( |
35
|
|
|
'methods' => WP_REST_Server::READABLE, |
36
|
|
|
'callback' => array( $this, 'get_item' ), |
37
|
|
|
'permission_callback' => array( $this, 'get_item_permissions_check' ), |
38
|
|
|
'args' => array( |
39
|
|
|
'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
40
|
|
|
), |
41
|
|
|
), |
42
|
|
|
array( |
43
|
|
|
'methods' => WP_REST_Server::DELETABLE, |
44
|
|
|
'callback' => array( $this, 'delete_item' ), |
45
|
|
|
'permission_callback' => array( $this, 'delete_item_permissions_check' ), |
46
|
|
|
), |
47
|
|
|
'schema' => array( $this, 'get_public_item_schema' ), |
48
|
|
|
)); |
49
|
|
|
|
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Check if a given request has access to get revisions |
54
|
|
|
* |
55
|
|
|
* @param WP_REST_Request $request Full data about the request. |
56
|
|
|
* @return WP_Error|boolean |
57
|
|
|
*/ |
58
|
|
|
public function get_items_permissions_check( $request ) { |
59
|
|
|
|
60
|
|
|
$parent = $this->get_post( $request['parent'] ); |
61
|
|
|
if ( ! $parent ) { |
62
|
|
|
return true; |
63
|
|
|
} |
64
|
|
|
$parent_post_type_obj = get_post_type_object( $parent->post_type ); |
65
|
|
|
if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) { |
66
|
|
|
return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) ); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
return true; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Get a collection of revisions |
74
|
|
|
* |
75
|
|
|
* @param WP_REST_Request $request Full data about the request. |
76
|
|
|
* @return WP_Error|WP_REST_Response |
77
|
|
|
*/ |
78
|
|
|
public function get_items( $request ) { |
79
|
|
|
|
80
|
|
|
$parent = $this->get_post( $request['parent'] ); |
81
|
|
View Code Duplication |
if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) { |
82
|
|
|
return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) ); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
$revisions = wp_get_post_revisions( $request['parent'] ); |
86
|
|
|
|
87
|
|
|
$response = array(); |
88
|
|
|
foreach ( $revisions as $revision ) { |
89
|
|
|
$data = $this->prepare_item_for_response( $revision, $request ); |
90
|
|
|
$response[] = $this->prepare_response_for_collection( $data ); |
91
|
|
|
} |
92
|
|
|
return rest_ensure_response( $response ); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Check if a given request has access to get a specific revision |
97
|
|
|
* |
98
|
|
|
* @param WP_REST_Request $request Full data about the request. |
99
|
|
|
* @return WP_Error|boolean |
100
|
|
|
*/ |
101
|
|
|
public function get_item_permissions_check( $request ) { |
102
|
|
|
return $this->get_items_permissions_check( $request ); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Get one revision from the collection |
107
|
|
|
* |
108
|
|
|
* @param WP_REST_Request $request Full data about the request. |
109
|
|
|
* @return WP_Error|array |
110
|
|
|
*/ |
111
|
|
|
public function get_item( $request ) { |
112
|
|
|
|
113
|
|
|
$parent = $this->get_post( $request['parent'] ); |
114
|
|
View Code Duplication |
if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) { |
115
|
|
|
return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) ); |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$revision = $this->get_post( $request['id'] ); |
119
|
|
|
if ( ! $revision || 'revision' !== $revision->post_type ) { |
120
|
|
|
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) ); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$response = $this->prepare_item_for_response( $revision, $request ); |
124
|
|
|
return rest_ensure_response( $response ); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Check if a given request has access to delete a revision |
129
|
|
|
* |
130
|
|
|
* @param WP_REST_Request $request Full details about the request. |
131
|
|
|
* @return WP_Error|boolean |
132
|
|
|
*/ |
133
|
|
View Code Duplication |
public function delete_item_permissions_check( $request ) { |
134
|
|
|
|
135
|
|
|
$response = $this->get_items_permissions_check( $request ); |
136
|
|
|
if ( ! $response || is_wp_error( $response ) ) { |
137
|
|
|
return $response; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
$post = $this->get_post( $request['id'] ); |
141
|
|
|
if ( ! $post ) { |
142
|
|
|
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) ); |
143
|
|
|
} |
144
|
|
|
$post_type = get_post_type_object( 'revision' ); |
145
|
|
|
return current_user_can( $post_type->cap->delete_post, $post->ID ); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Delete a single revision |
150
|
|
|
* |
151
|
|
|
* @param WP_REST_Request $request Full details about the request |
152
|
|
|
* @return WP_Error|boolean |
153
|
|
|
*/ |
154
|
|
|
public function delete_item( $request ) { |
155
|
|
|
$result = wp_delete_post( $request['id'], true ); |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Fires after a revision is deleted via the REST API. |
159
|
|
|
* |
160
|
|
|
* @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully) |
161
|
|
|
* or false (failure). If the revision was moved to to the trash, $result represents |
162
|
|
|
* its new state; if it was deleted, $result represents its state before deletion. |
163
|
|
|
* @param WP_REST_Request $request The request sent to the API. |
164
|
|
|
*/ |
165
|
|
|
do_action( 'rest_delete_revision', $result, $request ); |
166
|
|
|
|
167
|
|
|
if ( $result ) { |
168
|
|
|
return true; |
|
|
|
|
169
|
|
|
} else { |
170
|
|
|
return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) ); |
171
|
|
|
} |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* Prepare the revision for the REST response |
176
|
|
|
* |
177
|
|
|
* @param WP_Post $post Post revision object. |
178
|
|
|
* @param WP_REST_Request $request Request object. |
179
|
|
|
* @return WP_REST_Response $response |
180
|
|
|
*/ |
181
|
|
|
public function prepare_item_for_response( $post, $request ) { |
182
|
|
|
|
183
|
|
|
$schema = $this->get_item_schema(); |
184
|
|
|
|
185
|
|
|
$data = array(); |
186
|
|
|
|
187
|
|
|
if ( ! empty( $schema['properties']['author'] ) ) { |
188
|
|
|
$data['author'] = $post->post_author; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
View Code Duplication |
if ( ! empty( $schema['properties']['date'] ) ) { |
192
|
|
|
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date ); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if ( ! empty( $schema['properties']['date_gmt'] ) ) { |
196
|
|
|
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt ); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if ( ! empty( $schema['properties']['id'] ) ) { |
200
|
|
|
$data['id'] = $post->ID; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
View Code Duplication |
if ( ! empty( $schema['properties']['modified'] ) ) { |
204
|
|
|
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ); |
205
|
|
|
} |
206
|
|
|
|
207
|
|
View Code Duplication |
if ( ! empty( $schema['properties']['modified_gmt'] ) ) { |
208
|
|
|
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt ); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if ( ! empty( $schema['properties']['parent'] ) ) { |
212
|
|
|
$data['parent'] = (int) $post->post_parent; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
if ( ! empty( $schema['properties']['slug'] ) ) { |
216
|
|
|
$data['slug'] = $post->post_name; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
View Code Duplication |
if ( ! empty( $schema['properties']['guid'] ) ) { |
220
|
|
|
$data['guid'] = array( |
221
|
|
|
/** This filter is documented in wp-includes/post-template.php */ |
222
|
|
|
'rendered' => apply_filters( 'get_the_guid', $post->guid ), |
223
|
|
|
'raw' => $post->guid, |
224
|
|
|
); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
if ( ! empty( $schema['properties']['title'] ) ) { |
228
|
|
|
$data['title'] = array( |
229
|
|
|
'raw' => $post->post_title, |
230
|
|
|
'rendered' => get_the_title( $post->ID ), |
231
|
|
|
); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
View Code Duplication |
if ( ! empty( $schema['properties']['content'] ) ) { |
235
|
|
|
|
236
|
|
|
$data['content'] = array( |
237
|
|
|
'raw' => $post->post_content, |
238
|
|
|
/** This filter is documented in wp-includes/post-template.php */ |
239
|
|
|
'rendered' => apply_filters( 'the_content', $post->post_content ), |
240
|
|
|
); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
if ( ! empty( $schema['properties']['excerpt'] ) ) { |
244
|
|
|
$data['excerpt'] = array( |
245
|
|
|
'raw' => $post->post_excerpt, |
246
|
|
|
'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ), |
247
|
|
|
); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
$context = ! empty( $request['context'] ) ? $request['context'] : 'view'; |
251
|
|
|
$data = $this->add_additional_fields_to_object( $data, $request ); |
252
|
|
|
$data = $this->filter_response_by_context( $data, $context ); |
253
|
|
|
$response = rest_ensure_response( $data ); |
254
|
|
|
|
255
|
|
|
if ( ! empty( $data['parent'] ) ) { |
256
|
|
|
$response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) ); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Filter a revision returned from the API. |
261
|
|
|
* |
262
|
|
|
* Allows modification of the revision right before it is returned. |
263
|
|
|
* |
264
|
|
|
* @param WP_REST_Response $response The response object. |
265
|
|
|
* @param WP_Post $post The original revision object. |
266
|
|
|
* @param WP_REST_Request $request Request used to generate the response. |
267
|
|
|
*/ |
268
|
|
|
return apply_filters( 'rest_prepare_revision', $response, $post, $request ); |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Check the post_date_gmt or modified_gmt and prepare any post or |
273
|
|
|
* modified date for single post output. |
274
|
|
|
* |
275
|
|
|
* @param string $date_gmt |
276
|
|
|
* @param string|null $date |
277
|
|
|
* @return string|null ISO8601/RFC3339 formatted datetime. |
278
|
|
|
*/ |
279
|
|
View Code Duplication |
protected function prepare_date_response( $date_gmt, $date = null ) { |
280
|
|
|
if ( '0000-00-00 00:00:00' === $date_gmt ) { |
281
|
|
|
return null; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
if ( isset( $date ) ) { |
285
|
|
|
return mysql_to_rfc3339( $date ); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
return mysql_to_rfc3339( $date_gmt ); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
/** |
292
|
|
|
* Get the revision's schema, conforming to JSON Schema |
293
|
|
|
* |
294
|
|
|
* @return array |
295
|
|
|
*/ |
296
|
|
|
public function get_item_schema() { |
297
|
|
|
$schema = array( |
298
|
|
|
'$schema' => 'http://json-schema.org/draft-04/schema#', |
299
|
|
|
'title' => "{$this->parent_post_type}-revision", |
300
|
|
|
'type' => 'object', |
301
|
|
|
/* |
302
|
|
|
* Base properties for every Revision |
303
|
|
|
*/ |
304
|
|
|
'properties' => array( |
305
|
|
|
'author' => array( |
306
|
|
|
'description' => __( 'The id for the author of the object.' ), |
307
|
|
|
'type' => 'integer', |
308
|
|
|
'context' => array( 'view', 'edit', 'embed' ), |
309
|
|
|
), |
310
|
|
|
'date' => array( |
311
|
|
|
'description' => __( 'The date the object was published.' ), |
312
|
|
|
'type' => 'string', |
313
|
|
|
'format' => 'date-time', |
314
|
|
|
'context' => array( 'view', 'edit', 'embed' ), |
315
|
|
|
), |
316
|
|
|
'date_gmt' => array( |
317
|
|
|
'description' => __( 'The date the object was published, as GMT.' ), |
318
|
|
|
'type' => 'string', |
319
|
|
|
'format' => 'date-time', |
320
|
|
|
'context' => array( 'view', 'edit' ), |
321
|
|
|
), |
322
|
|
|
'guid' => array( |
323
|
|
|
'description' => __( 'GUID for the object, as it exists in the database.' ), |
324
|
|
|
'type' => 'string', |
325
|
|
|
'context' => array( 'view', 'edit' ), |
326
|
|
|
), |
327
|
|
|
'id' => array( |
328
|
|
|
'description' => __( 'Unique identifier for the object.' ), |
329
|
|
|
'type' => 'integer', |
330
|
|
|
'context' => array( 'view', 'edit', 'embed' ), |
331
|
|
|
), |
332
|
|
|
'modified' => array( |
333
|
|
|
'description' => __( 'The date the object was last modified.' ), |
334
|
|
|
'type' => 'string', |
335
|
|
|
'format' => 'date-time', |
336
|
|
|
'context' => array( 'view', 'edit' ), |
337
|
|
|
), |
338
|
|
|
'modified_gmt' => array( |
339
|
|
|
'description' => __( 'The date the object was last modified, as GMT.' ), |
340
|
|
|
'type' => 'string', |
341
|
|
|
'format' => 'date-time', |
342
|
|
|
'context' => array( 'view', 'edit' ), |
343
|
|
|
), |
344
|
|
|
'parent' => array( |
345
|
|
|
'description' => __( 'The id for the parent of the object.' ), |
346
|
|
|
'type' => 'integer', |
347
|
|
|
'context' => array( 'view', 'edit', 'embed' ), |
348
|
|
|
), |
349
|
|
|
'slug' => array( |
350
|
|
|
'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), |
351
|
|
|
'type' => 'string', |
352
|
|
|
'context' => array( 'view', 'edit', 'embed' ), |
353
|
|
|
), |
354
|
|
|
), |
355
|
|
|
); |
356
|
|
|
|
357
|
|
|
$parent_schema = $this->parent_controller->get_item_schema(); |
358
|
|
|
|
359
|
|
View Code Duplication |
if ( ! empty( $parent_schema['properties']['title'] ) ) { |
360
|
|
|
$schema['properties']['title'] = $parent_schema['properties']['title']; |
361
|
|
|
} |
362
|
|
View Code Duplication |
if ( ! empty( $parent_schema['properties']['content'] ) ) { |
363
|
|
|
$schema['properties']['content'] = $parent_schema['properties']['content']; |
364
|
|
|
} |
365
|
|
View Code Duplication |
if ( ! empty( $parent_schema['properties']['excerpt'] ) ) { |
366
|
|
|
$schema['properties']['excerpt'] = $parent_schema['properties']['excerpt']; |
367
|
|
|
} |
368
|
|
View Code Duplication |
if ( ! empty( $parent_schema['properties']['guid'] ) ) { |
369
|
|
|
$schema['properties']['guid'] = $parent_schema['properties']['guid']; |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
return $this->add_additional_fields_schema( $schema ); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
/** |
376
|
|
|
* Get the query params for collections |
377
|
|
|
* |
378
|
|
|
* @return array |
379
|
|
|
*/ |
380
|
|
|
public function get_collection_params() { |
381
|
|
|
return array( |
382
|
|
|
'context' => $this->get_context_param( array( 'default' => 'view' ) ), |
383
|
|
|
); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Check the post excerpt and prepare it for single post output. |
388
|
|
|
* |
389
|
|
|
* @param string $excerpt |
390
|
|
|
* @return string|null $excerpt |
391
|
|
|
*/ |
392
|
|
|
protected function prepare_excerpt_response( $excerpt, $post ) { |
393
|
|
|
|
394
|
|
|
/** This filter is documented in wp-includes/post-template.php */ |
395
|
|
|
$excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt, $post ) ); |
396
|
|
|
|
397
|
|
|
if ( empty( $excerpt ) ) { |
398
|
|
|
return ''; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
return $excerpt; |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
} |
405
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.