1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Class WP_REST_React_Controller |
5
|
|
|
*/ |
6
|
|
|
class WP_REST_React_Controller extends WP_REST_Controller { |
7
|
|
|
/** |
8
|
|
|
* The namespace of this controller's route. |
9
|
|
|
* |
10
|
|
|
* @var string |
11
|
|
|
*/ |
12
|
|
|
public $namespace; |
13
|
|
|
|
14
|
|
|
/** |
15
|
|
|
* The base of this controller's route. |
16
|
|
|
* |
17
|
|
|
* @var string |
18
|
|
|
*/ |
19
|
|
|
public $rest_base; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Constructor. |
23
|
|
|
*/ |
24
|
|
|
public function __construct() { |
25
|
|
|
$this->namespace = 'wp/v2'; |
26
|
|
|
$this->rest_base = 'react'; |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Register the routes for the objects of the controller. |
31
|
|
|
*/ |
32
|
|
|
public function register_routes() { |
33
|
|
|
register_rest_route( $this->namespace, $this->rest_base, array( |
34
|
|
|
array( |
35
|
|
|
'methods' => WP_Rest_Server::READABLE, |
36
|
|
|
'callback' => array( $this, 'get_items' ), |
37
|
|
|
'permission_callback' => array( $this, 'get_items_permissions_check' ), |
38
|
|
|
'args' => $this->get_collection_params(), |
39
|
|
|
), |
40
|
|
|
array( |
41
|
|
|
'methods' => WP_Rest_Server::CREATABLE, |
42
|
|
|
'callback' => array( $this, 'create_item' ), |
43
|
|
|
'permission_callback' => array( $this, 'create_item_permissions_check' ), |
44
|
|
|
'args' => $this->get_creation_params(), |
45
|
|
|
), |
46
|
|
|
'schema' => array( $this, 'get_public_item_schema' ), |
47
|
|
|
) ); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Check if a given request has access to read reactions. |
52
|
|
|
* |
53
|
|
|
* @param WP_REST_Request $request Full details about the request. |
54
|
|
|
* @return WP_Error|boolean |
55
|
|
|
*/ |
56
|
|
|
public function get_items_permissions_check( $request ) { |
57
|
|
|
if ( ! empty( $request['post'] ) ) { |
58
|
|
|
foreach ( (array) $request['post'] as $post_id ) { |
59
|
|
|
$post = get_post( $post_id ); |
60
|
|
|
if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post ) ) { |
61
|
|
|
return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this reaction.' ), array( 'status' => rest_authorization_required_code() ) ); |
62
|
|
|
} else if ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { |
63
|
|
|
return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read reactions without a post.' ), array( 'status' => rest_authorization_required_code() ) ); |
64
|
|
|
} |
65
|
|
|
} |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
return true; |
|
|
|
|
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Get a list of reactions. |
73
|
|
|
* |
74
|
|
|
* @param WP_REST_Request $request Full details about the request. |
75
|
|
|
* @return WP_Error|WP_REST_Response |
76
|
|
|
*/ |
77
|
|
|
public function get_items( $request ) { |
78
|
|
|
$prepared_args = array( |
79
|
|
|
'post__in' => $request['post'], |
80
|
|
|
'type' => 'reaction', |
81
|
|
|
); |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Filter arguments, before passing to WP_Comment_Query, when querying reactions via the REST API. |
85
|
|
|
* |
86
|
|
|
* @see https://developer.wordpress.org/reference/classes/wp_comment_query/ |
87
|
|
|
* |
88
|
|
|
* @param array $prepared_args Array of arguments for WP_Comment_Query. |
89
|
|
|
* @param WP_REST_Request $request The current request. |
90
|
|
|
*/ |
91
|
|
|
$prepared_args = apply_filters( 'rest_reaction_query', $prepared_args, $request ); |
92
|
|
|
|
93
|
|
|
$query = new WP_Comment_Query; |
94
|
|
|
$query_result = $query->query( $prepared_args ); |
95
|
|
|
|
96
|
|
|
$reactions_count = array(); |
97
|
|
|
foreach ( $query_result as $reaction ) { |
98
|
|
|
if ( empty( $reactions_count[ $reaction->comment_content ] ) ) { |
99
|
|
|
$reactions_count[ $reaction->comment_content ] = array( |
100
|
|
|
'count' => 0, |
101
|
|
|
'post_id' => $reaction->comment_post_ID, |
102
|
|
|
); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
$reactions_count[ $reaction->comment_content ]++; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
$reactions = array(); |
109
|
|
|
foreach ( $reactions_count as $emoji => $data ) { |
110
|
|
|
$reaction = array( |
111
|
|
|
'emoji' => $emoji, |
112
|
|
|
'count' => $data['count'], |
113
|
|
|
'post_id' => $data['post_id'], |
114
|
|
|
); |
115
|
|
|
|
116
|
|
|
$data = $this->prepare_item_for_response( $reaction, $request ); |
117
|
|
|
$reactions[] = $this->prepare_response_for_collection( $data ); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
$total_reactions = (int) $query->found_comments; |
121
|
|
|
$reaction_groups = count( $reactions ); |
122
|
|
|
|
123
|
|
|
$response = rest_ensure_response( $reactions ); |
124
|
|
|
$response->header( 'X-WP-Total', $total_reactions ); |
125
|
|
|
$response->header( 'X-WP-TotalGroups', $reaction_groups ); |
126
|
|
|
|
127
|
|
|
return $response; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Check if a given request has access to create a reaction |
132
|
|
|
* |
133
|
|
|
* @param WP_REST_Request $request Full details about the request. |
134
|
|
|
* @return WP_Error|boolean |
135
|
|
|
*/ |
136
|
|
|
public function create_item_permissions_check( $request ) { |
137
|
|
|
if ( ! empty( $request['post'] ) && $post = get_post( (int) $request['post'] ) ) { |
138
|
|
|
if ( ! $this->check_read_post_permission( $post ) ) { |
139
|
|
|
return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this reaction.' ), array( 'status' => rest_authorization_required_code() ) ); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
if ( ! comments_open( $post->ID ) ) { |
143
|
|
|
return new WP_Error( 'rest_reactions_closed', __( 'Sorry, reactions are closed on this post.' ), array( 'status' => 403 ) ); |
144
|
|
|
} |
145
|
|
|
} |
146
|
|
|
return true; |
|
|
|
|
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Create a reaction. |
151
|
|
|
* |
152
|
|
|
* @param WP_REST_Request $request Full details about the request. |
153
|
|
|
* @return WP_Error|WP_REST_Response |
154
|
|
|
*/ |
155
|
|
|
public function create_item( $request ) { |
156
|
|
|
$comment = array( |
157
|
|
|
'comment_content' => $request['emoji'], |
158
|
|
|
'comment_post_ID' => $request['post'], |
159
|
|
|
'comment_type' => 'reaction', |
160
|
|
|
); |
161
|
|
|
|
162
|
|
|
wp_insert_comment( $comment ); |
163
|
|
|
|
164
|
|
|
return $this->get_items( $request ); |
|
|
|
|
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* Check if we can read a post. |
169
|
|
|
* |
170
|
|
|
* Correctly handles posts with the inherit status. |
171
|
|
|
* |
172
|
|
|
* @param object $post Post object. |
173
|
|
|
* @return boolean Can we read it? |
174
|
|
|
*/ |
175
|
|
|
public function check_read_post_permission( $post ) { |
176
|
|
|
$posts_controller = new WP_REST_Posts_Controller( $post->post_type ); |
177
|
|
|
|
178
|
|
|
return $posts_controller->check_read_permission( $post ); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Prepare a reaction group output for response. |
183
|
|
|
* |
184
|
|
|
* @param array $reaction Reaction data. |
185
|
|
|
* @param WP_REST_Request $request Request object. |
186
|
|
|
* @return WP_REST_Response $response |
187
|
|
|
*/ |
188
|
|
|
public function prepare_item_for_response( $reaction, $request ) { |
189
|
|
|
$data = array( |
190
|
|
|
'emoji' => $reaction['emoji'], |
191
|
|
|
'count' => (int) $reaction['count'], |
192
|
|
|
'post_id' => (int) $reaction['post_id'], |
193
|
|
|
); |
194
|
|
|
|
195
|
|
|
// Wrap the data in a response object |
196
|
|
|
$response = rest_ensure_response( $data ); |
197
|
|
|
|
198
|
|
|
$response->add_links( $this->prepare_links( $reaction ) ); |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Filter a reaction group returned from the API. |
202
|
|
|
* |
203
|
|
|
* Allows modification of the reaction right before it is returned. |
204
|
|
|
* |
205
|
|
|
* @param WP_REST_Response $response The response object. |
206
|
|
|
* @param array $reaction The original reaction data. |
207
|
|
|
* @param WP_REST_Request $request Request used to generate the response. |
208
|
|
|
*/ |
209
|
|
|
return apply_filters( 'rest_prepare_comment', $response, $reaction, $request ); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Prepare a response for inserting into a collection. |
214
|
|
|
* |
215
|
|
|
* @param WP_REST_Response $response Response object. |
216
|
|
|
* @return array Response data, ready for insertion into collection data. |
217
|
|
|
*/ |
218
|
|
|
public function prepare_response_for_collection( $response ) { |
219
|
|
|
if ( ! ( $response instanceof WP_REST_Response ) ) { |
|
|
|
|
220
|
|
|
return $response; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
$data = (array) $response->get_data(); |
224
|
|
|
$links = WP_REST_Server::get_response_links( $response ); |
225
|
|
|
if ( ! empty( $links ) ) { |
226
|
|
|
$data['_links'] = $links; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
return $data; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** |
233
|
|
|
* Prepare links for the request. |
234
|
|
|
* |
235
|
|
|
* @param array $reaction Reaction. |
236
|
|
|
* @return array Links for the given reaction. |
237
|
|
|
*/ |
238
|
|
|
protected function prepare_links( $reaction ) { |
239
|
|
|
$links = array( |
240
|
|
|
'self' => array( |
241
|
|
|
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $reaction['emoji'] ) ), |
242
|
|
|
), |
243
|
|
|
'collection' => array( |
244
|
|
|
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ), |
245
|
|
|
), |
246
|
|
|
); |
247
|
|
|
|
248
|
|
|
if ( 0 !== (int) $reaction['post_id'] ) { |
249
|
|
|
$post = get_post( $reaction['post_id'] ); |
250
|
|
|
if ( ! empty( $post->ID ) ) { |
251
|
|
|
$obj = get_post_type_object( $post->post_type ); |
252
|
|
|
$base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; |
253
|
|
|
$links['up'] = array( |
254
|
|
|
'href' => rest_url( '/wp/v2/' . $base . '/' . $reaction['post_id'] ), |
255
|
|
|
'embeddable' => true, |
256
|
|
|
'post_type' => $post->post_type, |
257
|
|
|
); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
return $links; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Get the query params for collections |
266
|
|
|
* |
267
|
|
|
* @return array |
268
|
|
|
*/ |
269
|
|
|
public function get_collection_params() { |
270
|
|
|
$query_params = array(); |
271
|
|
|
|
272
|
|
|
$query_params['post'] = array( |
273
|
|
|
'default' => array(), |
274
|
|
|
'description' => __( 'Limit result set to resources assigned to specific post ids.' ), |
275
|
|
|
'type' => 'array', |
276
|
|
|
'sanitize_callback' => 'wp_parse_id_list', |
277
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
278
|
|
|
); |
279
|
|
|
|
280
|
|
|
return $query_params; |
281
|
|
|
} |
282
|
|
|
/** |
283
|
|
|
* Get the query params for collections |
284
|
|
|
* |
285
|
|
|
* @return array |
286
|
|
|
*/ |
287
|
|
|
public function get_creation_params() { |
288
|
|
|
$query_params = array(); |
289
|
|
|
|
290
|
|
|
$query_params['post'] = array( |
291
|
|
|
'default' => array(), |
292
|
|
|
'description' => __( 'The post ID to add a reaction to.' ), |
293
|
|
|
'type' => 'integer', |
294
|
|
|
'sanitize_callback' => 'absint', |
295
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
296
|
|
|
); |
297
|
|
|
|
298
|
|
|
$query_params['emoji'] = array( |
299
|
|
|
'default' => array(), |
300
|
|
|
'description' => __( 'The reaction emoji.' ), |
301
|
|
|
'type' => 'string', |
302
|
|
|
'validate_callback' => 'rest_validate_request_arg', |
303
|
|
|
); |
304
|
|
|
|
305
|
|
|
return $query_params; |
306
|
|
|
} |
307
|
|
|
} |
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.