PluginProbe
bbPress / trunk
bbPress vtrunk
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 2.2 2.2.1 2.2.2 All 71 releases
bbpress / includes / replies / functions.php

functions.php in bbPress trunk, at includes/replies/functions.php

2,602 lines 76.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * bbPress Reply Functions
5 *
6 * @package bbPress
7 * @subpackage Functions
8 */
9
10 // Exit if accessed directly
11 defined( 'ABSPATH' ) || exit;
12
13 /** Insert ********************************************************************/
14
15 /**
16 * A wrapper for wp_insert_post() that also includes the necessary meta values
17 * for the reply to function properly.
18 *
19 * @since 2.0.0 bbPress (r3349)
20 *
21 * @param array $reply_data Forum post data.
22 * @param array $reply_meta Forum meta data.
23 *
24 * @return int|false Reply ID on success, false on failure.
25 */
26 function bbp_insert_reply( $reply_data = array(), $reply_meta = array() ) {
27
28 // Parse arguments against default values
29 $reply_data = bbp_parse_args(
30 $reply_data,
31 array(
32 'post_parent' => 0, // topic ID
33 'post_type' => bbp_get_reply_post_type(),
34 'post_author' => bbp_get_current_user_id(),
35 'post_password' => '',
36 'post_content' => '',
37 'post_title' => '',
38 'menu_order' => bbp_get_topic_reply_count( $reply_data['post_parent'], true ) + 1,
39 'comment_status' => 'closed'
40 ),
41 'insert_reply'
42 );
43
44 // Possibly override status based on parent topic
45 if ( ! empty( $reply_data['post_parent'] ) && empty( $reply_data['post_status'] ) ) {
46 $reply_data['post_status'] = bbp_get_topic_status( $reply_data['post_parent'] );
47 }
48
49 // Insert reply
50 $reply_id = wp_insert_post( $reply_data, false );
51
52 // Bail if no reply was added
53 if ( empty( $reply_id ) ) {
54 return false;
55 }
56
57 // Parse arguments against default values
58 $reply_meta = bbp_parse_args(
59 $reply_meta,
60 array(
61 'author_ip' => bbp_current_author_ip(),
62 'forum_id' => 0,
63 'topic_id' => 0,
64 'reply_to' => 0
65 ),
66 'insert_reply_meta'
67 );
68
69 // Insert reply meta
70 foreach ( $reply_meta as $meta_key => $meta_value ) {
71
72 // Prefix if not prefixed
73 if ( '_bbp_' !== substr( $meta_key, 0, 5 ) ) {
74 $meta_key = '_bbp_' . $meta_key;
75 }
76
77 // Update the meta
78 update_post_meta( $reply_id, $meta_key, $meta_value );
79 }
80
81 // Update the reply and hierarchy
82 bbp_update_reply( $reply_id, $reply_meta['topic_id'], $reply_meta['forum_id'], array(), $reply_data['post_author'], false, $reply_meta['reply_to'] );
83
84 /**
85 * Fires after reply has been inserted via `bbp_insert_reply`.
86 *
87 * @since 2.6.0 bbPress (r6036)
88 *
89 * @param int $reply_id The reply id.
90 * @param int $reply_meta['topic_id'] The reply topic meta.
91 * @param int $reply_meta['forum_id'] The reply forum meta.
92 */
93 do_action( 'bbp_insert_reply', (int) $reply_id, (int) $reply_meta['topic_id'], (int) $reply_meta['forum_id'] );
94
95 // Return reply_id
96 return $reply_id;
97 }
98
99 /**
100 * Update counts after a reply is inserted via `bbp_insert_reply`.
101 *
102 * @since 2.6.0 bbPress (r6036)
103 *
104 * @param int $reply_id The reply id.
105 * @param int $topic_id The topic id.
106 * @param int $forum_id The forum id.
107 */
108 function bbp_insert_reply_update_counts( $reply_id = 0, $topic_id = 0, $forum_id = 0 ) {
109
110 // If the reply is public, update the reply counts.
111 if ( bbp_is_reply_published( $reply_id ) ) {
112 bbp_increase_topic_reply_count( $topic_id );
113 bbp_increase_forum_reply_count( $forum_id );
114
115 // If the reply isn't public only update the reply hidden counts.
116 } else {
117 bbp_increase_topic_reply_count_hidden( $topic_id );
118 bbp_increase_forum_reply_count_hidden( $forum_id );
119 }
120 }
121
122 /** Post Form Handlers ********************************************************/
123
124 /**
125 * Handles the front end reply submission.
126 *
127 * @since 2.0.0 bbPress (r2574)
128 *
129 * @param string $action The requested action to compare this function to
130 * id, anonymous data, reply author, edit (false), and
131 * the reply to id.
132 */
133 function bbp_new_reply_handler( $action = '' ) {
134
135 // Bail if action is not bbp-new-reply
136 if ( 'bbp-new-reply' !== $action ) {
137 return;
138 }
139
140 // Nonce check
141 if ( ! bbp_verify_nonce_request( 'bbp-new-reply' ) ) {
142 bbp_add_error( 'bbp_new_reply_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
143 return;
144 }
145
146 // Define local variable(s)
147 $topic_id = $forum_id = $reply_author = $reply_to = 0;
148 $reply_title = $reply_content = $terms = '';
149 $anonymous_data = array();
150
151 /** Reply Author **********************************************************/
152
153 // User is anonymous
154 if ( bbp_is_anonymous() ) {
155
156 // Filter anonymous data (variable is used later)
157 $anonymous_data = bbp_filter_anonymous_post_data();
158
159 // Anonymous data checks out, so set cookies, etc...
160 bbp_set_current_anonymous_user_data( $anonymous_data );
161
162 // User is logged in
163 } else {
164
165 // User cannot create replies
166 if ( ! current_user_can( 'publish_replies' ) ) {
167 bbp_add_error( 'bbp_reply_permission', __( '<strong>Error</strong>: You do not have permission to reply.', 'bbpress' ) );
168 }
169
170 // Reply author is current user
171 $reply_author = bbp_get_current_user_id();
172 }
173
174 /** Topic ID **************************************************************/
175
176 // Topic id was not passed
177 if ( empty( $_POST['bbp_topic_id'] ) ) {
178 bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID is missing.', 'bbpress' ) );
179
180 // Topic id is not a number
181 } elseif ( ! is_numeric( $_POST['bbp_topic_id'] ) ) {
182 bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID must be a number.', 'bbpress' ) );
183
184 // Topic id might be valid
185 } else {
186
187 // Get the topic id
188 $posted_topic_id = intval( $_POST['bbp_topic_id'] );
189
190 // Topic id is 0
191 if ( 0 === $posted_topic_id ) {
192 bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID is missing.', 'bbpress' ) );
193
194 // Topic id is a negative number
195 } elseif ( 0 > $posted_topic_id ) {
196 bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic ID cannot be a negative number.', 'bbpress' ) );
197
198 // Topic does not exist
199 } elseif ( ! bbp_get_topic( $posted_topic_id ) ) {
200 bbp_add_error( 'bbp_reply_topic_id', __( '<strong>Error</strong>: Topic does not exist.', 'bbpress' ) );
201
202 // Use the POST'ed topic id
203 } else {
204 $topic_id = $posted_topic_id;
205 }
206 }
207
208 // User cannot read parent topic ID
209 if ( ! current_user_can( 'read_topic', $topic_id ) ) {
210 bbp_add_error( 'bbp_new_reply_topic_public', __( '<strong>Error</strong>: You do not have the capability to read or create new replies in this topic.', 'bbpress' ) );
211 }
212
213 /** Forum ID **************************************************************/
214
215 // Try to use the forum id of the topic
216 if ( ! isset( $_POST['bbp_forum_id'] ) && ! empty( $topic_id ) ) {
217 $forum_id = bbp_get_topic_forum_id( $topic_id );
218
219 // Error check the POST'ed forum id
220 } elseif ( isset( $_POST['bbp_forum_id'] ) ) {
221
222 // Empty Forum id was passed
223 if ( empty( $_POST['bbp_forum_id'] ) ) {
224 bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
225
226 // Forum id is not a number
227 } elseif ( ! is_numeric( $_POST['bbp_forum_id'] ) ) {
228 bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum ID must be a number.', 'bbpress' ) );
229
230 // Forum id might be valid
231 } else {
232
233 // Get the forum id
234 $posted_forum_id = intval( $_POST['bbp_forum_id'] );
235
236 // Forum id is 0
237 if ( 0 === $posted_forum_id ) {
238 bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum ID is missing.', 'bbpress' ) );
239
240 // Forum id is a negative number
241 } elseif ( 0 > $posted_forum_id ) {
242 bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum ID cannot be a negative number.', 'bbpress' ) );
243
244 // Forum does not exist
245 } elseif ( ! bbp_get_forum( $posted_forum_id ) ) {
246 bbp_add_error( 'bbp_reply_forum_id', __( '<strong>Error</strong>: Forum does not exist.', 'bbpress' ) );
247
248 // Use the POST'ed forum id
249 } else {
250 $forum_id = $posted_forum_id;
251 }
252 }
253 }
254
255 // Forum exists
256 if ( ! empty( $forum_id ) ) {
257
258 // Forum is a category
259 if ( bbp_is_forum_category( $forum_id ) ) {
260 bbp_add_error( 'bbp_new_reply_forum_category', __( '<strong>Error</strong>: This forum is a category. No replies can be created in this forum.', 'bbpress' ) );
261
262 // Forum is not a category
263 } else {
264
265 // Forum not editable by user
266 if ( ! current_user_can( 'edit_forum', $forum_id ) ) {
267
268 // Forum is closed
269 if ( bbp_is_forum_closed( $forum_id ) ) {
270 bbp_add_error( 'bbp_new_reply_forum_closed', __( '<strong>Error</strong>: This forum is closed to new replies.', 'bbpress' ) );
271 }
272 }
273
274 // Forum not readable by user
275 if ( ! current_user_can( 'read_forum', $forum_id ) ) {
276 bbp_add_error( 'bbp_new_reply_forum_read', __( '<strong>Error</strong>: You do not have the capability to read or create new replies in this forum.', 'bbpress' ) );
277 }
278 }
279 }
280
281 /** Unfiltered HTML *******************************************************/
282
283 // Remove kses filters from title and content for capable users and if the nonce is verified
284 if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_reply'] ) && wp_create_nonce( 'bbp-unfiltered-html-reply_' . $topic_id ) === $_POST['_bbp_unfiltered_html_reply'] ) {
285 remove_filter( 'bbp_new_reply_pre_title', 'wp_filter_kses' );
286 remove_filter( 'bbp_new_reply_pre_content', 'bbp_encode_bad', 10 );
287 remove_filter( 'bbp_new_reply_pre_content', 'bbp_filter_kses', 30 );
288 }
289
290 /** Reply To **************************************************************/
291
292 // Handle Reply To of the reply; $_REQUEST for non-JS submissions
293 if ( isset( $_REQUEST['bbp_reply_to'] ) && is_numeric( $_REQUEST['bbp_reply_to'] ) ) {
294 $reply_to = bbp_validate_reply_to( $_REQUEST['bbp_reply_to'] );
295 }
296
297 // Check the Reply To ID
298 if ( ! empty( $reply_to ) ) {
299
300 // User cannot read parent reply ID
301 if ( ! current_user_can( 'read_reply', $reply_to ) ) {
302 bbp_add_error( 'bbp_new_reply_reply_to', __( '<strong>Error</strong>: You do not have the capability to read or create new replies to this reply.', 'bbpress' ) );
303 }
304 }
305
306 /** Reply Title ***********************************************************/
307
308 if ( ! empty( $_POST['bbp_reply_title'] ) ) {
309 $reply_title = sanitize_text_field( $_POST['bbp_reply_title'] );
310 }
311
312 // Filter and sanitize
313 $reply_title = apply_filters( 'bbp_new_reply_pre_title', $reply_title );
314
315 // Title too long
316 if ( bbp_is_title_too_long( $reply_title ) ) {
317 bbp_add_error( 'bbp_reply_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
318 }
319
320 /** Reply Content *********************************************************/
321
322 if ( ! empty( $_POST['bbp_reply_content'] ) ) {
323 $reply_content = $_POST['bbp_reply_content'];
324 }
325
326 // Filter and sanitize
327 $reply_content = apply_filters( 'bbp_new_reply_pre_content', $reply_content );
328
329 // No reply content
330 if ( empty( $reply_content ) ) {
331 bbp_add_error( 'bbp_reply_content', __( '<strong>Error</strong>: Your reply cannot be empty.', 'bbpress' ) );
332 }
333
334 /** Reply Flooding ********************************************************/
335
336 if ( ! bbp_check_for_flood( $anonymous_data, $reply_author ) ) {
337 bbp_add_error( 'bbp_reply_flood', __( '<strong>Error</strong>: Slow down; you move too fast.', 'bbpress' ) );
338 }
339
340 /** Reply Duplicate *******************************************************/
341
342 $dupe_args = array(
343 'post_type' => bbp_get_reply_post_type(),
344 'post_author' => $reply_author,
345 'post_content' => $reply_content,
346 'post_parent' => $topic_id,
347 'anonymous_data' => $anonymous_data
348 );
349
350 if ( ! bbp_check_for_duplicate( $dupe_args ) ) {
351 bbp_add_error( 'bbp_reply_duplicate', __( '<strong>Error</strong>: Duplicate reply detected; it looks as though you&#8217;ve already said that.', 'bbpress' ) );
352 }
353
354 /** Reply Bad Words *******************************************************/
355
356 if ( ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content, true ) ) {
357 bbp_add_error( 'bbp_reply_moderation', __( '<strong>Error</strong>: Your reply cannot be created at this time.', 'bbpress' ) );
358 }
359
360 /** Reply Status **********************************************************/
361
362 // Default to published
363 $reply_status = bbp_get_public_status_id();
364
365 // Maybe force into pending
366 if ( bbp_is_topic_pending( $topic_id ) || ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content ) ) {
367 $reply_status = bbp_get_pending_status_id();
368 }
369
370 /** Topic Closed **********************************************************/
371
372 // If topic is closed, moderators can still reply
373 if ( bbp_is_topic_closed( $topic_id ) && ! current_user_can( 'moderate', $topic_id ) ) {
374 bbp_add_error( 'bbp_reply_topic_closed', __( '<strong>Error</strong>: Topic is closed.', 'bbpress' ) );
375 }
376
377 /** Topic Tags ************************************************************/
378
379 // Either replace terms
380 if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', $topic_id ) && ! empty( $_POST['bbp_topic_tags'] ) ) {
381 $terms = sanitize_text_field( $_POST['bbp_topic_tags'] );
382
383 // ...or remove them.
384 } elseif ( isset( $_POST['bbp_topic_tags'] ) ) {
385 $terms = '';
386
387 // Existing terms
388 } else {
389 $terms = bbp_get_topic_tag_names( $topic_id );
390 }
391
392 /** Additional Actions (Before Save) **************************************/
393
394 do_action( 'bbp_new_reply_pre_extras', $topic_id, $forum_id );
395
396 // Bail if errors
397 if ( bbp_has_errors() ) {
398 return;
399 }
400
401 /** No Errors *************************************************************/
402
403 // Add the content of the form to $reply_data as an array
404 // Just in time manipulation of reply data before being created
405 $reply_data = apply_filters(
406 'bbp_new_reply_pre_insert',
407 array(
408 'post_author' => $reply_author,
409 'post_title' => $reply_title,
410 'post_content' => $reply_content,
411 'post_status' => $reply_status,
412 'post_parent' => $topic_id,
413 'post_type' => bbp_get_reply_post_type(),
414 'comment_status' => 'closed',
415 'menu_order' => bbp_get_topic_reply_count( $topic_id, true ) + 1
416 )
417 );
418
419 // Insert reply
420 $reply_id = wp_insert_post( $reply_data, true );
421
422 /** No Errors *************************************************************/
423
424 // Check for missing reply_id or error
425 if ( ! empty( $reply_id ) && ! is_wp_error( $reply_id ) ) {
426
427 /** Topic Tags ********************************************************/
428
429 // Just in time manipulation of reply terms before being edited
430 $terms = apply_filters( 'bbp_new_reply_pre_set_terms', $terms, $topic_id, $reply_id );
431
432 // Insert terms
433 $terms = wp_set_post_terms( $topic_id, $terms, bbp_get_topic_tag_tax_id(), false );
434
435 // Term error
436 if ( is_wp_error( $terms ) ) {
437 bbp_add_error( 'bbp_reply_tags', __( '<strong>Error</strong>: There was a problem adding the tags to the topic.', 'bbpress' ) );
438 }
439
440 /** Trash Check *******************************************************/
441
442 // If this reply starts as trash, add it to pre_trashed_replies
443 // for the topic, so it is properly restored.
444 if ( bbp_is_topic_trash( $topic_id ) || ( bbp_get_trash_status_id() === $reply_data['post_status'] ) ) {
445
446 // Trash the reply
447 wp_trash_post( $reply_id );
448
449 // Only add to pre-trashed array if topic is trashed
450 if ( bbp_is_topic_trash( $topic_id ) ) {
451
452 // Get pre_trashed_replies for topic
453 $pre_trashed_meta = get_post_meta( $topic_id, '_bbp_pre_trashed_replies', true );
454
455 // Format the meta value
456 $pre_trashed_replies = is_array( $pre_trashed_meta )
457 ? array_filter( $pre_trashed_meta )
458 : array();
459
460 // Add this reply to the end of the existing replies
461 $pre_trashed_replies[] = $reply_id;
462
463 // Update the pre_trashed_reply post meta
464 update_post_meta( $topic_id, '_bbp_pre_trashed_replies', $pre_trashed_replies );
465 }
466
467 /** Spam Check ********************************************************/
468
469 // If reply or topic are spam, officially spam this reply
470 } elseif ( bbp_is_topic_spam( $topic_id ) || ( bbp_get_spam_status_id() === $reply_data['post_status'] ) ) {
471 add_post_meta( $reply_id, '_bbp_spam_meta_status', bbp_get_public_status_id() );
472
473 // Only add to pre-spammed array if topic is spam
474 if ( bbp_is_topic_spam( $topic_id ) ) {
475
476 // Get pre_spammed_replies for topic
477 $pre_trashed_meta = get_post_meta( $topic_id, '_bbp_pre_spammed_replies', true );
478
479 // Format the meta value
480 $pre_spammed_replies = is_array( $pre_trashed_meta )
481 ? array_filter( $pre_trashed_meta )
482 : array();
483
484 // Add this reply to the end of the existing replies
485 $pre_spammed_replies[] = $reply_id;
486
487 // Update the pre_spammed_replies post meta
488 update_post_meta( $topic_id, '_bbp_pre_spammed_replies', $pre_spammed_replies );
489 }
490 }
491
492 /** Update counts, etc... *********************************************/
493
494 do_action( 'bbp_new_reply', $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_data['post_author'], false, $reply_to );
495
496 /** Additional Actions (After Save) ***********************************/
497
498 do_action( 'bbp_new_reply_post_extras', $reply_id );
499
500 /** Redirect **********************************************************/
501
502 // Redirect to
503 $redirect_to = bbp_get_redirect_to();
504
505 // Get the reply URL
506 $reply_url = bbp_get_reply_url( $reply_id, $redirect_to );
507
508 // Allow to be filtered
509 $reply_url = apply_filters( 'bbp_new_reply_redirect_to', $reply_url, $redirect_to, $reply_id );
510
511 /** Successful Save ***************************************************/
512
513 // Redirect back to new reply
514 bbp_redirect( $reply_url );
515
516 /** Errors ****************************************************************/
517
518 // WP_Error
519 } elseif ( is_wp_error( $reply_id ) ) {
520 /* translators: %s: Error message(s) */
521 bbp_add_error( 'bbp_reply_error', sprintf( __( '<strong>Error</strong>: The following problem(s) occurred: %s', 'bbpress' ), $reply_id->get_error_message() ) );
522
523 // Generic error
524 } else {
525 bbp_add_error( 'bbp_reply_error', __( '<strong>Error</strong>: The reply was not created.', 'bbpress' ) );
526 }
527 }
528
529 /**
530 * Handles the front end edit reply submission.
531 *
532 * @param string $action The requested action to compare this function to
533 * id, anonymous data, reply author, bool true (for edit),
534 * and the reply to id.
535 */
536 function bbp_edit_reply_handler( $action = '' ) {
537
538 // Bail if action is not bbp-edit-reply
539 if ( 'bbp-edit-reply' !== $action ) {
540 return;
541 }
542
543 // Define local variable(s)
544 $revisions_removed = false;
545 $reply = $reply_id = $reply_to = $reply_author = $topic_id = $forum_id = 0;
546 $reply_title = $reply_content = $reply_edit_reason = $terms = '';
547 $anonymous_data = array();
548
549 /** Reply *****************************************************************/
550
551 // Reply id was not passed
552 if ( empty( $_POST['bbp_reply_id'] ) ) {
553 bbp_add_error( 'bbp_edit_reply_id', __( '<strong>Error</strong>: Reply ID not found.', 'bbpress' ) );
554 return;
555
556 // Reply id was passed
557 } elseif ( is_numeric( $_POST['bbp_reply_id'] ) ) {
558 $reply_id = (int) $_POST['bbp_reply_id'];
559 $reply = bbp_get_reply( $reply_id );
560 }
561
562 // Nonce check
563 if ( ! bbp_verify_nonce_request( 'bbp-edit-reply_' . $reply_id ) ) {
564 bbp_add_error( 'bbp_edit_reply_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
565 return;
566 }
567
568 // Reply does not exist
569 if ( empty( $reply ) ) {
570 bbp_add_error( 'bbp_edit_reply_not_found', __( '<strong>Error</strong>: The reply you want to edit was not found.', 'bbpress' ) );
571 return;
572
573 // User cannot edit this reply
574 } elseif ( ! current_user_can( 'edit_reply', $reply_id ) ) {
575 bbp_add_error( 'bbp_edit_reply_permission', __( '<strong>Error</strong>: You do not have permission to edit that reply.', 'bbpress' ) );
576 return;
577
578 // It is an anonymous post
579 } elseif ( bbp_is_reply_anonymous( $reply_id ) ) {
580
581 // Filter anonymous data
582 $anonymous_data = bbp_filter_anonymous_post_data();
583
584 // Set reply author
585 } else {
586 $reply_author = bbp_get_reply_author_id( $reply_id );
587 }
588
589 // Remove kses filters from title and content for capable users and if the nonce is verified
590 if ( current_user_can( 'unfiltered_html' ) && ! empty( $_POST['_bbp_unfiltered_html_reply'] ) && wp_create_nonce( 'bbp-unfiltered-html-reply_' . $reply_id ) === $_POST['_bbp_unfiltered_html_reply'] ) {
591 remove_filter( 'bbp_edit_reply_pre_title', 'wp_filter_kses' );
592 remove_filter( 'bbp_edit_reply_pre_content', 'bbp_encode_bad', 10 );
593 remove_filter( 'bbp_edit_reply_pre_content', 'bbp_filter_kses', 30 );
594 }
595
596 /** Reply Topic ***********************************************************/
597
598 $topic_id = bbp_get_reply_topic_id( $reply_id );
599
600 // User cannot read parent topic ID
601 if ( ! current_user_can( 'read_topic', $topic_id ) ) {
602 bbp_add_error( 'bbp_edit_reply_topic_read', __( '<strong>Error</strong>: You do not have the capability to read or create new replies in this topic.', 'bbpress' ) );
603 }
604
605 /** Topic Forum ***********************************************************/
606
607 $forum_id = bbp_get_topic_forum_id( $topic_id );
608
609 // Forum exists
610 if ( ! empty( $forum_id ) && ( bbp_get_reply_forum_id( $reply_id ) !== $forum_id ) ) {
611
612 // Forum is a category
613 if ( bbp_is_forum_category( $forum_id ) ) {
614 bbp_add_error( 'bbp_edit_reply_forum_category', __( '<strong>Error</strong>: This forum is a category. No replies can be created in this forum.', 'bbpress' ) );
615
616 // Forum is not a category
617 } else {
618
619 // Forum not editable by user
620 if ( ! current_user_can( 'edit_forum', $forum_id ) ) {
621
622 // Forum is closed
623 if ( bbp_is_forum_closed( $forum_id ) ) {
624 bbp_add_error( 'bbp_edit_reply_forum_closed', __( '<strong>Error</strong>: This forum is closed to new replies.', 'bbpress' ) );
625 }
626 }
627
628 // Forum not readable by user
629 if ( ! current_user_can( 'read_forum', $forum_id ) ) {
630 bbp_add_error( 'bbp_edit_reply_forum_read', __( '<strong>Error</strong>: You do not have the capability to read or create new replies in this forum.', 'bbpress' ) );
631 }
632 }
633 }
634
635 /** Reply To **************************************************************/
636
637 $reply_to = bbp_get_reply_to( $reply_id );
638
639 // Maybe sanitize Reply To, using $_REQUEST for non-JS submissions
640 if ( isset( $_REQUEST['bbp_reply_to'] ) && is_numeric( $_REQUEST['bbp_reply_to'] ) ) {
641 $reply_to = intval( $_REQUEST['bbp_reply_to'] );
642 }
643
644 // Validate Reply To
645 $reply_to = bbp_validate_reply_to( $reply_to, $reply_id );
646
647 // Check the Reply To ID
648 if ( ! empty( $reply_to ) ) {
649
650 // User cannot read parent reply ID
651 if ( ! current_user_can( 'read_reply', $reply_to ) ) {
652 bbp_add_error( 'bbp_edit_reply_reply_to', __( '<strong>Error</strong>: You do not have the capability to read or create new replies to this reply.', 'bbpress' ) );
653 }
654 }
655
656 /** Reply Title ***********************************************************/
657
658 if ( ! empty( $_POST['bbp_reply_title'] ) ) {
659 $reply_title = sanitize_text_field( $_POST['bbp_reply_title'] );
660 }
661
662 // Filter and sanitize
663 $reply_title = apply_filters( 'bbp_edit_reply_pre_title', $reply_title, $reply_id );
664
665 // Title too long
666 if ( bbp_is_title_too_long( $reply_title ) ) {
667 bbp_add_error( 'bbp_reply_title', __( '<strong>Error</strong>: Your title is too long.', 'bbpress' ) );
668 }
669
670 /** Reply Content *********************************************************/
671
672 if ( ! empty( $_POST['bbp_reply_content'] ) ) {
673 $reply_content = $_POST['bbp_reply_content'];
674 }
675
676 // Filter and sanitize
677 $reply_content = apply_filters( 'bbp_edit_reply_pre_content', $reply_content, $reply_id );
678
679 // No reply content
680 if ( empty( $reply_content ) ) {
681 bbp_add_error( 'bbp_edit_reply_content', __( '<strong>Error</strong>: Your reply cannot be empty.', 'bbpress' ) );
682 }
683
684 /** Reply Bad Words *******************************************************/
685
686 if ( ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content, true ) ) {
687 bbp_add_error( 'bbp_reply_moderation', __( '<strong>Error</strong>: Your reply cannot be edited at this time.', 'bbpress' ) );
688 }
689
690 /** Reply Status **********************************************************/
691
692 // Get available reply statuses
693 $reply_statuses = bbp_get_reply_statuses( $reply_id );
694
695 // Use existing post_status
696 $reply_status = $reply->post_status;
697
698 // Maybe force into pending
699 if ( bbp_is_reply_public( $reply_id ) && ! bbp_check_for_moderation( $anonymous_data, $reply_author, $reply_title, $reply_content ) ) {
700 $reply_status = bbp_get_pending_status_id();
701
702 // Check for possible posted reply status
703 } elseif ( ! empty( $_POST['bbp_reply_status'] ) && in_array( $_POST['bbp_reply_status'], array_keys( $reply_statuses ), true ) ) {
704
705 // Allow capable users to explicitly override the status
706 if ( current_user_can( 'moderate', $reply_id ) ) {
707 $reply_status = sanitize_key( $_POST['bbp_reply_status'] );
708
709 // Not capable
710 } else {
711 bbp_add_error( 'bbp_edit_reply_status', __( '<strong>Error</strong>: You do not have permission to do that.', 'bbpress' ) );
712 }
713 }
714
715 /** Topic Tags ************************************************************/
716
717 // Either replace terms
718 if ( bbp_allow_topic_tags() && current_user_can( 'assign_topic_tags', $topic_id ) && ! empty( $_POST['bbp_topic_tags'] ) ) {
719 $terms = sanitize_text_field( $_POST['bbp_topic_tags'] );
720
721 // ...or remove them.
722 } elseif ( isset( $_POST['bbp_topic_tags'] ) ) {
723 $terms = '';
724
725 // Existing terms
726 } else {
727 $terms = bbp_get_topic_tag_names( $topic_id );
728 }
729
730 /** Additional Actions (Before Save) **************************************/
731
732 do_action( 'bbp_edit_reply_pre_extras', $reply_id );
733
734 // Bail if errors
735 if ( bbp_has_errors() ) {
736 return;
737 }
738
739 /** No Errors *************************************************************/
740
741 // Add the content of the form to $reply_data as an array
742 // Just in time manipulation of reply data before being edited
743 $reply_data = apply_filters(
744 'bbp_edit_reply_pre_insert',
745 array(
746 'ID' => $reply_id,
747 'post_title' => $reply_title,
748 'post_content' => $reply_content,
749 'post_status' => $reply_status,
750 'post_parent' => $topic_id,
751 'post_author' => $reply_author,
752 'post_type' => bbp_get_reply_post_type()
753 )
754 );
755
756 // Toggle revisions to avoid duplicates
757 if ( post_type_supports( bbp_get_reply_post_type(), 'revisions' ) ) {
758 $revisions_removed = true;
759 remove_post_type_support( bbp_get_reply_post_type(), 'revisions' );
760 }
761
762 // Insert reply
763 $reply_id = wp_update_post( $reply_data );
764
765 // Toggle revisions back on
766 if ( true === $revisions_removed ) {
767 $revisions_removed = false;
768 add_post_type_support( bbp_get_reply_post_type(), 'revisions' );
769 }
770
771 /** Topic Tags ************************************************************/
772
773 // Just in time manipulation of reply terms before being edited
774 $terms = apply_filters( 'bbp_edit_reply_pre_set_terms', $terms, $topic_id, $reply_id );
775
776 // Insert terms
777 $terms = wp_set_post_terms( $topic_id, $terms, bbp_get_topic_tag_tax_id(), false );
778
779 // Term error
780 if ( is_wp_error( $terms ) ) {
781 bbp_add_error( 'bbp_reply_tags', __( '<strong>Error</strong>: There was a problem adding the tags to the topic.', 'bbpress' ) );
782 }
783
784 /** No Errors *************************************************************/
785
786 if ( ! empty( $reply_id ) && ! is_wp_error( $reply_id ) ) {
787
788 // Update counts, etc...
789 do_action( 'bbp_edit_reply', $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_data['post_author'], true, $reply_to );
790
791 /** Revisions *********************************************************/
792
793 // Revision Reason
794 if ( ! empty( $_POST['bbp_reply_edit_reason'] ) ) {
795 $reply_edit_reason = sanitize_text_field( $_POST['bbp_reply_edit_reason'] );
796 }
797
798 // Update revision log
799 if ( ! empty( $_POST['bbp_log_reply_edit'] ) && ( '1' === $_POST['bbp_log_reply_edit'] ) ) {
800 $revision_id = wp_save_post_revision( $reply_id );
801 if ( ! empty( $revision_id ) ) {
802 bbp_update_reply_revision_log(
803 array(
804 'reply_id' => $reply_id,
805 'revision_id' => $revision_id,
806 'author_id' => bbp_get_current_user_id(),
807 'reason' => $reply_edit_reason
808 )
809 );
810 }
811 }
812
813 /** Additional Actions (After Save) ***********************************/
814
815 do_action( 'bbp_edit_reply_post_extras', $reply_id );
816
817 /** Redirect **********************************************************/
818
819 // Redirect to
820 $redirect_to = bbp_get_redirect_to();
821
822 // Get the reply URL
823 $reply_url = bbp_get_reply_url( $reply_id, $redirect_to );
824
825 // Allow to be filtered
826 $reply_url = apply_filters( 'bbp_edit_reply_redirect_to', $reply_url, $redirect_to );
827
828 /** Successful Edit ***************************************************/
829
830 // Redirect back to new reply
831 bbp_redirect( $reply_url );
832
833 /** Errors ****************************************************************/
834
835 } else {
836 $append_error = ( is_wp_error( $reply_id ) && $reply_id->get_error_message() )
837 ? $reply_id->get_error_message() . ' '
838 : '';
839
840 /* translators: %s: Error message(s) */
841 bbp_add_error( 'bbp_reply_error', sprintf( __( '<strong>Error</strong>: The following problem(s) have been found with your reply: %s. Please try again.', 'bbpress' ) ), $append_error );
842 }
843 }
844
845 /**
846 * Handle all the extra meta stuff from posting a new reply or editing a reply.
847 *
848 * @param int $reply_id Optional. Reply id.
849 * @param int $topic_id Optional. Topic id.
850 * @param int $forum_id Optional. Forum id.
851 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
852 * supply if supplying $author_id. Should be
853 * sanitized (see {@link bbp_filter_anonymous_post_data()}.
854 * @param int $author_id Author id.
855 * @param bool $is_edit Optional. Is the post being edited? Defaults to false.
856 * @param int $reply_to Optional. Reply to id.
857 */
858 function bbp_update_reply( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $author_id = 0, $is_edit = false, $reply_to = 0 ) {
859
860 // Validate the ID's passed from 'bbp_new_reply' action
861 $reply_id = bbp_get_reply_id( $reply_id );
862 $topic_id = bbp_get_topic_id( $topic_id );
863 $forum_id = bbp_get_forum_id( $forum_id );
864 $reply_to = bbp_validate_reply_to( $reply_to, $reply_id );
865
866 // Get the current user ID
867 $user_id = bbp_get_current_user_id();
868
869 // Bail if there is no reply
870 if ( empty( $reply_id ) ) {
871 return;
872 }
873
874 // Check author_id, fallback to current user ID
875 if ( empty( $author_id ) ) {
876 $author_id = $user_id;
877 }
878
879 // Check topic_id, fallback to post_parent or meta
880 if ( empty( $topic_id ) ) {
881 $topic_id = bbp_get_reply_topic_id( $reply_id );
882 }
883
884 // Check forum_id, fallback to post_parent or meta
885 if ( ! empty( $topic_id ) && empty( $forum_id ) ) {
886 $forum_id = bbp_get_topic_forum_id( $topic_id );
887 }
888
889 // Update locks
890 update_post_meta( $reply_id, '_edit_last', $user_id );
891 delete_post_meta( $reply_id, '_edit_lock' );
892
893 // Forum/Topic meta (early, for use in downstream functions)
894 bbp_update_reply_forum_id( $reply_id, $forum_id );
895 bbp_update_reply_topic_id( $reply_id, $topic_id );
896 bbp_update_reply_to ( $reply_id, $reply_to );
897
898 // If anonymous post, store name, email, website and ip in post_meta.
899 if ( ! empty( $anonymous_data ) ) {
900
901 // Update anonymous meta data (not cookies)
902 bbp_update_anonymous_post_author( $reply_id, $anonymous_data, bbp_get_reply_post_type() );
903
904 // Set transient for throttle check (only on new, not edit)
905 if ( empty( $is_edit ) ) {
906 set_transient( '_bbp_' . bbp_current_author_ip() . '_last_posted', time(), HOUR_IN_SECONDS );
907 }
908 }
909
910 // Handle Subscription Checkbox
911 if ( bbp_is_subscriptions_active() && ! empty( $author_id ) && ! empty( $topic_id ) ) {
912
913 // Check if subscribed
914 $subscribed = bbp_is_user_subscribed( $author_id, $topic_id );
915
916 // Check for action
917 $subscheck = ( ! empty( $_POST['bbp_topic_subscription'] ) && ( 'bbp_subscribe' === $_POST['bbp_topic_subscription'] ) )
918 ? true
919 : false;
920
921 // Subscribed and unsubscribing
922 if ( ( true === $subscribed ) && ( false === $subscheck ) ) {
923 bbp_remove_user_subscription( $author_id, $topic_id );
924
925 // Not subscribed and subscribing
926 } elseif ( ( false === $subscribed ) && ( true === $subscheck ) ) {
927 bbp_add_user_subscription( $author_id, $topic_id );
928 }
929 }
930
931 // Update associated topic values if this is a new reply
932 if ( empty( $is_edit ) ) {
933
934 // Update poster activity time
935 bbp_update_user_last_posted( $author_id );
936
937 // Update poster IP
938 update_post_meta( $reply_id, '_bbp_author_ip', bbp_current_author_ip(), false );
939
940 // Last active time
941 $last_active_time = get_post_field( 'post_date', $reply_id );
942
943 // Walk up ancestors and do the dirty work
944 bbp_update_reply_walker( $reply_id, $last_active_time, $forum_id, $topic_id, false );
945 }
946
947 // Bump the custom query cache
948 wp_cache_set( 'last_changed', microtime(), 'bbpress_posts' );
949 }
950
951 /**
952 * Walk up the ancestor tree from the current reply, and update all the counts.
953 *
954 * @since 2.0.0 bbPress (r2884)
955 *
956 * @param int $reply_id Optional. Reply id.
957 * @param string $last_active_time Optional. Last active time.
958 * @param int $forum_id Optional. Forum id.
959 * @param int $topic_id Optional. Topic id.
960 * @param bool $refresh If set to true, unsets all the previous parameters.
961 * Defaults to true.
962 */
963 function bbp_update_reply_walker( $reply_id, $last_active_time = '', $forum_id = 0, $topic_id = 0, $refresh = true ) {
964
965 // Verify the reply ID
966 $reply_id = bbp_get_reply_id( $reply_id );
967
968 // Reply was passed
969 if ( ! empty( $reply_id ) ) {
970
971 // Get the topic ID if none was passed
972 if ( empty( $topic_id ) ) {
973 $topic_id = bbp_get_reply_topic_id( $reply_id );
974 }
975
976 // Get the forum ID if none was passed
977 if ( empty( $forum_id ) ) {
978 $forum_id = bbp_get_reply_forum_id( $reply_id );
979 }
980 }
981
982 // Set the active_id based on topic_id/reply_id
983 $active_id = empty( $reply_id ) ? $topic_id : $reply_id;
984
985 // Setup ancestors array to walk up
986 $ancestors = array_values( array_unique( array_merge( array( $topic_id, $forum_id ), (array) get_post_ancestors( $topic_id ) ) ) );
987
988 // If we want a full refresh, unset any of the possibly passed variables
989 if ( true === $refresh ) {
990 $forum_id = $topic_id = $reply_id = $active_id = $last_active_time = 0;
991 }
992
993 // Walk up ancestors
994 if ( ! empty( $ancestors ) ) {
995 foreach ( $ancestors as $ancestor ) {
996
997 // Reply meta relating to most recent reply
998 // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
999 if ( bbp_is_reply( $ancestor ) ) {
1000 // @todo - hierarchical replies
1001
1002 // Topic meta relating to most recent reply
1003 } elseif ( bbp_is_topic( $ancestor ) ) {
1004
1005 // Only update if reply is published
1006 if ( ! bbp_is_reply_pending( $reply_id ) ) {
1007
1008 // Last reply and active ID's
1009 bbp_update_topic_last_reply_id ( $ancestor, $reply_id );
1010 bbp_update_topic_last_active_id( $ancestor, $active_id );
1011
1012 // Get the last active time if none was passed
1013 $topic_last_active_time = $last_active_time;
1014 if ( empty( $last_active_time ) ) {
1015 $topic_last_active_time = get_post_field( 'post_date', bbp_get_topic_last_active_id( $ancestor ) );
1016 }
1017
1018 bbp_update_topic_last_active_time( $ancestor, $topic_last_active_time );
1019 }
1020
1021 // Only update reply count if we've deleted a reply
1022 if ( in_array( current_filter(), array( 'bbp_deleted_reply', 'save_post' ), true ) ) {
1023 bbp_update_topic_reply_count( $ancestor );
1024 bbp_update_topic_reply_count_hidden( $ancestor );
1025 bbp_update_topic_voice_count( $ancestor );
1026 }
1027
1028 // Forum meta relating to most recent topic
1029 } elseif ( bbp_is_forum( $ancestor ) ) {
1030
1031 // Only update if reply is published
1032 if ( ! bbp_is_reply_pending( $reply_id ) && ! bbp_is_topic_pending( $topic_id ) ) {
1033
1034 // Last topic and reply ID's
1035 bbp_update_forum_last_topic_id( $ancestor, $topic_id );
1036 bbp_update_forum_last_reply_id( $ancestor, $reply_id );
1037
1038 // Last Active
1039 bbp_update_forum_last_active_id( $ancestor, $active_id );
1040
1041 // Get the last active time if none was passed
1042 $forum_last_active_time = $last_active_time;
1043 if ( empty( $last_active_time ) ) {
1044 $forum_last_active_time = get_post_field( 'post_date', bbp_get_forum_last_active_id( $ancestor ) );
1045 }
1046
1047 bbp_update_forum_last_active_time( $ancestor, $forum_last_active_time );
1048 }
1049
1050 // Only update reply count if we've deleted a reply
1051 if ( in_array( current_filter(), array( 'bbp_deleted_reply', 'save_post' ), true ) ) {
1052 bbp_update_forum_reply_count( $ancestor );
1053 }
1054 }
1055 }
1056 }
1057 }
1058
1059 /** Reply Updaters ************************************************************/
1060
1061 /**
1062 * Update the reply with its forum id it is in.
1063 *
1064 * @since 2.0.0 bbPress (r2855)
1065 *
1066 * @param int $reply_id Optional. Reply id to update.
1067 * @param int $forum_id Optional. Forum id.
1068 * @return bool The forum id of the reply.
1069 */
1070 function bbp_update_reply_forum_id( $reply_id = 0, $forum_id = 0 ) {
1071
1072 // Validation
1073 $reply_id = bbp_get_reply_id( $reply_id );
1074 $forum_id = bbp_get_forum_id( $forum_id );
1075
1076 // If no forum_id was passed, walk up ancestors and look for forum type
1077 if ( empty( $forum_id ) ) {
1078
1079 // Get ancestors
1080 $ancestors = get_post_ancestors( $reply_id );
1081
1082 // Loop through ancestors
1083 if ( ! empty( $ancestors ) ) {
1084 foreach ( $ancestors as $ancestor ) {
1085
1086 // Get first parent that is a forum
1087 if ( get_post_field( 'post_type', $ancestor ) === bbp_get_forum_post_type() ) {
1088 $forum_id = $ancestor;
1089
1090 // Found a forum, so exit the loop and continue
1091 continue;
1092 }
1093 }
1094 }
1095 }
1096
1097 // Update the forum ID
1098 $retval = bbp_update_forum_id( $reply_id, $forum_id );
1099
1100 // Filter & return
1101 return (int) apply_filters( 'bbp_update_reply_forum_id', $retval, $reply_id, $forum_id );
1102 }
1103
1104 /**
1105 * Update the reply with its topic id it is in.
1106 *
1107 * @since 2.0.0 bbPress (r2855)
1108 *
1109 * @param int $reply_id Optional. Reply id to update.
1110 * @param int $topic_id Optional. Topic id.
1111 * @return bool The topic id of the reply.
1112 */
1113 function bbp_update_reply_topic_id( $reply_id = 0, $topic_id = 0 ) {
1114
1115 // Validation
1116 $reply_id = bbp_get_reply_id( $reply_id );
1117 $topic_id = bbp_get_topic_id( $topic_id );
1118
1119 // If no topic_id was passed, walk up ancestors and look for topic type
1120 if ( empty( $topic_id ) ) {
1121
1122 // Get ancestors
1123 $ancestors = (array) get_post_ancestors( $reply_id );
1124
1125 // Loop through ancestors
1126 if ( ! empty( $ancestors ) ) {
1127 foreach ( $ancestors as $ancestor ) {
1128
1129 // Get first parent that is a topic
1130 if ( get_post_field( 'post_type', $ancestor ) === bbp_get_topic_post_type() ) {
1131 $topic_id = $ancestor;
1132
1133 // Found a topic, so exit the loop and continue
1134 continue;
1135 }
1136 }
1137 }
1138 }
1139
1140 // Update the topic ID
1141 $retval = bbp_update_topic_id( $reply_id, $topic_id );
1142
1143 // Filter & return
1144 return (int) apply_filters( 'bbp_update_reply_topic_id', $retval, $reply_id, $topic_id );
1145 }
1146
1147 /*
1148 * Update the meta data with its parent reply-to id, of a reply.
1149 *
1150 * @since 2.4.0 bbPress (r4944)
1151 *
1152 * @param int $reply_id Reply id to update.
1153 * @param int $reply_to Optional. Reply to id.
1154 * @return bool The parent reply id of the reply.
1155 */
1156 function bbp_update_reply_to( $reply_id = 0, $reply_to = 0 ) {
1157
1158 // Validation
1159 $reply_id = bbp_get_reply_id( $reply_id );
1160 $reply_to = bbp_validate_reply_to( $reply_to, $reply_id );
1161
1162 // Update or delete the `reply_to` postmeta
1163 if ( ! empty( $reply_id ) ) {
1164
1165 // Update the reply to
1166 if ( ! empty( $reply_to ) ) {
1167 $reply_to = bbp_update_reply_to_id( $reply_id, $reply_to );
1168
1169 // Delete the reply to
1170 } else {
1171 delete_post_meta( $reply_id, '_bbp_reply_to' );
1172 }
1173 }
1174
1175 // Filter & return
1176 return (int) apply_filters( 'bbp_update_reply_to', $reply_to, $reply_id );
1177 }
1178
1179 /**
1180 * Get all ancestors to a reply.
1181 *
1182 * Because settings can be changed, this function does not care if hierarchical
1183 * replies are active or to what depth.
1184 *
1185 * @since 2.6.0 bbPress (r5390)
1186 *
1187 * @param int $reply_id
1188 * @return array
1189 */
1190 function bbp_get_reply_ancestors( $reply_id = 0 ) {
1191
1192 // Validation
1193 $reply_id = bbp_get_reply_id( $reply_id );
1194 $ancestors = array();
1195
1196 // Reply id is valid
1197 if ( ! empty( $reply_id ) ) {
1198
1199 // Try to get reply parent
1200 $reply_to = bbp_get_reply_to( $reply_id );
1201
1202 // Reply has a hierarchical parent
1203 if ( ! empty( $reply_to ) ) {
1204
1205 // Setup the current ID and current post as an ancestor
1206 $id = $reply_to;
1207 $ancestors = array( $reply_to );
1208
1209 // Get parent reply
1210 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
1211 while ( $ancestor = bbp_get_reply( $id ) ) {
1212
1213 // Does parent have a parent?
1214 $grampy_id = bbp_get_reply_to( $ancestor->ID );
1215
1216 // Loop detection: If the ancestor has been seen before, break.
1217 if ( empty( $ancestor->post_parent ) || ( $grampy_id === $reply_id ) || in_array( $grampy_id, $ancestors, true ) ) {
1218 break;
1219 }
1220
1221 $id = $ancestors[] = $grampy_id;
1222 }
1223 }
1224 }
1225
1226 // Filter & return
1227 return (array) apply_filters( 'bbp_get_reply_ancestors', $ancestors, $reply_id );
1228 }
1229
1230 /**
1231 * Update the revision log of the reply.
1232 *
1233 * @since 2.0.0 bbPress (r2782)
1234 *
1235 * @param array $args Supports these args:
1236 * - reply_id: reply id
1237 * - author_id: Author id
1238 * - reason: Reason for editing
1239 * - revision_id: Revision id
1240 * @return mixed False on failure, true on success.
1241 */
1242 function bbp_update_reply_revision_log( $args = array() ) {
1243
1244 // Parse arguments against default values
1245 $r = bbp_parse_args(
1246 $args,
1247 array(
1248 'reason' => '',
1249 'reply_id' => 0,
1250 'author_id' => 0,
1251 'revision_id' => 0
1252 ),
1253 'update_reply_revision_log'
1254 );
1255
1256 // Populate the variables
1257 $r['reason'] = bbp_format_revision_reason( $r['reason'] );
1258 $r['reply_id'] = bbp_get_reply_id( $r['reply_id'] );
1259 $r['author_id'] = bbp_get_user_id ( $r['author_id'], false, true );
1260 $r['revision_id'] = (int) $r['revision_id'];
1261
1262 // Get the logs and append the new one to those
1263 $revision_log = bbp_get_reply_raw_revision_log( $r['reply_id'] );
1264 $revision_log[ $r['revision_id'] ] = array(
1265 'author' => $r['author_id'],
1266 'reason' => $r['reason']
1267 );
1268
1269 // Finally, update
1270 update_post_meta( $r['reply_id'], '_bbp_revision_log', $revision_log );
1271
1272 // Filter & return
1273 return apply_filters( 'bbp_update_reply_revision_log', $revision_log, $r['reply_id'] );
1274 }
1275
1276 /**
1277 * Move reply handler.
1278 *
1279 * Handles the front end move reply submission.
1280 *
1281 * @since 2.3.0 bbPress (r4521)
1282 *
1283 * @param string $action The requested action to compare this function to.
1284 */
1285 function bbp_move_reply_handler( $action = '' ) {
1286
1287 // Bail if action is not 'bbp-move-reply'
1288 if ( 'bbp-move-reply' !== $action ) {
1289 return;
1290 }
1291
1292 // Prevent debug notices
1293 $move_reply_id = $destination_topic_id = 0;
1294 $destination_topic_title = '';
1295 $destination_topic = $move_reply = $source_topic = '';
1296
1297 /** Move Reply ***********************************************************/
1298
1299 if ( empty( $_POST['bbp_reply_id'] ) ) {
1300 bbp_add_error( 'bbp_move_reply_reply_id', __( '<strong>Error</strong>: A reply ID is required.', 'bbpress' ) );
1301 } else {
1302 $move_reply_id = (int) $_POST['bbp_reply_id'];
1303 }
1304
1305 $move_reply = bbp_get_reply( $move_reply_id );
1306
1307 // Reply exists
1308 if ( empty( $move_reply ) ) {
1309 bbp_add_error( 'bbp_mover_reply_r_not_found', __( '<strong>Error</strong>: The reply you want to move was not found.', 'bbpress' ) );
1310 }
1311
1312 /** Topic to Move From ***************************************************/
1313
1314 // Get the current topic a reply is in
1315 $source_topic = bbp_get_topic( $move_reply->post_parent );
1316
1317 // No topic
1318 if ( empty( $source_topic ) ) {
1319 bbp_add_error( 'bbp_move_reply_source_not_found', __( '<strong>Error</strong>: The topic you want to move from was not found.', 'bbpress' ) );
1320 }
1321
1322 // Nonce check failed
1323 if ( ! bbp_verify_nonce_request( 'bbp-move-reply_' . $move_reply->ID ) ) {
1324 bbp_add_error( 'bbp_move_reply_nonce', __( '<strong>Error</strong>: Are you sure you wanted to do that?', 'bbpress' ) );
1325 return;
1326 }
1327
1328 // Use cannot edit topic
1329 if ( ! current_user_can( 'edit_topic', $source_topic->ID ) ) {
1330 bbp_add_error( 'bbp_move_reply_source_permission', __( '<strong>Error</strong>: You do not have permission to edit the source topic.', 'bbpress' ) );
1331 }
1332
1333 // How to move
1334 if ( ! empty( $_POST['bbp_reply_move_option'] ) ) {
1335 $move_option = (string) trim( $_POST['bbp_reply_move_option'] );
1336 }
1337
1338 // Invalid move option
1339 if ( empty( $move_option ) || ! in_array( $move_option, array( 'existing', 'topic' ), true ) ) {
1340 bbp_add_error( 'bbp_move_reply_option', __( '<strong>Error</strong>: You need to choose a valid move option.', 'bbpress' ) );
1341
1342 // Valid move option
1343 } else {
1344
1345 // What kind of move
1346 switch ( $move_option ) {
1347
1348 // Into an existing topic
1349 case 'existing' :
1350
1351 // Get destination topic id
1352 if ( empty( $_POST['bbp_destination_topic'] ) ) {
1353 bbp_add_error( 'bbp_move_reply_destination_id', __( '<strong>Error</strong>: A topic ID is required.', 'bbpress' ) );
1354 } else {
1355 $destination_topic_id = (int) $_POST['bbp_destination_topic'];
1356 }
1357
1358 // Get the destination topic
1359 $destination_topic = bbp_get_topic( $destination_topic_id );
1360
1361 // No destination topic
1362 if ( empty( $destination_topic ) ) {
1363 bbp_add_error( 'bbp_move_reply_destination_not_found', __( '<strong>Error</strong>: The topic you want to move to was not found.', 'bbpress' ) );
1364 }
1365
1366 // User cannot edit the destination topic
1367 if ( ! current_user_can( 'edit_topic', $destination_topic->ID ) ) {
1368 bbp_add_error( 'bbp_move_reply_destination_permission', __( '<strong>Error</strong>: You do not have permission to edit the destination topic.', 'bbpress' ) );
1369 }
1370
1371 // Bump the reply position
1372 $reply_position = bbp_get_topic_reply_count( $destination_topic->ID, true ) + 1;
1373
1374 // Update the reply
1375 wp_update_post(
1376 array(
1377 'ID' => $move_reply->ID,
1378 'post_title' => '',
1379 'post_name' => false, // will be automatically generated
1380 'post_parent' => $destination_topic->ID,
1381 'menu_order' => $reply_position,
1382 'guid' => ''
1383 )
1384 );
1385
1386 // Adjust reply meta values
1387 bbp_update_reply_topic_id( $move_reply->ID, $destination_topic->ID );
1388 bbp_update_reply_forum_id( $move_reply->ID, bbp_get_topic_forum_id( $destination_topic->ID ) );
1389
1390 break;
1391
1392 // Move reply to a new topic
1393 case 'topic' :
1394 default :
1395 // User needs to be able to publish topics
1396 if ( current_user_can( 'publish_topics' ) ) {
1397
1398 // Use the new title that was passed
1399 if ( ! empty( $_POST['bbp_reply_move_destination_title'] ) ) {
1400 $destination_topic_title = sanitize_text_field( $_POST['bbp_reply_move_destination_title'] );
1401
1402 // Use the source topic title
1403 } else {
1404 $destination_topic_title = $source_topic->post_title;
1405 }
1406
1407 // Update the topic
1408 $destination_topic_id = wp_update_post(
1409 array(
1410 'ID' => $move_reply->ID,
1411 'post_title' => $destination_topic_title,
1412 'post_name' => false,
1413 'post_type' => bbp_get_topic_post_type(),
1414 'post_parent' => $source_topic->post_parent,
1415 'guid' => ''
1416 )
1417 );
1418
1419 // Get the topic
1420 $destination_topic = bbp_get_topic( $destination_topic_id );
1421
1422 // Make sure the new topic knows its a topic
1423 bbp_update_topic_topic_id( $move_reply->ID );
1424
1425 // Shouldn't happen
1426 if ( false === $destination_topic_id || is_wp_error( $destination_topic_id ) || empty( $destination_topic ) ) {
1427 bbp_add_error( 'bbp_move_reply_destination_reply', __( '<strong>Error</strong>: There was a problem converting the reply into the topic. Please try again.', 'bbpress' ) );
1428 }
1429
1430 // User cannot publish posts
1431 } else {
1432 bbp_add_error( 'bbp_move_reply_destination_permission', __( '<strong>Error</strong>: You do not have permission to create new topics. The reply could not be converted into a topic.', 'bbpress' ) );
1433 }
1434
1435 break;
1436 }
1437 }
1438
1439 // Bail if there are errors
1440 if ( bbp_has_errors() ) {
1441 return;
1442 }
1443
1444 /** No Errors - Clean Up **************************************************/
1445
1446 // Update counts, etc...
1447 do_action( 'bbp_pre_move_reply', $move_reply->ID, $source_topic->ID, $destination_topic->ID );
1448
1449 /** Date Check ************************************************************/
1450
1451 // Check if the destination topic is older than the move reply
1452 if ( strtotime( $move_reply->post_date ) < strtotime( $destination_topic->post_date ) ) {
1453
1454 // Set destination topic post_date to 1 second before from reply
1455 // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
1456 $destination_post_date = date( 'Y-m-d H:i:s', strtotime( $move_reply->post_date ) - 1 );
1457
1458 // Update destination topic
1459 wp_update_post(
1460 array(
1461 'ID' => $destination_topic_id,
1462 'post_date' => $destination_post_date,
1463 'post_date_gmt' => get_gmt_from_date( $destination_post_date )
1464 )
1465 );
1466 }
1467
1468 // Set the last reply ID and freshness to the move_reply
1469 $last_reply_id = $move_reply->ID;
1470 $freshness = $move_reply->post_date;
1471
1472 // Get the reply to
1473 $parent = bbp_get_reply_to( $move_reply->ID );
1474
1475 // Fix orphaned children
1476 $children = get_posts(
1477 array(
1478 'post_type' => bbp_get_reply_post_type(),
1479 'meta_key' => '_bbp_reply_to',
1480 'meta_type' => 'NUMERIC',
1481 'meta_value' => $move_reply->ID,
1482 )
1483 );
1484
1485 foreach ( $children as $child ) {
1486 bbp_update_reply_to( $child->ID, $parent );
1487 }
1488
1489 // Remove reply_to from moved reply
1490 delete_post_meta( $move_reply->ID, '_bbp_reply_to' );
1491
1492 // It is a new topic and we need to set some default metas to make
1493 // the topic display in bbp_has_topics() list
1494 if ( 'topic' === $move_option ) {
1495 bbp_update_topic_last_reply_id ( $destination_topic->ID, $last_reply_id );
1496 bbp_update_topic_last_active_id ( $destination_topic->ID, $last_reply_id );
1497 bbp_update_topic_last_active_time( $destination_topic->ID, $freshness );
1498
1499 // Otherwise update the existing destination topic
1500 } else {
1501 bbp_update_topic_last_reply_id ( $destination_topic->ID );
1502 bbp_update_topic_last_active_id ( $destination_topic->ID );
1503 bbp_update_topic_last_active_time( $destination_topic->ID );
1504 }
1505
1506 // Update source topic ID last active
1507 bbp_update_topic_last_reply_id ( $source_topic->ID );
1508 bbp_update_topic_last_active_id ( $source_topic->ID );
1509 bbp_update_topic_last_active_time( $source_topic->ID );
1510
1511 /** Successful Move ******************************************************/
1512
1513 // Update counts, etc...
1514 do_action( 'bbp_post_move_reply', $move_reply->ID, $source_topic->ID, $destination_topic->ID );
1515
1516 // Redirect back to the topic
1517 bbp_redirect( bbp_get_topic_permalink( $destination_topic->ID ) );
1518 }
1519
1520 /**
1521 * Fix counts on reply move.
1522 *
1523 * When a reply is moved, update the counts of source and destination topic
1524 * and their forums.
1525 *
1526 * @since 2.3.0 bbPress (r4521)
1527 *
1528 * @param int $move_reply_id Move reply id.
1529 * @param int $source_topic_id Source topic id.
1530 * @param int $destination_topic_id Destination topic id.
1531 */
1532 function bbp_move_reply_count( $move_reply_id, $source_topic_id, $destination_topic_id ) {
1533
1534 // Forum topic counts
1535 bbp_update_forum_topic_count( bbp_get_topic_forum_id( $destination_topic_id ) );
1536
1537 // Forum reply counts
1538 bbp_update_forum_reply_count( bbp_get_topic_forum_id( $destination_topic_id ) );
1539
1540 // Topic reply counts
1541 bbp_update_topic_reply_count( $source_topic_id );
1542 bbp_update_topic_reply_count( $destination_topic_id );
1543
1544 // Topic hidden reply counts
1545 bbp_update_topic_reply_count_hidden( $source_topic_id );
1546 bbp_update_topic_reply_count_hidden( $destination_topic_id );
1547
1548 // Topic voice counts
1549 bbp_update_topic_voice_count( $source_topic_id );
1550 bbp_update_topic_voice_count( $destination_topic_id );
1551
1552 do_action( 'bbp_move_reply_count', $move_reply_id, $source_topic_id, $destination_topic_id );
1553 }
1554
1555 /** Reply Actions *************************************************************/
1556
1557 /**
1558 * Handles the front end spamming/unspamming and trashing/untrashing/deleting of
1559 * replies.
1560 *
1561 * @since 2.0.0 bbPress (r2740)
1562 *
1563 * @param string $action The requested action to compare this function to.
1564 */
1565 function bbp_toggle_reply_handler( $action = '' ) {
1566
1567 // Bail if required GET actions aren't passed
1568 if ( empty( $_GET['reply_id'] ) ) {
1569 return;
1570 }
1571
1572 // What's the reply id?
1573 $reply_id = bbp_get_reply_id( (int) $_GET['reply_id'] );
1574
1575 // Get possible reply-handler toggles
1576 $toggles = bbp_get_reply_toggles( $reply_id );
1577
1578 // Bail if action isn't meant for this function
1579 if ( ! in_array( $action, $toggles, true ) ) {
1580 return;
1581 }
1582
1583 // Make sure reply exists
1584 $reply = bbp_get_reply( $reply_id );
1585 if ( empty( $reply ) ) {
1586 bbp_add_error( 'bbp_toggle_reply_missing', __( '<strong>Error</strong>: This reply could not be found or no longer exists.', 'bbpress' ) );
1587 return;
1588 }
1589
1590 // What is the user doing here?
1591 if ( ! current_user_can( 'edit_reply', $reply_id ) || ( 'bbp_toggle_reply_trash' === $action && ! current_user_can( 'delete_reply', $reply_id ) ) ) {
1592 bbp_add_error( 'bbp_toggle_reply_permission', __( '<strong>Error</strong>: You do not have permission to do that.', 'bbpress' ) );
1593 return;
1594 }
1595
1596 // Sub-action?
1597 $sub_action = ! empty( $_GET['sub_action'] )
1598 ? sanitize_key( $_GET['sub_action'] )
1599 : false;
1600
1601 // Preliminary array
1602 $post_data = array( 'ID' => $reply_id );
1603
1604 // Do the reply toggling
1605 $retval = bbp_toggle_reply(
1606 array(
1607 'id' => $reply_id,
1608 'action' => $action,
1609 'sub_action' => $sub_action,
1610 'data' => $post_data
1611 )
1612 );
1613
1614 // Do additional reply toggle actions
1615 do_action( 'bbp_toggle_reply_handler', $retval['status'], $post_data, $action );
1616
1617 // Redirect back to reply
1618 if ( ( false !== $retval['status'] ) && ! is_wp_error( $retval['status'] ) ) {
1619 bbp_redirect( $retval['redirect_to'] );
1620
1621 // Handle errors
1622 } else {
1623 bbp_add_error( 'bbp_toggle_reply', $retval['message'] );
1624 }
1625 }
1626
1627 /**
1628 * Do the actual reply toggling.
1629 *
1630 * This function is used by `bbp_toggle_reply_handler()` to do the actual heavy
1631 * lifting when it comes to toggling replies. It only really makes sense to call
1632 * within that context, so if you need to call this function directly, make sure
1633 * you're also doing what the handler does too.
1634 *
1635 * @since 2.6.0 bbPress (r6133)
1636 * @access private
1637 *
1638 * @param array $args
1639 */
1640 function bbp_toggle_reply( $args = array() ) {
1641
1642 // Parse the arguments
1643 $r = bbp_parse_args(
1644 $args,
1645 array(
1646 'id' => 0,
1647 'action' => '',
1648 'sub_action' => '',
1649 'data' => array()
1650 )
1651 );
1652
1653 // Build the nonce suffix
1654 $nonce_suffix = bbp_get_reply_post_type() . '_' . (int) $r['id'];
1655
1656 // Default return values
1657 $retval = array(
1658 'status' => 0,
1659 'message' => '',
1660 'redirect_to' => bbp_get_reply_url( $r['id'], bbp_get_redirect_to() ),
1661 'view_all' => false
1662 );
1663
1664 // What action are we trying to perform?
1665 switch ( $r['action'] ) {
1666
1667 // Toggle approve
1668 case 'bbp_toggle_reply_approve' :
1669 check_ajax_referer( "approve-{$nonce_suffix}" );
1670
1671 $is_approve = bbp_is_reply_pending( $r['id'] );
1672 $retval['status'] = $is_approve ? bbp_approve_reply( $r['id'] ) : bbp_unapprove_reply( $r['id'] );
1673 $retval['message'] = $is_approve ? __( '<strong>Error</strong>: There was a problem approving the reply.', 'bbpress' ) : __( '<strong>Error</strong>: There was a problem unapproving the reply.', 'bbpress' );
1674 $retval['view_all'] = ! $is_approve;
1675
1676 break;
1677
1678 // Toggle spam
1679 case 'bbp_toggle_reply_spam' :
1680 check_ajax_referer( "spam-{$nonce_suffix}" );
1681
1682 $is_spam = bbp_is_reply_spam( $r['id'] );
1683 $retval['status'] = $is_spam ? bbp_unspam_reply( $r['id'] ) : bbp_spam_reply( $r['id'] );
1684 $retval['message'] = $is_spam ? __( '<strong>Error</strong>: There was a problem unmarking the reply as spam.', 'bbpress' ) : __( '<strong>Error</strong>: There was a problem marking the reply as spam.', 'bbpress' );
1685 $retval['view_all'] = ! $is_spam;
1686
1687 break;
1688
1689 // Toggle trash
1690 case 'bbp_toggle_reply_trash' :
1691
1692 // Which subaction?
1693 switch ( $r['sub_action'] ) {
1694 case 'trash':
1695 check_ajax_referer( "trash-{$nonce_suffix}" );
1696
1697 $retval['view_all'] = true;
1698 $retval['status'] = wp_trash_post( $r['id'] );
1699 $retval['message'] = __( '<strong>Error</strong>: There was a problem trashing the reply.', 'bbpress' );
1700
1701 break;
1702
1703 case 'untrash':
1704 check_ajax_referer( "untrash-{$nonce_suffix}" );
1705
1706 $retval['status'] = wp_untrash_post( $r['id'] );
1707 $retval['message'] = __( '<strong>Error</strong>: There was a problem untrashing the reply.', 'bbpress' );
1708
1709 break;
1710
1711 case 'delete':
1712 check_ajax_referer( "delete-{$nonce_suffix}" );
1713
1714 $retval['status'] = wp_delete_post( $r['id'] );
1715 $retval['message'] = __( '<strong>Error</strong>: There was a problem deleting the reply.', 'bbpress' );
1716
1717 break;
1718 }
1719
1720 break;
1721 }
1722
1723 // Add view all if needed
1724 if ( ! empty( $retval['view_all'] ) ) {
1725 $retval['redirect_to'] = bbp_add_view_all( $retval['redirect_to'], true );
1726 }
1727
1728 // Filter & return
1729 return apply_filters( 'bbp_toggle_reply', $retval, $r, $args );
1730 }
1731
1732 /** Helpers *******************************************************************/
1733
1734 /**
1735 * Return an associative array of available reply statuses.
1736 *
1737 * @since 2.6.0 bbPress (r5399)
1738 *
1739 * @param int $reply_id Optional. Reply id.
1740 *
1741 * @return array
1742 */
1743 function bbp_get_reply_statuses( $reply_id = 0 ) {
1744
1745 // Filter & return
1746 return (array) apply_filters(
1747 'bbp_get_reply_statuses',
1748 array(
1749 bbp_get_public_status_id() => _x( 'Publish', 'Publish the reply', 'bbpress' ),
1750 bbp_get_spam_status_id() => _x( 'Spam', 'Spam the reply', 'bbpress' ),
1751 bbp_get_trash_status_id() => _x( 'Trash', 'Trash the reply', 'bbpress' ),
1752 bbp_get_pending_status_id() => _x( 'Pending', 'Mark reply as pending', 'bbpress' )
1753 ),
1754 $reply_id
1755 );
1756 }
1757
1758 /**
1759 * Return array of available reply toggle actions.
1760 *
1761 * @since 2.6.0 bbPress (r6133)
1762 *
1763 * @param int $reply_id Optional. Reply id.
1764 *
1765 * @return array
1766 */
1767 function bbp_get_reply_toggles( $reply_id = 0 ) {
1768
1769 // Filter & return
1770 return (array) apply_filters(
1771 'bbp_get_toggle_reply_actions',
1772 array(
1773 'bbp_toggle_reply_spam',
1774 'bbp_toggle_reply_trash',
1775 'bbp_toggle_reply_approve'
1776 ),
1777 $reply_id
1778 );
1779 }
1780
1781 /**
1782 * Return array of public reply statuses.
1783 *
1784 * @since 2.6.0 bbPress (r6705)
1785 *
1786 * @return array
1787 */
1788 function bbp_get_public_reply_statuses() {
1789 $statuses = array(
1790 bbp_get_public_status_id()
1791 );
1792
1793 // Filter & return
1794 return (array) apply_filters( 'bbp_get_public_reply_statuses', $statuses );
1795 }
1796
1797 /**
1798 * Return array of non-public reply statuses.
1799 *
1800 * @since 2.6.0 bbPress (r6791)
1801 *
1802 * @return array
1803 */
1804 function bbp_get_non_public_reply_statuses() {
1805 $statuses = array(
1806 bbp_get_trash_status_id(),
1807 bbp_get_spam_status_id(),
1808 bbp_get_pending_status_id()
1809 );
1810
1811 // Filter & return
1812 return (array) apply_filters( 'bbp_get_non_public_reply_statuses', $statuses );
1813 }
1814
1815 /** Reply Actions *************************************************************/
1816
1817 /**
1818 * Marks a reply as spam.
1819 *
1820 * @since 2.0.0 bbPress (r2740)
1821 *
1822 * @param int $reply_id Reply id.
1823 * @return mixed False or {@link WP_Error} on failure, reply id on success.
1824 */
1825 function bbp_spam_reply( $reply_id = 0 ) {
1826
1827 // Get reply
1828 $reply = bbp_get_reply( $reply_id );
1829 if ( empty( $reply ) ) {
1830 return $reply;
1831 }
1832
1833 // Bail if already spam
1834 if ( bbp_get_spam_status_id() === $reply->post_status ) {
1835 return false;
1836 }
1837
1838 // Execute pre spam code
1839 do_action( 'bbp_spam_reply', $reply_id );
1840
1841 // Add the original post status as post meta for future restoration
1842 add_post_meta( $reply_id, '_bbp_spam_meta_status', $reply->post_status );
1843
1844 // Set post status to spam
1845 $reply->post_status = bbp_get_spam_status_id();
1846
1847 // No revisions
1848 remove_action( 'pre_post_update', 'wp_save_post_revision' );
1849
1850 // Update the reply
1851 $reply_id = wp_update_post( $reply );
1852
1853 // Execute post spam code
1854 do_action( 'bbp_spammed_reply', $reply_id );
1855
1856 // Return reply_id
1857 return $reply_id;
1858 }
1859
1860 /**
1861 * Unspams a reply.
1862 *
1863 * @since 2.0.0 bbPress (r2740)
1864 *
1865 * @param int $reply_id Reply id.
1866 * @return mixed False or {@link WP_Error} on failure, reply id on success.
1867 */
1868 function bbp_unspam_reply( $reply_id = 0 ) {
1869
1870 // Get reply
1871 $reply = bbp_get_reply( $reply_id );
1872 if ( empty( $reply ) ) {
1873 return $reply;
1874 }
1875
1876 // Bail if already not spam
1877 if ( bbp_get_spam_status_id() !== $reply->post_status ) {
1878 return false;
1879 }
1880
1881 // Execute pre unspam code
1882 do_action( 'bbp_unspam_reply', $reply_id );
1883
1884 // Get pre spam status
1885 $reply->post_status = get_post_meta( $reply_id, '_bbp_spam_meta_status', true );
1886
1887 // If no previous status, default to publish
1888 if ( empty( $reply->post_status ) ) {
1889 $reply->post_status = bbp_get_public_status_id();
1890 }
1891
1892 // Delete pre spam meta
1893 delete_post_meta( $reply_id, '_bbp_spam_meta_status' );
1894
1895 // No revisions
1896 remove_action( 'pre_post_update', 'wp_save_post_revision' );
1897
1898 // Update the reply
1899 $reply_id = wp_update_post( $reply );
1900
1901 // Execute post unspam code
1902 do_action( 'bbp_unspammed_reply', $reply_id );
1903
1904 // Return reply_id
1905 return $reply_id;
1906 }
1907
1908 /**
1909 * Approves a reply.
1910 *
1911 * @since 2.6.0 bbPress (r5506)
1912 *
1913 * @param int $reply_id Reply id.
1914 * @return mixed False or {@link WP_Error} on failure, reply id on success.
1915 */
1916 function bbp_approve_reply( $reply_id = 0 ) {
1917
1918 // Get reply
1919 $reply = bbp_get_reply( $reply_id );
1920 if ( empty( $reply ) ) {
1921 return $reply;
1922 }
1923
1924 // Get new status
1925 $status = bbp_get_public_status_id();
1926
1927 // Bail if already approved
1928 if ( $status === $reply->post_status ) {
1929 return false;
1930 }
1931
1932 // Execute pre pending code
1933 do_action( 'bbp_approve_reply', $reply_id );
1934
1935 // Set publish status
1936 $reply->post_status = $status;
1937
1938 // Set post date GMT - prevents post_date override in wp_update_post()
1939 $reply->post_date_gmt = get_gmt_from_date( $reply->post_date );
1940
1941 // No revisions
1942 remove_action( 'pre_post_update', 'wp_save_post_revision' );
1943
1944 // Update reply
1945 $reply_id = wp_update_post( $reply );
1946
1947 // Execute post pending code
1948 do_action( 'bbp_approved_reply', $reply_id );
1949
1950 // Return reply_id
1951 return $reply_id;
1952 }
1953
1954 /**
1955 * Unapproves a reply.
1956 *
1957 * @since 2.6.0 bbPress (r5506)
1958 *
1959 * @param int $reply_id Reply id.
1960 * @return mixed False or {@link WP_Error} on failure, reply id on success.
1961 */
1962 function bbp_unapprove_reply( $reply_id = 0 ) {
1963
1964 // Get reply
1965 $reply = bbp_get_reply( $reply_id );
1966 if ( empty( $reply ) ) {
1967 return $reply;
1968 }
1969
1970 // Get new status
1971 $status = bbp_get_pending_status_id();
1972
1973 // Bail if already pending
1974 if ( $status === $reply->post_status ) {
1975 return false;
1976 }
1977
1978 // Execute pre open code
1979 do_action( 'bbp_unapprove_reply', $reply_id );
1980
1981 // Set pending status
1982 $reply->post_status = $status;
1983
1984 // No revisions
1985 remove_action( 'pre_post_update', 'wp_save_post_revision' );
1986
1987 // Update reply
1988 $reply_id = wp_update_post( $reply );
1989
1990 // Execute post open code
1991 do_action( 'bbp_unapproved_reply', $reply_id );
1992
1993 // Return reply_id
1994 return $reply_id;
1995 }
1996
1997 /** Before Delete/Trash/Untrash ***********************************************/
1998
1999 /**
2000 * Called before deleting a reply.
2001 *
2002 * @since 2.0.0 bbPress (r2895)
2003 */
2004 function bbp_delete_reply( $reply_id = 0 ) {
2005 $reply_id = bbp_get_reply_id( $reply_id );
2006
2007 if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
2008 return false;
2009 }
2010
2011 do_action( 'bbp_delete_reply', $reply_id );
2012 }
2013
2014 /**
2015 * Called before trashing a reply.
2016 *
2017 * @since 2.0.0 bbPress (r2895)
2018 */
2019 function bbp_trash_reply( $reply_id = 0 ) {
2020 $reply_id = bbp_get_reply_id( $reply_id );
2021
2022 if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
2023 return false;
2024 }
2025
2026 do_action( 'bbp_trash_reply', $reply_id );
2027 }
2028
2029 /**
2030 * Called before untrashing (restoring) a reply.
2031 *
2032 * @since 2.0.0 bbPress (r2895)
2033 */
2034 function bbp_untrash_reply( $reply_id = 0 ) {
2035 $reply_id = bbp_get_reply_id( $reply_id );
2036
2037 if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
2038 return false;
2039 }
2040
2041 do_action( 'bbp_untrash_reply', $reply_id );
2042 }
2043
2044 /** After Delete/Trash/Untrash ************************************************/
2045
2046 /**
2047 * Called after deleting a reply.
2048 *
2049 * @since 2.0.0 bbPress (r2993)
2050 */
2051 function bbp_deleted_reply( $reply_id = 0 ) {
2052 $reply_id = bbp_get_reply_id( $reply_id );
2053
2054 if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
2055 return false;
2056 }
2057
2058 do_action( 'bbp_deleted_reply', $reply_id );
2059 }
2060
2061 /**
2062 * Called after trashing a reply.
2063 *
2064 * @since 2.0.0 bbPress (r2993)
2065 */
2066 function bbp_trashed_reply( $reply_id = 0 ) {
2067 $reply_id = bbp_get_reply_id( $reply_id );
2068
2069 if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
2070 return false;
2071 }
2072
2073 do_action( 'bbp_trashed_reply', $reply_id );
2074 }
2075
2076 /**
2077 * Called after untrashing (restoring) a reply.
2078 *
2079 * @since 2.0.0 bbPress (r2993)
2080 */
2081 function bbp_untrashed_reply( $reply_id = 0 ) {
2082 $reply_id = bbp_get_reply_id( $reply_id );
2083
2084 if ( empty( $reply_id ) || ! bbp_is_reply( $reply_id ) ) {
2085 return false;
2086 }
2087
2088 do_action( 'bbp_untrashed_reply', $reply_id );
2089 }
2090
2091 /** Settings ******************************************************************/
2092
2093 /**
2094 * Return the replies per page setting.
2095 *
2096 * @since 2.0.0 bbPress (r3540)
2097 *
2098 * @param int $default Default replies per page (15).
2099 * @return int
2100 */
2101 function bbp_get_replies_per_page( $default = 15 ) {
2102
2103 // Get database option and cast as integer
2104 $retval = get_option( '_bbp_replies_per_page', $default );
2105
2106 // If return val is empty, set it to default
2107 if ( empty( $retval ) ) {
2108 $retval = $default;
2109 }
2110
2111 // Filter & return
2112 return (int) apply_filters( 'bbp_get_replies_per_page', $retval, $default );
2113 }
2114
2115 /**
2116 * Return the replies per RSS page setting.
2117 *
2118 * @since 2.0.0 bbPress (r3540)
2119 *
2120 * @param int $default Default replies per page (25).
2121 * @return int
2122 */
2123 function bbp_get_replies_per_rss_page( $default = 25 ) {
2124
2125 // Get database option and cast as integer
2126 $retval = get_option( '_bbp_replies_per_rss_page', $default );
2127
2128 // If return val is empty, set it to default
2129 if ( empty( $retval ) ) {
2130 $retval = $default;
2131 }
2132
2133 // Filter & return
2134 return (int) apply_filters( 'bbp_get_replies_per_rss_page', $retval, $default );
2135 }
2136
2137 /** Autoembed *****************************************************************/
2138
2139 /**
2140 * Check if auto-embeds are enabled and hook them in if so.
2141 *
2142 * @since 2.1.0 bbPress (r3752)
2143 *
2144 * @global WP_Embed $wp_embed
2145 */
2146 function bbp_reply_content_autoembed() {
2147 global $wp_embed;
2148
2149 if ( bbp_use_autoembed() && is_a( $wp_embed, 'WP_Embed' ) ) {
2150 add_filter( 'bbp_get_reply_content', array( $wp_embed, 'autoembed' ), 2 );
2151 }
2152 }
2153
2154 /** Filters *******************************************************************/
2155
2156 /**
2157 * Used by bbp_has_replies() to add the lead topic post to the posts loop.
2158 *
2159 * This function filters the 'post_where' of the WP_Query, and changes the query
2160 * to include both the topic AND its children in the same loop.
2161 *
2162 * @since 2.1.0 bbPress (r4058)
2163 *
2164 * @param string $where
2165 * @param WP_Query $query
2166 * @return string
2167 */
2168 function _bbp_has_replies_where( $where = '', $query = false ) {
2169
2170 /** Bail ******************************************************************/
2171
2172 // Bail if the sky is falling
2173 if ( empty( $where ) || empty( $query ) ) {
2174 return $where;
2175 }
2176
2177 // Bail if including specific post ID's
2178 if ( $query->get( 'post__in' ) ) {
2179 return $where;
2180 }
2181
2182 // Get post_parent from query (used to get the topic ID below)
2183 $post_parent = $query->get( 'post_parent' );
2184
2185 // Bail if post_parent is default '' (uses is_numeric() because WordPress does internally)
2186 if ( ! is_numeric( $post_parent ) ) {
2187 return $where;
2188 }
2189
2190 // Get post_type from query, define array to diff against
2191 $queried_types = (array) $query->get( 'post_type' );
2192 $post_types = array( bbp_get_topic_post_type(), bbp_get_reply_post_type() );
2193
2194 // Bail if query is not already for both topic and reply post types
2195 if ( array_diff( $post_types, $queried_types ) ) {
2196 return $where;
2197 }
2198
2199 /** Proceed ***************************************************************/
2200
2201 // Table name for posts
2202 $table_name = bbp_db()->prefix . 'posts';
2203
2204 // Get the topic ID from the post_parent, set in bbp_has_replies()
2205 $topic_id = bbp_get_topic_id( $post_parent );
2206
2207 // The texts to search for
2208 $search = array(
2209 "FROM {$table_name} ",
2210 "WHERE 1=1 AND {$table_name}.post_parent = {$topic_id}",
2211 ") AND {$table_name}.post_parent = {$topic_id}"
2212 );
2213
2214 // The texts to replace them with
2215 $replace = array(
2216 $search[0] . 'FORCE INDEX (PRIMARY, post_parent) ',
2217 "WHERE 1=1 AND ({$table_name}.ID = {$topic_id} OR {$table_name}.post_parent = {$topic_id})",
2218 ") AND ({$table_name}.ID = {$topic_id} OR {$table_name}.post_parent = {$topic_id})"
2219 );
2220
2221 // Try to replace the search text with the replacement
2222 $new_where = str_replace( $search, $replace, $where );
2223 if ( ! empty( $new_where ) ) {
2224 $where = $new_where;
2225 }
2226
2227 return $where;
2228 }
2229
2230 /** Feeds *********************************************************************/
2231
2232 /**
2233 * Output an RSS2 feed of replies, based on the query passed.
2234 *
2235 * @since 2.0.0 bbPress (r3171)
2236 *
2237 * @param array $replies_query
2238 */
2239 function bbp_display_replies_feed_rss2( $replies_query = array() ) {
2240
2241 // User cannot access forum this topic is in
2242 if ( bbp_is_single_topic() && ! bbp_user_can_view_forum( array( 'forum_id' => bbp_get_topic_forum_id() ) ) ) {
2243 return;
2244 }
2245
2246 // Adjust the title based on context
2247 if ( bbp_is_single_topic() ) {
2248 $title = get_wp_title_rss();
2249 } elseif ( ! bbp_show_lead_topic() ) {
2250 $title = get_bloginfo_rss( 'name' ) . ' &#187; ' . __( 'All Posts', 'bbpress' );
2251 } else {
2252 $title = get_bloginfo_rss( 'name' ) . ' &#187; ' . __( 'All Replies', 'bbpress' );
2253 }
2254
2255 $title = apply_filters( 'wp_title_rss', $title );
2256
2257 // Display the feed
2258 header( 'Content-Type: ' . feed_content_type( 'rss2' ) . '; charset=' . get_option( 'blog_charset' ), true );
2259 header( 'Status: 200 OK' );
2260 echo '<?xml version="1.0" encoding="' . get_option( 'blog_charset' ) . '"?' . '>'; ?>
2261
2262 <rss version="2.0"
2263 xmlns:content="http://purl.org/rss/1.0/modules/content/"
2264 xmlns:wfw="http://wellformedweb.org/CommentAPI/"
2265 xmlns:dc="http://purl.org/dc/elements/1.1/"
2266 xmlns:atom="http://www.w3.org/2005/Atom"
2267
2268 <?php do_action( 'bbp_feed' ); ?>
2269 >
2270
2271 <channel>
2272
2273 <title><?php echo $title; // Already escaped ?></title>
2274 <atom:link href="<?php self_link(); ?>" rel="self" type="application/rss+xml" />
2275 <link><?php self_link(); ?></link>
2276 <description><?php //?></description><?php // phpcs:ignore ?>
2277 <lastBuildDate><?php echo get_feed_build_date( 'r' ); ?></lastBuildDate>
2278 <generator><?php echo esc_url_raw( 'https://bbpress.org/?v=' . convert_chars( bbp_get_version() ) ); ?></generator>
2279 <language><?php bloginfo_rss( 'language' ); ?></language>
2280
2281 <?php do_action( 'bbp_feed_head' ); ?>
2282
2283 <?php if ( bbp_is_single_topic() ) : ?>
2284 <?php if ( bbp_user_can_view_forum( array( 'forum_id' => bbp_get_topic_forum_id() ) ) ) : ?>
2285 <?php if ( bbp_show_lead_topic() ) : ?>
2286
2287 <item>
2288 <guid><?php bbp_topic_permalink(); ?></guid>
2289 <title><![CDATA[<?php bbp_topic_title(); ?>]]></title>
2290 <link><?php bbp_topic_permalink(); ?></link>
2291 <pubDate><?php echo mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ); ?></pubDate>
2292 <dc:creator><?php bbp_topic_author_display_name(); ?></dc:creator>
2293
2294 <description>
2295 <![CDATA[
2296 <p>
2297 <?php
2298 printf(
2299 /* translators: %s: Number of replies */
2300 __( 'Replies: %s', 'bbpress' ),
2301 bbp_get_topic_reply_count()
2302 );
2303 ?>
2304 </p>
2305 <?php bbp_topic_content(); ?>
2306 ]]>
2307 </description>
2308
2309 <?php rss_enclosure(); ?>
2310
2311 <?php do_action( 'bbp_feed_item' ); ?>
2312
2313 </item>
2314
2315 <?php endif; ?>
2316 <?php endif; ?>
2317 <?php endif; ?>
2318
2319 <?php if ( bbp_has_replies( $replies_query ) ) : ?>
2320 <?php while ( bbp_replies() ) :
2321
2322 bbp_the_reply(); ?>
2323
2324 <item>
2325 <guid><?php bbp_reply_url(); ?></guid>
2326 <title><![CDATA[<?php bbp_reply_title(); ?>]]></title>
2327 <link><?php bbp_reply_url(); ?></link>
2328 <pubDate><?php echo mysql2date( 'D, d M Y H:i:s +0000', get_post_time( 'Y-m-d H:i:s', true ), false ); ?></pubDate>
2329 <dc:creator><?php bbp_reply_author_display_name(); ?></dc:creator>
2330
2331 <description>
2332 <![CDATA[
2333 <?php bbp_reply_content(); ?>
2334 ]]>
2335 </description>
2336
2337 <?php rss_enclosure(); ?>
2338
2339 <?php do_action( 'bbp_feed_item' ); ?>
2340
2341 </item>
2342
2343 <?php endwhile; ?>
2344 <?php endif; ?>
2345
2346 <?php do_action( 'bbp_feed_footer' ); ?>
2347
2348 </channel>
2349 </rss>
2350
2351 <?php
2352
2353 // We're done here
2354 exit();
2355 }
2356
2357 /** Permissions ***************************************************************/
2358
2359 /**
2360 * Redirect if unauthorized user is attempting to edit a reply.
2361 *
2362 * @since 2.1.0 bbPress (r3605)
2363 */
2364 function bbp_check_reply_edit() {
2365
2366 // Bail if not editing a topic
2367 if ( ! bbp_is_reply_edit() ) {
2368 return;
2369 }
2370
2371 // User cannot edit topic, so redirect back to reply
2372 if ( ! current_user_can( 'edit_reply', bbp_get_reply_id() ) ) {
2373 bbp_redirect( bbp_get_reply_url() );
2374 }
2375 }
2376
2377 /** Reply Position ************************************************************/
2378
2379 /**
2380 * Update the position of the reply.
2381 *
2382 * The reply position is stored in the menu_order column of the posts table.
2383 * This is done to prevent using a meta_query to retrieve posts in the proper
2384 * freshness order. By updating the menu_order accordingly, we're able to
2385 * leverage core WordPress query ordering much more effectively.
2386 *
2387 * @since 2.1.0 bbPress (r3933)
2388 *
2389 * @param int $reply_id
2390 * @param int $reply_position
2391 *
2392 * @return mixed
2393 */
2394 function bbp_update_reply_position( $reply_id = 0, $reply_position = false ) {
2395
2396 // Bail if reply_id is empty
2397 $reply_id = bbp_get_reply_id( $reply_id );
2398 if ( empty( $reply_id ) ) {
2399 return false;
2400 }
2401
2402 // Prepare the reply position
2403 $reply_position = is_numeric( $reply_position )
2404 ? (int) $reply_position
2405 : bbp_get_reply_position_raw( $reply_id, bbp_get_reply_topic_id( $reply_id ) );
2406
2407 // Get the current reply position
2408 $current_position = get_post_field( 'menu_order', $reply_id );
2409
2410 // Bail if no change
2411 if ( $reply_position === $current_position ) {
2412 return false;
2413 }
2414
2415 // Filters not removed
2416 $removed = false;
2417
2418 // Toggle revisions off as we are not altering content
2419 if ( has_filter( 'clean_post_cache', 'bbp_clean_post_cache' ) ) {
2420 $removed = true;
2421 remove_filter( 'clean_post_cache', 'bbp_clean_post_cache', 10, 2 );
2422 }
2423
2424 // Update the replies' 'menu_order' with the reply position
2425 $bbp_db = bbp_db();
2426 $bbp_db->update( $bbp_db->posts, array( 'menu_order' => $reply_position ), array( 'ID' => $reply_id ) );
2427 clean_post_cache( $reply_id );
2428
2429 // Toggle revisions back on
2430 if ( true === $removed ) {
2431 $removed = false;
2432 add_filter( 'clean_post_cache', 'bbp_clean_post_cache', 10, 2 );
2433 }
2434
2435 return (int) $reply_position;
2436 }
2437
2438 /**
2439 * Get the position of a reply by querying the DB directly for the replies
2440 * of a given topic.
2441 *
2442 * @since 2.1.0 bbPress (r3933)
2443 *
2444 * @param int $reply_id
2445 * @param int $topic_id
2446 */
2447 function bbp_get_reply_position_raw( $reply_id = 0, $topic_id = 0 ) {
2448
2449 // Get required data
2450 $reply_position = 0;
2451 $reply_id = bbp_get_reply_id( $reply_id );
2452 $topic_id = ! empty( $topic_id )
2453 ? bbp_get_topic_id( $topic_id )
2454 : bbp_get_reply_topic_id( $reply_id );
2455
2456 // If reply is actually the first post in a topic, return 0
2457 if ( $reply_id !== $topic_id ) {
2458
2459 // Make sure the topic has replies before running another query
2460 $reply_count = bbp_get_topic_reply_count( $topic_id, false );
2461 if ( ! empty( $reply_count ) ) {
2462
2463 // Get reply id's
2464 $topic_replies = bbp_get_all_child_ids( $topic_id, bbp_get_reply_post_type() );
2465 if ( ! empty( $topic_replies ) ) {
2466
2467 // Reverse replies array and search for current reply position
2468 $topic_replies = array_reverse( $topic_replies );
2469 $reply_position = array_search( $reply_id, $topic_replies, true );
2470
2471 // Bump the position to compensate for the lead topic post,
2472 // or reset to 0 if the reply was not found in the children list.
2473 if ( false !== $reply_position ) {
2474 ++$reply_position;
2475 } else {
2476 $reply_position = 0;
2477 }
2478 }
2479 }
2480 }
2481
2482 return (int) $reply_position;
2483 }
2484
2485 /** Hierarchical Replies ******************************************************/
2486
2487 /**
2488 * Are replies threaded?
2489 *
2490 * @since 2.4.0 bbPress (r4944)
2491 * @since 2.6.0 bbPress (r6245) Always false on user profile reply pages.
2492 *
2493 * @param bool $default Optional. Default value true.
2494 *
2495 * @return bool Are replies threaded?
2496 */
2497 function bbp_thread_replies() {
2498 $depth = bbp_thread_replies_depth();
2499 $allow = bbp_allow_threaded_replies();
2500
2501 // Never thread replies on user profile pages. It looks weird, and we know
2502 // it is undesirable for the majority of installations.
2503 if ( bbp_is_single_user_replies() ) {
2504 $retval = false;
2505 } else {
2506 $retval = (bool) ( ( $depth >= 2 ) && ( true === $allow ) );
2507 }
2508
2509 // Filter & return
2510 return (bool) apply_filters( 'bbp_thread_replies', $retval, $depth, $allow );
2511 }
2512
2513 /**
2514 * List threaded replies.
2515 *
2516 * @since 2.4.0 bbPress (r4944)
2517 */
2518 function bbp_list_replies( $args = array() ) {
2519
2520 // Get bbPress
2521 $bbp = bbpress();
2522
2523 // Reset the reply depth
2524 $bbp->reply_query->reply_depth = 0;
2525
2526 // In reply loop
2527 $bbp->reply_query->in_the_loop = true;
2528
2529 // Parse arguments
2530 $r = bbp_parse_args(
2531 $args,
2532 array(
2533 'walker' => new BBP_Walker_Reply(),
2534 'max_depth' => bbp_thread_replies_depth(),
2535 'style' => 'ul',
2536 'callback' => null,
2537 'end_callback' => null,
2538 'page' => 1,
2539 'per_page' => -1
2540 ),
2541 'list_replies'
2542 );
2543
2544 // Allowed styles (supported by BBP_Walker_Reply)
2545 $allowed = array( 'div', 'ol', 'ul' );
2546
2547 // Get style
2548 $style = in_array( $r['style'], $allowed, true )
2549 ? $r['style']
2550 : 'ul';
2551
2552 // Walk the replies
2553 $walked_html = $r['walker']->paged_walk(
2554 $bbp->reply_query->posts,
2555 $r['max_depth'],
2556 $r['page'],
2557 $r['per_page'],
2558 $r
2559 );
2560
2561 // Override the "max_num_pages" setting
2562 $bbp->max_num_pages = $r['walker']->max_pages;
2563
2564 // No longer in reply loop
2565 $bbp->reply_query->in_the_loop = false;
2566
2567 // Output the replies list
2568 echo "<{$style} class='bbp-replies-list'>" . $walked_html . "</{$style}>";
2569 }
2570
2571 /**
2572 * Validate a `reply_to` field for hierarchical replies.
2573 *
2574 * Checks for 2 scenarios:
2575 * -- The reply to ID is actually a reply
2576 * -- The reply to ID does not match the current reply
2577 *
2578 * @see https://bbpress.trac.wordpress.org/ticket/2588
2579 * @see https://bbpress.trac.wordpress.org/ticket/2586
2580 *
2581 * @since 2.5.4 bbPress (r5377)
2582 *
2583 * @param int $reply_to
2584 * @param int $reply_id
2585 *
2586 * @return int $reply_to
2587 */
2588 function bbp_validate_reply_to( $reply_to = 0, $reply_id = 0 ) {
2589
2590 // The parent reply must actually be a reply
2591 if ( ! bbp_is_reply( $reply_to ) ) {
2592 $reply_to = 0;
2593 }
2594
2595 // The parent reply cannot be itself
2596 if ( $reply_id === $reply_to ) {
2597 $reply_to = 0;
2598 }
2599
2600 return (int) $reply_to;
2601 }
2602