PluginProbe
bbPress / 2.6.19
bbPress v2.6.19
2.6.19 2.6.18 2.6.17 trunk 2.0 2.0-beta-1 2.0-beta-2b 2.0-beta-3 2.0-beta-3b 2.0-rc-2 2.0-rc-3 2.0-rc-4 2.0-rc-5 2.0.1 2.0.2 2.0.3 2.1 2.1-beta-1 2.1-rc1 2.1-rc2 2.1-rc3 2.1-rc4 2.1.1 2.1.2 2.1.3 All 74 releases
bbpress / includes / common / rest.php

rest.php in bbPress 2.6.19, at includes/common/rest.php

468 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * bbPress REST API.
5 *
6 * @package bbPress
7 * @subpackage REST
8 */
9
10 // Exit if accessed directly
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Check single-user REST reads against non-forum published posts.
15 *
16 * WordPress counts all published REST post types without checking whether
17 * bbPress topics and replies belong to restricted forums.
18 *
19 * @since 2.6.19 bbPress (r7710)
20 *
21 * @param mixed $response Current REST response.
22 * @param array $handler Matched route handler.
23 * @param WP_REST_Request $request REST request.
24 * @return mixed REST response or error.
25 */
26 function bbp_filter_rest_user_discovery( $response, $handler, $request ) {
27 $callback = isset( $handler['callback'] ) ? $handler['callback'] : null;
28
29 if ( null !== $response || ! is_array( $callback ) || ! isset( $callback[0], $callback[1] ) ) {
30 return $response;
31 }
32
33 if ( ! $callback[0] instanceof WP_REST_Users_Controller || 'get_item' !== $callback[1] || ! in_array( $request->get_method(), array( 'GET', 'HEAD' ), true ) ) {
34 return $response;
35 }
36
37 $user_id = (int) $request->get_param( 'id' );
38
39 if ( $user_id <= 0 || get_current_user_id() === $user_id || current_user_can( 'list_users' ) || current_user_can( 'edit_user', $user_id ) ) {
40 return $response;
41 }
42
43 $post_types = array_values( array_diff( get_post_types( array( 'show_in_rest' => true ), 'names' ), bbp_get_post_types() ) );
44
45 if ( ! empty( $post_types ) && count_user_posts( $user_id, $post_types ) ) {
46 return $response;
47 }
48
49 return new WP_Error(
50 'rest_user_cannot_view',
51 esc_html__( 'Sorry, you are not allowed to list users.', 'bbpress' ),
52 array( 'status' => rest_authorization_required_code() )
53 );
54 }
55
56 /**
57 * REST API controller for bbPress post types.
58 *
59 * @since 2.6.17 bbPress (r7482)
60 */
61 class BBP_REST_Posts_Controller extends WP_REST_Posts_Controller {
62
63 /**
64 * Apply bbPress's strict block list before creating a topic or reply.
65 *
66 * @since 2.6.19 bbPress (r7624)
67 *
68 * @param WP_REST_Request $request Full details about the request.
69 * @return true|WP_Error True if the request has access, WP_Error otherwise.
70 */
71 public function create_item_permissions_check( $request ) {
72 $retval = parent::create_item_permissions_check( $request );
73
74 if ( is_wp_error( $retval ) || ! $retval || ! in_array( $this->post_type, array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ), true ) ) {
75 return $retval;
76 }
77
78 if ( ! bbp_check_for_moderation( array(), bbp_get_current_user_id(), $this->get_moderation_title( $request, null ), $this->get_moderation_content( $request, null ), true ) ) {
79 return new WP_Error(
80 'bbp_rest_disallowed_content',
81 esc_html__( 'This forum content cannot be created at this time.', 'bbpress' ),
82 array( 'status' => 400 )
83 );
84 }
85
86 return $retval;
87 }
88
89 /**
90 * Checks if a post can be updated.
91 *
92 * @since 2.6.17 bbPress (r7490)
93 * @since 2.6.19 bbPress (r7684) Check the topic forum on edits.
94 *
95 * @param WP_REST_Request $request Full details about the request.
96 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
97 */
98 public function update_item_permissions_check( $request ) {
99 $retval = parent::update_item_permissions_check( $request );
100
101 if ( is_wp_error( $retval ) ) {
102 return $retval;
103 }
104
105 $post = isset( $request['id'] ) ? get_post( $request['id'] ) : null;
106 if ( ! empty( $post ) && ! $this->check_read_permission( $post ) ) {
107 return new WP_Error(
108 'bbp_rest_cannot_edit_forum_content',
109 esc_html__( 'You are not allowed to edit this forum content.', 'bbpress' ),
110 array( 'status' => rest_authorization_required_code() )
111 );
112 }
113
114 // Match the front-end topic edit checks for category and closed forums.
115 if ( ! empty( $post ) && ( bbp_get_topic_post_type() === $post->post_type ) ) {
116 $topic_forum_id = bbp_get_topic_forum_id( $post->ID );
117
118 if ( bbp_is_forum_category( $topic_forum_id ) || ( bbp_is_forum_closed( $topic_forum_id ) && ! current_user_can( 'edit_forum', $topic_forum_id ) ) ) {
119 return new WP_Error(
120 'bbp_rest_cannot_edit_topic_forum',
121 esc_html__( 'You are not allowed to edit this topic in its forum.', 'bbpress' ),
122 array( 'status' => rest_authorization_required_code() )
123 );
124 }
125 }
126
127 // REST requests do not use the front-end edit query flags that enforce
128 // bbPress's edit lock in the topic and reply capability mappings.
129 if ( ! empty( $post ) && $this->is_forum_content( $post ) ) {
130 $can_moderate = current_user_can( 'moderate', $post->ID );
131
132 if ( ! $can_moderate ) {
133 // Only moderators may change status or move the edit window.
134 if ( $request->has_param( 'status' ) && ( $request['status'] !== $post->post_status ) ) {
135 return new WP_Error(
136 'bbp_rest_cannot_change_status',
137 esc_html__( 'You are not allowed to change this forum content status.', 'bbpress' ),
138 array( 'status' => rest_authorization_required_code() )
139 );
140 }
141
142 if ( $this->is_post_date_changed( $request, $post ) ) {
143 return new WP_Error(
144 'bbp_rest_cannot_change_date',
145 esc_html__( 'You are not allowed to change this forum content date.', 'bbpress' ),
146 array( 'status' => rest_authorization_required_code() )
147 );
148 }
149
150 // Pending posts may have a zero GMT date even when they are recent.
151 $post_date_gmt = ( '0000-00-00 00:00:00' === $post->post_date_gmt )
152 ? get_gmt_from_date( $post->post_date )
153 : $post->post_date_gmt;
154
155 if ( ( bbp_get_current_user_id() === (int) $post->post_author ) && bbp_past_edit_lock( $post_date_gmt ) ) {
156 return new WP_Error(
157 'bbp_rest_edit_lock',
158 esc_html__( 'You can no longer edit this forum content.', 'bbpress' ),
159 array( 'status' => rest_authorization_required_code() )
160 );
161 }
162 }
163
164 if ( ! bbp_check_for_moderation( array(), (int) $post->post_author, $this->get_moderation_title( $request, $post ), $this->get_moderation_content( $request, $post ), true ) ) {
165 return new WP_Error(
166 'bbp_rest_disallowed_content',
167 esc_html__( 'This forum content cannot be edited at this time.', 'bbpress' ),
168 array( 'status' => 400 )
169 );
170 }
171 }
172
173 $forum_id = ! empty( $post ) ? bbp_get_forum_id( $post->ID ) : 0;
174 $forum = bbp_get_forum( $forum_id );
175
176 if ( empty( $forum ) ) {
177 return $retval;
178 }
179
180 $parent_changed = $request->has_param( 'parent' ) && ( (int) $request['parent'] !== (int) $forum->post_parent );
181 $status_changed = $request->has_param( 'status' ) && ( $request['status'] !== $forum->post_status );
182 $order_changed = $request->has_param( 'menu_order' ) && ( (int) $request['menu_order'] !== (int) $forum->menu_order );
183
184 if ( ( $parent_changed || $order_changed ) && ! current_user_can( 'assign_moderators' ) ) {
185 return new WP_Error(
186 'bbp_rest_cannot_edit_forum_structure',
187 esc_html__( 'You are not allowed to change this forum structure.', 'bbpress' ),
188 array( 'status' => rest_authorization_required_code() )
189 );
190 }
191
192 if ( $status_changed && ! current_user_can( 'manage_forum_attributes', $forum_id ) ) {
193 return new WP_Error(
194 'bbp_rest_cannot_edit_forum_visibility',
195 esc_html__( 'You are not allowed to change this forum visibility.', 'bbpress' ),
196 array( 'status' => rest_authorization_required_code() )
197 );
198 }
199
200 return $retval;
201 }
202
203 /**
204 * Apply bbPress moderation to REST edits before WordPress saves the post.
205 *
206 * @since 2.6.19 bbPress (r7614)
207 *
208 * @param WP_REST_Request $request Full details about the request.
209 * @return WP_REST_Response|WP_Error Response or error from WordPress.
210 */
211 public function update_item( $request ) {
212 $post = isset( $request['id'] ) ? get_post( $request['id'] ) : null;
213
214 if ( ! empty( $post ) && $this->is_forum_content( $post ) && in_array( $post->post_status, bbp_get_public_topic_statuses(), true ) ) {
215 $title = $this->get_moderation_title( $request, $post );
216 $content = $this->get_moderation_content( $request, $post );
217
218 if ( ! bbp_check_for_moderation( array(), (int) $post->post_author, $title, $content ) ) {
219 $request->set_param( 'status', bbp_get_pending_status_id() );
220 }
221 }
222
223 return parent::update_item( $request );
224 }
225
226 /**
227 * Whether a post is a topic or reply.
228 *
229 * @since 2.6.19 bbPress (r7614)
230 *
231 * @param WP_Post $post Post to check.
232 * @return bool Whether this is forum content.
233 */
234 private function is_forum_content( $post ) {
235 return in_array( $post->post_type, array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ), true );
236 }
237
238 /**
239 * Whether a REST request changes a topic or reply publication date.
240 *
241 * @since 2.6.19 bbPress (r7614)
242 *
243 * @param WP_REST_Request $request Full details about the request.
244 * @param WP_Post $post Existing post.
245 * @return bool Whether the date would change.
246 */
247 private function is_post_date_changed( $request, $post ) {
248 $post_date_gmt = ( '0000-00-00 00:00:00' === $post->post_date_gmt )
249 ? get_gmt_from_date( $post->post_date )
250 : $post->post_date_gmt;
251
252 foreach ( array(
253 'date' => false,
254 'date_gmt' => true,
255 ) as $field => $is_gmt ) {
256 if ( ! $request->has_param( $field ) ) {
257 continue;
258 }
259
260 $dates = is_string( $request[ $field ] ) ? rest_get_date_with_gmt( $request[ $field ], $is_gmt ) : false;
261
262 if ( empty( $dates ) || ( $post->post_date !== $dates[0] ) || ( $post_date_gmt !== $dates[1] ) ) {
263 return true;
264 }
265 }
266
267 return false;
268 }
269
270 /**
271 * Get the title that bbPress moderation should check.
272 *
273 * @since 2.6.19 bbPress (r7614)
274 *
275 * @param WP_REST_Request $request Full details about the request.
276 * @param WP_Post|null $post Existing post, or null when creating.
277 * @return string Title to check.
278 */
279 private function get_moderation_title( $request, $post ) {
280 if ( ! $request->has_param( 'title' ) ) {
281 return empty( $post ) ? '' : $post->post_title;
282 }
283
284 $title = $request['title'];
285
286 return is_string( $title )
287 ? $title
288 : ( ! empty( $title['raw'] ) ? $title['raw'] : ( empty( $post ) ? '' : $post->post_title ) );
289 }
290
291 /**
292 * Get the content that bbPress moderation should check.
293 *
294 * @since 2.6.19 bbPress (r7614)
295 *
296 * @param WP_REST_Request $request Full details about the request.
297 * @param WP_Post|null $post Existing post, or null when creating.
298 * @return string Content to check.
299 */
300 private function get_moderation_content( $request, $post ) {
301 if ( ! $request->has_param( 'content' ) ) {
302 return empty( $post ) ? '' : $post->post_content;
303 }
304
305 $content = $request['content'];
306
307 return is_string( $content )
308 ? $content
309 : ( isset( $content['raw'] ) ? $content['raw'] : ( empty( $post ) ? '' : $post->post_content ) );
310 }
311
312 /**
313 * Checks if a post type is allowed for permission checks.
314 *
315 * bbPress posts may be attachment parents even when their own REST routes
316 * are disabled.
317 *
318 * @since 2.6.17 bbPress (r7482)
319 *
320 * @param WP_Post_Type|string $post_type Post type object or name.
321 * @return bool Whether the post type is allowed.
322 */
323 protected function check_is_post_type_allowed( $post_type ) {
324 if ( ! is_object( $post_type ) ) {
325 $post_type = get_post_type_object( $post_type );
326 }
327
328 $types = array( bbp_get_forum_post_type(), bbp_get_topic_post_type(), bbp_get_reply_post_type() );
329
330 if ( ! empty( $post_type ) && in_array( $post_type->name, $types, true ) ) {
331 return true;
332 }
333
334 return parent::check_is_post_type_allowed( $post_type );
335 }
336
337 /**
338 * Checks if a post can be read.
339 *
340 * @since 2.6.17 bbPress (r7482)
341 * @since 2.6.19 bbPress (r7682) Check the parent topic for replies.
342 *
343 * @param WP_Post $post Post object.
344 * @return bool Whether the post can be read.
345 */
346 public function check_read_permission( $post ) {
347 $post_type = get_post_type_object( $post->post_type );
348 if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
349 return false;
350 }
351
352 $can_read = parent::check_read_permission( $post );
353 $user_id = bbp_get_current_user_id();
354 $password_parent_id = 0;
355
356 // Get the forum ID for this post
357 switch ( $post->post_type ) {
358 case bbp_get_forum_post_type() :
359 $forum_id = $post->ID;
360 $password_parent_id = bbp_get_forum_parent_id( $post->ID );
361 break;
362
363 case bbp_get_topic_post_type() :
364 $forum_id = bbp_get_topic_forum_id( $post->ID );
365 $password_parent_id = $forum_id;
366 break;
367
368 case bbp_get_reply_post_type() :
369 $forum_id = bbp_get_reply_forum_id( $post->ID );
370 $password_parent_id = bbp_get_reply_topic_id( $post->ID );
371 break;
372
373 default :
374 $forum_id = 0;
375 break;
376 }
377
378 // Check access to restricted forums and their ancestors
379 $restricted = ! empty( $forum_id ) && bbp_is_forum_restricted( $forum_id, true );
380 $moderator = $restricted && is_user_logged_in() && bbp_is_user_forum_moderator( $user_id, $forum_id );
381
382 // Allow filtered moderators to read the restricted forum object
383 $moderator_can_read = $moderator
384 && ( bbp_get_forum_post_type() === $post->post_type )
385 && in_array( $post->post_status, array( bbp_get_private_status_id(), bbp_get_hidden_status_id() ), true );
386
387 if ( ! $can_read && ! $moderator_can_read ) {
388 return false;
389 }
390
391 if ( bbp_is_forum_restricted_for_user( $forum_id, $user_id ) ) {
392 return false;
393 }
394
395 // WordPress checks only the requested object's password. bbPress forum
396 // content also inherits password requirements from its parents.
397 if ( ! empty( $password_parent_id ) && bbp_get_password_required_id( $password_parent_id ) ) {
398 return false;
399 }
400
401 // A public reply cannot expose an unreadable topic
402 if ( bbp_get_reply_post_type() === $post->post_type ) {
403 $topic_id = bbp_get_reply_topic_id( $post->ID );
404 $topic = ! empty( $topic_id ) ? bbp_get_topic( $topic_id ) : null;
405
406 if ( empty( $topic ) || ! $this->check_read_permission( $topic ) ) {
407 return false;
408 }
409 }
410
411 return true;
412 }
413 }
414
415 /**
416 * REST API controller for attachments to bbPress post types.
417 *
418 * @since 2.6.17 bbPress (r7482)
419 */
420 class BBP_REST_Attachments_Controller extends WP_REST_Attachments_Controller {
421
422 /**
423 * Checks if an attachment can be read.
424 *
425 * @since 2.6.17 bbPress (r7482)
426 *
427 * @param WP_Post $post Attachment post object.
428 * @return bool Whether the attachment can be read.
429 */
430 public function check_read_permission( $post ) {
431 $post_type = get_post_type_object( $post->post_type );
432 if ( ! $this->check_is_post_type_allowed( $post_type ) ) {
433 return false;
434 }
435
436 // Inherit bbPress permissions from the attachment parent
437 if ( ( 'inherit' === $post->post_status ) && ! empty( $post->post_parent ) ) {
438 $parent = get_post( $post->post_parent );
439 $types = array( bbp_get_forum_post_type(), bbp_get_topic_post_type(), bbp_get_reply_post_type() );
440
441 if ( ! empty( $parent ) && in_array( $parent->post_type, $types, true ) ) {
442 if ( bbp_get_password_required_id( $parent->ID ) ) {
443 return false;
444 }
445
446 $controller = new BBP_REST_Posts_Controller( $parent->post_type );
447
448 return $controller->check_read_permission( $parent );
449 }
450 }
451
452 return parent::check_read_permission( $post );
453 }
454 }
455
456 /**
457 * Use the bbPress controller for attachments when Core's is unchanged.
458 *
459 * @since 2.6.17 bbPress (r7482)
460 */
461 function bbp_register_rest_attachment_controller() {
462 $post_type = get_post_type_object( 'attachment' );
463
464 if ( ! empty( $post_type ) && ( 'WP_REST_Attachments_Controller' === $post_type->rest_controller_class ) ) {
465 $post_type->rest_controller_class = 'BBP_REST_Attachments_Controller';
466 }
467 }
468