PluginProbe
bbPress / 2.6.17
bbPress v2.6.17
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 2.2 2.2.1 All 72 releases
bbpress / includes / common / functions.php

functions.php in bbPress 2.6.17, at includes/common/functions.php

3,060 lines 88.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * bbPress Common Functions
5 *
6 * Common functions are ones that are used by more than one component, like
7 * forums, topics, replies, users, topic tags, etc...
8 *
9 * @package bbPress
10 * @subpackage Functions
11 */
12
13 // Exit if accessed directly
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * Return array of bbPress registered post types
18 *
19 * @since 2.6.0 bbPress (r6813)
20 *
21 * @param array $args Array of arguments to pass into `get_post_types()`
22 *
23 * @return array
24 */
25 function bbp_get_post_types( $args = array() ) {
26
27 // Parse args
28 $r = bbp_parse_args(
29 $args,
30 array(
31 'source' => 'bbpress'
32 ),
33 'get_post_types'
34 );
35
36 // Return post types
37 return get_post_types( $r );
38 }
39
40 /** URLs **********************************************************************/
41
42 /**
43 * Return the unescaped redirect_to request value
44 *
45 * @bbPress (r4655)
46 *
47 * @return string The URL to redirect to, if set
48 */
49 function bbp_get_redirect_to() {
50
51 // Check 'redirect_to' request parameter
52 $retval = ! empty( $_REQUEST['redirect_to'] )
53 ? $_REQUEST['redirect_to']
54 : '';
55
56 // Filter & return
57 return apply_filters( 'bbp_get_redirect_to', $retval );
58 }
59
60 /**
61 * Append 'view=all' to query string if it's already there from referer
62 *
63 * @since 2.0.0 bbPress (r3325)
64 *
65 * @param string $original_link Original Link to be modified
66 * @param bool $force Override bbp_get_view_all() check
67 * @return string The link with 'view=all' appended if necessary
68 */
69 function bbp_add_view_all( $original_link = '', $force = false ) {
70
71 // Are we appending the view=all vars?
72 $link = ( bbp_get_view_all() || ! empty( $force ) )
73 ? add_query_arg( array( 'view' => 'all' ), $original_link )
74 : $original_link;
75
76 // Filter & return
77 return apply_filters( 'bbp_add_view_all', $link, $original_link );
78 }
79
80 /**
81 * Remove 'view=all' from query string
82 *
83 * @since 2.0.0 bbPress (r3325)
84 *
85 * @param string $original_link Original Link to be modified
86 * @return string The link with 'view=all' appended if necessary
87 */
88 function bbp_remove_view_all( $original_link = '' ) {
89
90 // Remove `view' argument
91 $link = remove_query_arg( 'view', $original_link );
92
93 // Filter & return
94 return apply_filters( 'bbp_remove_view_all', $link, $original_link );
95 }
96
97 /**
98 * If current user can and is viewing all topics/replies
99 *
100 * @since 2.0.0 bbPress (r3325)
101 *
102 * @param string $cap Capability used to ensure user can view all
103 *
104 * @return bool Whether current user can and is viewing all
105 */
106 function bbp_get_view_all( $cap = 'moderate' ) {
107 $retval = ( ( ! empty( $_GET['view'] ) && ( 'all' === $_GET['view'] ) && current_user_can( $cap ) ) );
108
109 // Filter & return
110 return (bool) apply_filters( 'bbp_get_view_all', (bool) $retval, $cap );
111 }
112
113 /**
114 * Assist pagination by returning correct page number
115 *
116 * @since 2.0.0 bbPress (r2628)
117 *
118 * @return int Current page number
119 */
120 function bbp_get_paged() {
121 $wp_query = bbp_get_wp_query();
122
123 // Check the query var
124 if ( get_query_var( 'paged' ) ) {
125 $paged = get_query_var( 'paged' );
126
127 // Check query paged
128 } elseif ( ! empty( $wp_query->query['paged'] ) ) {
129 $paged = $wp_query->query['paged'];
130 }
131
132 // Paged found
133 if ( ! empty( $paged ) ) {
134 return (int) $paged;
135 }
136
137 // Default to first page
138 return 1;
139 }
140
141 /** Misc **********************************************************************/
142
143 /**
144 * Return the unique non-empty values of an array.
145 *
146 * @since 2.6.0 bbPress (r6481)
147 *
148 * @param array $array Array to get values of
149 *
150 * @return array
151 */
152 function bbp_get_unique_array_values( $array = array() ) {
153 return array_unique( array_filter( array_values( $array ) ) );
154 }
155
156 /**
157 * Return the non-empty string values of an array.
158 *
159 * @since 2.6.17 bbPress
160 *
161 * @param mixed $arr Value or array to get string values of
162 *
163 * @return array
164 */
165 function bbp_get_string_array_values( $arr = array() ) {
166 $retval = array();
167
168 foreach ( (array) $arr as $value ) {
169 if ( is_string( $value ) && ( '' !== $value ) ) {
170 $retval[] = $value;
171 }
172 }
173
174 return $retval;
175 }
176
177 /**
178 * Fix post author id on post save
179 *
180 * When a logged in user changes the status of an anonymous reply or topic, or
181 * edits it, the post_author field is set to the logged in user's id. This
182 * function fixes that.
183 *
184 * @since 2.0.0 bbPress (r2734)
185 *
186 * @param array $data Post data
187 * @param array $postarr Original post array (includes post id)
188 * @return array Data
189 */
190 function bbp_fix_post_author( $data = array(), $postarr = array() ) {
191
192 // Post is not being updated or the post_author is already 0, return
193 if ( empty( $postarr['ID'] ) || empty( $data['post_author'] ) ) {
194 return $data;
195 }
196
197 // Post is not a topic or reply, return
198 if ( ! in_array( $data['post_type'], array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ), true ) ) {
199 return $data;
200 }
201
202 // Is the post by an anonymous user?
203 if (
204 ( bbp_get_topic_post_type() === $data['post_type'] && ! bbp_is_topic_anonymous( $postarr['ID'] ) )
205 ||
206 ( bbp_get_reply_post_type() === $data['post_type'] && ! bbp_is_reply_anonymous( $postarr['ID'] ) )
207 ) {
208 return $data;
209 }
210
211 // The post is being updated. It is a topic or a reply and is written by an anonymous user.
212 // Set the post_author back to 0
213 $data['post_author'] = 0;
214
215 return $data;
216 }
217
218 /**
219 * Use the previous status when restoring a forum, topic, or reply.
220 *
221 * Fixes an issue since WordPress 5.6.0. See
222 * {@link https://bbpress.trac.wordpress.org/ticket/3433}.
223 *
224 * @since 2.6.10 bbPress (r7233)
225 *
226 * @param string $new_status New status to use when untrashing. Default: 'draft'
227 * @param int $post_id Post ID
228 * @param string $previous_status Previous post status from '_wp_trash_meta_status' meta key. Default: 'pending'
229 */
230 function bbp_fix_untrash_post_status( $new_status = 'draft', $post_id = 0, $previous_status = 'pending' ) {
231
232 // Bail if not a forum, topic, or reply
233 if ( ! bbp_is_forum( $post_id ) && ! bbp_is_topic( $post_id ) && ! bbp_is_reply( $post_id ) ) {
234 return $new_status;
235 }
236
237 // Prefer the previous status, falling back to the new status
238 $retval = ! empty( $previous_status )
239 ? $previous_status
240 : $new_status;
241
242 return $retval;
243 }
244
245 /**
246 * Update related counts when a topic or reply is created or changes status.
247 *
248 * @since 2.6.17
249 *
250 * @param string $new_status New post status.
251 * @param string $old_status Old post status.
252 * @param WP_Post $post Post object.
253 */
254 function bbp_update_counts_on_transition_post_status( $new_status = '', $old_status = '', $post = false ) {
255
256 /**
257 * Short-circuits count updates for a persisted post-status transition.
258 *
259 * Returning a non-null value prevents the normal topic and reply count
260 * updates. This allows integrations with custom post-status lifecycles or
261 * count storage to replace the complete transition operation.
262 *
263 * @since 2.6.17
264 *
265 * @param null|bool $check Whether to short-circuit count updates.
266 * @param string $new_status New post status.
267 * @param string $old_status Old post status.
268 * @param WP_Post $post Post object.
269 */
270 $check = apply_filters( 'bbp_pre_update_counts_on_transition_post_status', null, $new_status, $old_status, $post );
271 if ( null !== $check ) {
272 return (bool) $check;
273 }
274
275 // Bail if the status did not change
276 if ( $new_status === $old_status ) {
277 return;
278 }
279
280 $is_new = ( 'new' === $old_status );
281
282 // Topic counts
283 if ( bbp_get_topic_post_type() === $post->post_type ) {
284 $was_public = in_array( $old_status, bbp_get_public_topic_statuses(), true );
285 $is_public = in_array( $new_status, bbp_get_public_topic_statuses(), true );
286 $was_hidden = in_array( $old_status, bbp_get_non_public_topic_statuses(), true );
287 $is_hidden = in_array( $new_status, bbp_get_non_public_topic_statuses(), true );
288 $public_difference = (int) $is_public - (int) $was_public;
289 $hidden_difference = (int) $is_hidden - (int) $was_hidden;
290
291 // A new topic or count boundary crossing changes at least one count
292 if ( ! empty( $public_difference ) || ! empty( $hidden_difference ) ) {
293 $forum_id = $is_new
294 ? $post->post_parent
295 : bbp_get_topic_forum_id( $post->ID );
296
297 // Update the forum's public topic count
298 if ( ! empty( $forum_id ) && ! empty( $public_difference ) ) {
299 bbp_bump_forum_topic_count( $forum_id, $public_difference );
300 }
301
302 // Update the forum's hidden topic count
303 if ( ! empty( $forum_id ) && ! empty( $hidden_difference ) ) {
304 bbp_bump_forum_topic_count_hidden( $forum_id, $hidden_difference );
305 }
306
307 // User counts only include public topics
308 if ( ! empty( $public_difference ) ) {
309 bbp_bump_user_topic_count( $post->post_author, $public_difference );
310 }
311
312 // Apply every public reply when its topic crosses the public boundary
313 if ( ! $is_new && ! empty( $forum_id ) && ! empty( $public_difference ) ) {
314 $reply_count = bbp_get_topic_reply_count( $post->ID, true );
315 $reply_difference = $reply_count * $public_difference;
316
317 if ( ! empty( $reply_difference ) ) {
318 bbp_bump_forum_reply_count( $forum_id, $reply_difference );
319 }
320 }
321 }
322
323 // Reply counts
324 } elseif ( bbp_get_reply_post_type() === $post->post_type ) {
325 $was_public = in_array( $old_status, bbp_get_public_reply_statuses(), true );
326 $is_public = in_array( $new_status, bbp_get_public_reply_statuses(), true );
327 $was_hidden = in_array( $old_status, bbp_get_non_public_reply_statuses(), true );
328 $is_hidden = in_array( $new_status, bbp_get_non_public_reply_statuses(), true );
329 $public_difference = (int) $is_public - (int) $was_public;
330 $hidden_difference = (int) $is_hidden - (int) $was_hidden;
331
332 // A new reply or count boundary crossing changes at least one count
333 if ( ! empty( $public_difference ) || ! empty( $hidden_difference ) ) {
334 $topic_id = $is_new
335 ? $post->post_parent
336 : bbp_get_reply_topic_id( $post->ID );
337 $forum_id = $is_new
338 ? bbp_get_topic_forum_id( $topic_id )
339 : bbp_get_reply_forum_id( $post->ID );
340 $forum_public_difference = bbp_is_topic_public( $topic_id )
341 ? $public_difference
342 : 0;
343
344 // Update the topic's public reply count
345 if ( ! empty( $topic_id ) && ! empty( $public_difference ) ) {
346 bbp_bump_topic_reply_count( $topic_id, $public_difference );
347 }
348
349 // Update the topic's hidden reply count
350 if ( ! empty( $topic_id ) && ! empty( $hidden_difference ) ) {
351 bbp_bump_topic_reply_count_hidden( $topic_id, $hidden_difference );
352 }
353
354 // Update the forum's public reply count
355 if ( ! empty( $forum_id ) && ! empty( $forum_public_difference ) ) {
356 bbp_bump_forum_reply_count( $forum_id, $forum_public_difference );
357 }
358
359 // Update the forum's hidden reply count
360 if ( ! empty( $forum_id ) && ! empty( $hidden_difference ) ) {
361 bbp_bump_forum_reply_count_hidden( $forum_id, $hidden_difference );
362 }
363
364 // User counts only include public replies
365 if ( ! empty( $public_difference ) ) {
366 bbp_bump_user_reply_count( $post->post_author, $public_difference );
367 }
368 }
369 }
370 }
371
372 /**
373 * Check a date against the length of time something can be edited.
374 *
375 * It is recommended to leave $utc set to true and to work with UTC/GMT dates.
376 * Turning this off will use the WordPress offset which is likely undesirable.
377 *
378 * @since 2.0.0 bbPress (r3133)
379 * @since 2.6.0 bbPress (r6868) Inverted some logic and added unit tests
380 *
381 * @param string $datetime Gets run through strtotime()
382 * @param boolean $utc Default true. Is the timestamp in UTC?
383 *
384 * @return bool True by default, if date is past, or editing is disabled.
385 */
386 function bbp_past_edit_lock( $datetime = '', $utc = true ) {
387
388 // Default value
389 $retval = true;
390
391 // Check if date and editing is allowed
392 if ( bbp_allow_content_edit() ) {
393
394 // Get number of minutes to allow editing for
395 $minutes = bbp_get_edit_lock();
396
397 // 0 minutes means forever, so can never be past edit-lock time
398 if ( 0 === $minutes ) {
399 $retval = false;
400
401 // Checking against a specific datetime
402 } elseif ( ! empty( $datetime ) ) {
403
404 // Period of time
405 $lockable = "+{$minutes} minutes";
406 if ( true === $utc ) {
407 $lockable .= ' UTC';
408 }
409
410 // Now
411 $cur_time = current_time( 'timestamp', $utc ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
412
413 // Get the duration in seconds
414 $duration = strtotime( $lockable ) - $cur_time;
415
416 // Diff the times down to seconds
417 $lock_time = strtotime( $lockable, $cur_time );
418 $past_time = strtotime( $datetime, $cur_time );
419 $diff_time = ( $lock_time - $past_time ) - $duration;
420
421 // Check if less than lock time
422 if ( $diff_time < $duration ) {
423 $retval = false;
424 }
425 }
426 }
427
428 // Filter & return
429 return (bool) apply_filters( 'bbp_past_edit_lock', $retval, $datetime, $utc );
430 }
431
432 /**
433 * Get number of days something should remain trashed for before it is cleaned
434 * up by WordPress Cron. If set to 0, items will skip trash and be deleted
435 * immediately.
436 *
437 * @since 2.6.0 bbPress (r6424)
438 *
439 * @param string $context Provide context for additional filtering
440 * @return int Number of days items remain in trash
441 */
442 function bbp_get_trash_days( $context = 'forum' ) {
443
444 // Sanitize the context
445 $context = sanitize_key( $context );
446
447 // Check the WordPress constant
448 $days = defined( 'EMPTY_TRASH_DAYS' )
449 ? (int) EMPTY_TRASH_DAYS
450 : 30;
451
452 // Filter & return
453 return (int) apply_filters( 'bbp_get_trash_days', $days, $context );
454 }
455
456 /** Statistics ****************************************************************/
457
458 /**
459 * Get the forum statistics
460 *
461 * @since 2.0.0 bbPress (r2769)
462 * @since 2.6.0 bbPress (r6055) Added:
463 * `count_pending_topics`
464 * `count_pending_replies`
465 * @since 2.6.10 bbPress (r7235) Renamed:
466 * `count_trashed_topics` to `count_trash_topics`
467 * `count_trashed_replies` to `count_trash_replies`
468 * `count_spammed_topics` to `count_spam_topics`
469 * `count_spammed_replies` to `count_spam_replies`
470 * Added:
471 * `count_hidden_topics`
472 * `count_hidden_replies`
473 *
474 * @param array $args Optional. The function supports these arguments (all
475 * default to true):
476 *
477 * - count_users: Count users?
478 * - count_forums: Count forums?
479 * - count_topics: Count topics? If set to false, private, spam and
480 * trash topics are also not counted.
481 * - count_pending_topics: Count pending topics? (only counted if the current
482 * user has edit_others_topics cap)
483 * - count_private_topics: Count private topics? (only counted if the current
484 * user has read_private_topics cap)
485 * - count_hidden_topics: Count hidden topics? (only counted if the current
486 * user has read_hidden_topics cap)
487 * - count_spam_topics: Count spam topics? (only counted if the current
488 * user has edit_others_topics cap)
489 * - count_trash_topics: Count trash topics? (only counted if the current
490 * user has view_trash cap)
491 * - count_replies: Count replies? If set to false, private, spam and
492 * trash replies are also not counted.
493 * - count_pending_replies: Count pending replies? (only counted if the current
494 * user has edit_others_replies cap)
495 * - count_private_replies: Count private replies? (only counted if the current
496 * user has read_private_replies cap)
497 * - count_hidden_replies: Count hidden replies? (only counted if the current
498 * user has read_hidden_replies cap)
499 * - count_spam_replies: Count spam replies? (only counted if the current
500 * user has edit_others_replies cap)
501 * - count_trash_replies: Count trash replies? (only counted if the current
502 * user has view_trash cap)
503 * - count_tags: Count tags? If set to false, empty tags are also
504 * not counted
505 * - count_empty_tags: Count empty tags?
506 *
507 * @return array Array of statistics
508 */
509 function bbp_get_statistics( $args = array() ) {
510
511 // Parse arguments against default values
512 $r = bbp_parse_args(
513 $args,
514 array(
515
516 // Users
517 'count_users' => true,
518
519 // Forums
520 'count_forums' => true,
521
522 // Topics
523 'count_topics' => true,
524 'count_pending_topics' => true,
525 'count_private_topics' => true,
526 'count_spam_topics' => true,
527 'count_trash_topics' => true,
528 'count_hidden_topics' => true,
529
530 // Replies
531 'count_replies' => true,
532 'count_pending_replies' => true,
533 'count_private_replies' => true,
534 'count_spam_replies' => true,
535 'count_trash_replies' => true,
536 'count_hidden_replies' => true,
537
538 // Topic tags
539 'count_tags' => true,
540 'count_empty_tags' => true
541
542 ),
543 'get_statistics'
544 );
545
546 // Defaults
547 $topic_count = $topic_count_hidden = 0;
548 $reply_count = $reply_count_hidden = 0;
549 $topic_tag_count = $empty_topic_tag_count = 0;
550 $hidden_topic_title = $hidden_reply_title = '';
551
552 // Post statuses
553 $publish = bbp_get_public_status_id();
554 $closed = bbp_get_closed_status_id();
555 $pending = bbp_get_pending_status_id();
556 $private = bbp_get_private_status_id();
557 $hidden = bbp_get_hidden_status_id();
558 $spam = bbp_get_spam_status_id();
559 $trash = bbp_get_trash_status_id();
560
561 // Users
562 $user_count = ! empty( $r['count_users'] )
563 ? bbp_get_total_users()
564 : 0;
565
566 // Forums
567 $forum_count = ! empty( $r['count_forums'] )
568 ? wp_count_posts( bbp_get_forum_post_type() )->{$publish}
569 : 0;
570
571 // Default capabilities
572 $caps = array(
573 'view_trash' => false,
574 'read_private_topics' => false,
575 'edit_others_topics' => false,
576 'read_private_replies' => false,
577 'edit_others_replies' => false,
578 'edit_topic_tags' => false
579 );
580
581 // Get capabilities
582 foreach ( $caps as $key => $cap ) {
583 $caps[ $key ] = current_user_can( $cap );
584 }
585
586 // Topics
587 if ( ! empty( $r['count_topics'] ) ) {
588
589 // Count all topics
590 $all_topics = wp_count_posts( bbp_get_topic_post_type() );
591
592 // Published (publish + closed)
593 $topic_count = $all_topics->{$publish} + $all_topics->{$closed};
594
595 // Declare empty arrays
596 $topics = $topic_titles = array_fill_keys( bbp_get_non_public_topic_statuses(), '' );
597
598 // Pending
599 if ( ! empty( $r['count_pending_topics'] ) && ! empty( $caps['edit_others_topics'] ) ) {
600 $topics[ $pending ] = bbp_number_not_negative( $all_topics->{$pending} );
601 /* translators: %s: Number of pending topics */
602 $topic_titles[ $pending ] = sprintf( esc_html__( 'Pending: %s', 'bbpress' ), bbp_number_format_i18n( $topics[ $pending ] ) );
603 }
604
605 // Private
606 if ( ! empty( $r['count_private_topics'] ) && ! empty( $caps['read_private_topics'] ) ) {
607 $topics[ $private ] = bbp_number_not_negative( $all_topics->{$private} );
608 /* translators: %s: Number of private topics */
609 $topic_titles[ $private ] = sprintf( esc_html__( 'Private: %s', 'bbpress' ), bbp_number_format_i18n( $topics[ $private ] ) );
610 }
611
612 // Hidden
613 if ( ! empty( $r['count_hidden_topics'] ) && ! empty( $caps['read_hidden_topics'] ) ) {
614 $topics[ $hidden ] = bbp_number_not_negative( $all_topics->{$hidden} );
615 /* translators: %s: Number of hidden topics */
616 $topic_titles[ $hidden ] = sprintf( esc_html__( 'Hidden: %s', 'bbpress' ), bbp_number_format_i18n( $topics[ $hidden ] ) );
617 }
618
619 // Spam
620 if ( ! empty( $r['count_spam_topics'] ) && ! empty( $caps['edit_others_topics'] ) ) {
621 $topics[ $spam ] = bbp_number_not_negative( $all_topics->{$spam} );
622 /* translators: %s: Number of spam topics */
623 $topic_titles[ $spam ] = sprintf( esc_html__( 'Spammed: %s', 'bbpress' ), bbp_number_format_i18n( $topics[ $spam ] ) );
624 }
625
626 // Trash
627 if ( ! empty( $r['count_trash_topics'] ) && ! empty( $caps['view_trash'] ) ) {
628 $topics[ $trash ] = bbp_number_not_negative( $all_topics->{$trash} );
629 /* translators: %s: Number of trashed topics */
630 $topic_titles[ $trash ] = sprintf( esc_html__( 'Trashed: %s', 'bbpress' ), bbp_number_format_i18n( $topics[ $trash ] ) );
631 }
632
633 // Total hidden (pending, private, hidden, spam, trash)
634 $topic_count_hidden = array_sum( array_filter( $topics ) );
635
636 // Compile the hidden topic title
637 $hidden_topic_title = implode( ' | ', array_filter( $topic_titles ) );
638 }
639
640 // Replies
641 if ( ! empty( $r['count_replies'] ) ) {
642
643 // Count all replies
644 $all_replies = wp_count_posts( bbp_get_reply_post_type() );
645
646 // Published
647 $reply_count = $all_replies->{$publish};
648
649 // Declare empty arrays
650 $replies = $reply_titles = array_fill_keys( bbp_get_non_public_reply_statuses(), '' );
651
652 // Pending
653 if ( ! empty( $r['count_pending_replies'] ) && ! empty( $caps['edit_others_replies'] ) ) {
654 $replies[ $pending ] = bbp_number_not_negative( $all_replies->{$pending} );
655 /* translators: %s: Number of pending replies */
656 $reply_titles[ $pending ] = sprintf( esc_html__( 'Pending: %s', 'bbpress' ), bbp_number_format_i18n( $replies[ $pending ] ) );
657 }
658
659 // Private
660 if ( ! empty( $r['count_private_replies'] ) && ! empty( $caps['read_private_replies'] ) ) {
661 $replies[ $private ] = bbp_number_not_negative( $all_replies->{$private} );
662 /* translators: %s: Number of private replies */
663 $reply_titles[ $private ] = sprintf( esc_html__( 'Private: %s', 'bbpress' ), bbp_number_format_i18n( $replies[ $private ] ) );
664 }
665
666 // Hidden
667 if ( ! empty( $r['count_hidden_replies'] ) && ! empty( $caps['read_hidden_replies'] ) ) {
668 $replies[ $hidden ] = bbp_number_not_negative( $all_replies->{$hidden} );
669 /* translators: %s: Number of hidden replies */
670 $reply_titles[ $hidden ] = sprintf( esc_html__( 'Hidden: %s', 'bbpress' ), bbp_number_format_i18n( $replies[ $hidden ] ) );
671 }
672
673 // Spam
674 if ( ! empty( $r['count_spam_replies'] ) && ! empty( $caps['edit_others_replies'] ) ) {
675 $replies[ $spam ] = bbp_number_not_negative( $all_replies->{$spam} );
676 /* translators: %s: Number of spam replies */
677 $reply_titles[ $spam ] = sprintf( esc_html__( 'Spammed: %s', 'bbpress' ), bbp_number_format_i18n( $replies[ $spam ] ) );
678 }
679
680 // Trash
681 if ( ! empty( $r['count_trash_replies'] ) && ! empty( $caps['view_trash'] ) ) {
682 $replies[ $trash ] = bbp_number_not_negative( $all_replies->{$trash} );
683 /* translators: %s: Number of trashed replies */
684 $reply_titles[ $trash ] = sprintf( esc_html__( 'Trashed: %s', 'bbpress' ), bbp_number_format_i18n( $replies[ $trash ] ) );
685 }
686
687 // Total hidden (pending, private, hidden, spam, trash)
688 $reply_count_hidden = array_sum( array_filter( $replies ) );
689
690 // Compile the hidden replies title
691 $hidden_reply_title = implode( ' | ', $reply_titles );
692 }
693
694 // Topic Tags
695 if ( ! empty( $r['count_tags'] ) && bbp_allow_topic_tags() ) {
696
697 // Get the topic-tag taxonomy ID
698 $tt_id = bbp_get_topic_tag_tax_id();
699
700 // Get the count
701 $topic_tag_count = wp_count_terms(
702 array(
703 'taxonomy' => $tt_id,
704 'hide_empty' => true
705 )
706 );
707
708 // Empty tags
709 if ( ! empty( $r['count_empty_tags'] ) && ! empty( 'edit_topic_tags' ) ) {
710 $empty_topic_tag_count = wp_count_terms( $tt_id ) - $topic_tag_count;
711 }
712 }
713
714 // Tally the tallies
715 $counts = compact(
716 'user_count',
717 'forum_count',
718 'topic_count',
719 'topic_count_hidden',
720 'reply_count',
721 'reply_count_hidden',
722 'topic_tag_count',
723 'empty_topic_tag_count'
724 );
725
726 // Define return value
727 $statistics = array();
728
729 // Loop through and store the integer and i18n formatted counts
730 foreach ( $counts as $key => $count ) {
731 $not_negative = bbp_number_not_negative( $count );
732 $statistics[ $key ] = bbp_number_format_i18n( $not_negative );
733 $statistics[ "{$key}_int" ] = $not_negative;
734 }
735
736 // Add the hidden (topic/reply) count title attribute strings
737 $statistics['hidden_topic_title'] = $hidden_topic_title;
738 $statistics['hidden_reply_title'] = $hidden_reply_title;
739
740 // Filter & return
741 return (array) apply_filters( 'bbp_get_statistics', $statistics, $r, $args );
742 }
743
744 /** New/edit topic/reply helpers **********************************************/
745
746 /**
747 * Filter anonymous post data
748 *
749 * We use REMOTE_ADDR here directly. If you are behind a proxy, you should
750 * ensure that it is properly set, such as in wp-config.php, for your
751 * environment. See {@link https://core.trac.wordpress.org/ticket/9235}
752 *
753 * Note that bbp_pre_anonymous_filters() is responsible for sanitizing each
754 * of the filtered core anonymous values here.
755 *
756 * If there are any errors, those are directly added to {@link bbPress:errors}
757 *
758 * @since 2.0.0 bbPress (r2734)
759 *
760 * @param array $args Optional. If no args are there, then $_POST values are
761 * @return bool|array False on errors, values in an array on success
762 */
763 function bbp_filter_anonymous_post_data( $args = array() ) {
764
765 // Parse arguments against default values
766 $r = bbp_parse_args(
767 $args,
768 array(
769 'bbp_anonymous_name' => ! empty( $_POST['bbp_anonymous_name'] ) ? $_POST['bbp_anonymous_name'] : false,
770 'bbp_anonymous_email' => ! empty( $_POST['bbp_anonymous_email'] ) ? $_POST['bbp_anonymous_email'] : false,
771 'bbp_anonymous_website' => ! empty( $_POST['bbp_anonymous_website'] ) ? $_POST['bbp_anonymous_website'] : false,
772 ),
773 'filter_anonymous_post_data'
774 );
775
776 // Strip invalid characters
777 $r = bbp_sanitize_anonymous_post_author( $r );
778
779 // Filter name
780 $r['bbp_anonymous_name'] = apply_filters( 'bbp_pre_anonymous_post_author_name', $r['bbp_anonymous_name'] );
781 if ( empty( $r['bbp_anonymous_name'] ) ) {
782 bbp_add_error( 'bbp_anonymous_name', __( '<strong>Error</strong>: Invalid author name.', 'bbpress' ) );
783 }
784
785 // Filter email address
786 $r['bbp_anonymous_email'] = apply_filters( 'bbp_pre_anonymous_post_author_email', $r['bbp_anonymous_email'] );
787 if ( empty( $r['bbp_anonymous_email'] ) ) {
788 bbp_add_error( 'bbp_anonymous_email', __( '<strong>Error</strong>: Invalid email address.', 'bbpress' ) );
789 }
790
791 // Website is optional (can be empty)
792 $r['bbp_anonymous_website'] = apply_filters( 'bbp_pre_anonymous_post_author_website', $r['bbp_anonymous_website'] );
793
794 // Filter & return
795 return (array) apply_filters( 'bbp_filter_anonymous_post_data', $r, $args );
796 }
797
798 /**
799 * Sanitize an array of anonymous post author data
800 *
801 * @since 2.6.0 bbPress (r6400)
802 *
803 * @param array $anonymous_data
804 * @return array
805 */
806 function bbp_sanitize_anonymous_post_author( $anonymous_data = array() ) {
807
808 // Make sure anonymous data is an array
809 if ( ! is_array( $anonymous_data ) ) {
810 $anonymous_data = array();
811 }
812
813 // Map meta data to comment fields (as guides for stripping invalid text)
814 $fields = array(
815 'bbp_anonymous_name' => 'comment_author',
816 'bbp_anonymous_email' => 'comment_author_email',
817 'bbp_anonymous_website' => 'comment_author_url'
818 );
819
820 // Setup a new return array
821 $r = $anonymous_data;
822
823 // Get the database
824 $bbp_db = bbp_db();
825
826 // Strip invalid text from fields
827 foreach ( $fields as $bbp_field => $comment_field ) {
828 if ( ! empty( $r[ $bbp_field ] ) ) {
829 $r[ $bbp_field ] = $bbp_db->strip_invalid_text_for_column( $bbp_db->comments, $comment_field, $r[ $bbp_field ] );
830 }
831 }
832
833 // Filter & return
834 return (array) apply_filters( 'bbp_sanitize_anonymous_post_author', $r, $anonymous_data );
835 }
836
837 /**
838 * Update the relevant meta-data for an anonymous post author
839 *
840 * @since 2.6.0 bbPress (r6400)
841 *
842 * @param int $post_id
843 * @param array $anonymous_data
844 * @param string $post_type
845 */
846 function bbp_update_anonymous_post_author( $post_id = 0, $anonymous_data = array(), $post_type = '' ) {
847
848 // Maybe look for anonymous
849 if ( empty( $anonymous_data ) ) {
850 $anonymous_data = bbp_filter_anonymous_post_data();
851 }
852
853 // Sanitize parameters
854 $post_id = (int) $post_id;
855 $post_type = sanitize_key( $post_type );
856
857 // Bail if missing required data
858 if ( empty( $post_id ) || empty( $post_type ) || empty( $anonymous_data ) ) {
859 return;
860 }
861
862 // Parse arguments against default values
863 $r = bbp_parse_args(
864 $anonymous_data,
865 array(
866 'bbp_anonymous_name' => '',
867 'bbp_anonymous_email' => '',
868 'bbp_anonymous_website' => '',
869 ),
870 "update_{$post_type}"
871 );
872
873 // Update all anonymous metas
874 foreach ( $r as $anon_key => $anon_value ) {
875
876 // Update, or delete if empty
877 ! empty( $anon_value )
878 ? update_post_meta( $post_id, '_' . $anon_key, (string) $anon_value, false )
879 : delete_post_meta( $post_id, '_' . $anon_key );
880 }
881 }
882
883 /**
884 * Check for duplicate topics/replies
885 *
886 * Check to make sure that a user is not making a duplicate post
887 *
888 * @since 2.0.0 bbPress (r2763)
889 *
890 * @param array $post_data Contains information about the comment
891 * @return bool True if it is not a duplicate, false if it is
892 */
893 function bbp_check_for_duplicate( $post_data = array() ) {
894
895 // Parse arguments against default values
896 $r = bbp_parse_args(
897 $post_data,
898 array(
899 'post_author' => 0,
900 'post_type' => array( bbp_get_topic_post_type(), bbp_get_reply_post_type() ),
901 'post_parent' => 0,
902 'post_content' => '',
903 'post_status' => bbp_get_trash_status_id(),
904 'anonymous_data' => array()
905 ),
906 'check_for_duplicate'
907 );
908
909 // No duplicate checks for those who can throttle
910 if ( user_can( (int) $r['post_author'], 'throttle' ) ) {
911 return true;
912 }
913
914 // Get the DB
915 $bbp_db = bbp_db();
916
917 // Default clauses
918 $join = $where = '';
919
920 // Check for anonymous post
921 if ( empty( $r['post_author'] ) && ( ! empty( $r['anonymous_data'] ) && ! empty( $r['anonymous_data']['bbp_anonymous_email'] ) ) ) {
922
923 // Sanitize the email address for querying
924 $email = sanitize_email( $r['anonymous_data']['bbp_anonymous_email'] );
925
926 // Only proceed
927 if ( ! empty( $email ) && is_email( $email ) ) {
928
929 // Get the meta SQL
930 $clauses = get_meta_sql(
931 array(
932 array(
933 'key' => '_bbp_anonymous_email',
934 'value' => $email,
935 )
936 ),
937 'post',
938 $bbp_db->posts,
939 'ID'
940 );
941
942 // Set clauses
943 $join = $clauses['join'];
944
945 // "'", "%", "$" and are valid characters in email addresses
946 $where = $bbp_db->remove_placeholder_escape( $clauses['where'] );
947 }
948 }
949
950 // Unslash $r to pass through DB->prepare()
951 //
952 // @see: https://bbpress.trac.wordpress.org/ticket/2185/
953 // @see: https://core.trac.wordpress.org/changeset/23973/
954 $r = wp_unslash( $r );
955
956 // Prepare duplicate check query
957 $query = "SELECT ID FROM {$bbp_db->posts} {$join}";
958 $query .= $bbp_db->prepare('WHERE post_type = %s AND post_status != %s AND post_author = %d AND post_content = %s', $r['post_type'], $r['post_status'], $r['post_author'], $r['post_content'] );
959 $query .= ! empty( $r['post_parent'] )
960 ? $bbp_db->prepare( ' AND post_parent = %d', $r['post_parent'] )
961 : '';
962 $query .= $where;
963 $query .= ' LIMIT 1';
964 $dupe = apply_filters( 'bbp_check_for_duplicate_query', $query, $r );
965
966 // Dupe found
967 if ( $bbp_db->get_var( $dupe ) ) {
968 do_action( 'bbp_check_for_duplicate_trigger', $post_data );
969 return false;
970 }
971
972 // Dupe not found
973 return true;
974 }
975
976 /**
977 * Check for flooding
978 *
979 * Check to make sure that a user is not making too many posts in a short amount
980 * of time.
981 *
982 * @since 2.0.0 bbPress (r2734)
983 *
984 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
985 * supply if supplying $author_id. Should be
986 * sanitized (see {@link bbp_filter_anonymous_post_data()}
987 * @param int $author_id Optional. Supply if it's a post by a logged in user.
988 * Do not supply if supplying $anonymous_data.
989 * @return bool True if there is no flooding, false if there is
990 */
991 function bbp_check_for_flood( $anonymous_data = array(), $author_id = 0 ) {
992
993 // Allow for flood check to be skipped
994 if ( apply_filters( 'bbp_bypass_check_for_flood', false, $anonymous_data, $author_id ) ) {
995 return true;
996 }
997
998 // Option disabled. No flood checks.
999 $throttle_time = get_option( '_bbp_throttle_time' );
1000 if ( empty( $throttle_time ) || ! bbp_allow_content_throttle() ) {
1001 return true;
1002 }
1003
1004 // User is anonymous, so check a transient based on the IP
1005 if ( ! empty( $anonymous_data ) ) {
1006 $last_posted = get_transient( '_bbp_' . bbp_current_author_ip() . '_last_posted' );
1007
1008 if ( ! empty( $last_posted ) && ( time() < ( $last_posted + $throttle_time ) ) ) {
1009 return false;
1010 }
1011
1012 // User is logged in, so check their last posted time
1013 } elseif ( ! empty( $author_id ) ) {
1014 $author_id = (int) $author_id;
1015 $last_posted = bbp_get_user_last_posted( $author_id );
1016
1017 if ( ! empty( $last_posted ) && ( time() < ( $last_posted + $throttle_time ) ) && ! user_can( $author_id, 'throttle' ) ) {
1018 return false;
1019 }
1020 } else {
1021 return false;
1022 }
1023
1024 return true;
1025 }
1026
1027 /**
1028 * Checks topics and replies against the discussion moderation of blocked keys
1029 *
1030 * @since 2.1.0 bbPress (r3581)
1031 *
1032 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
1033 * supply if supplying $author_id. Should be
1034 * sanitized (see {@link bbp_filter_anonymous_post_data()}
1035 * @param int $author_id Topic or reply author ID
1036 * @param string $title The title of the content
1037 * @param string $content The content being posted
1038 * @param mixed $strict False for moderation_keys. True for disallowed_keys.
1039 * String for custom keys.
1040 * @return bool True if test is passed, false if fail
1041 */
1042 function bbp_check_for_moderation( $anonymous_data = array(), $author_id = 0, $title = '', $content = '', $strict = false ) {
1043
1044 // Custom moderation option key
1045 if ( is_string( $strict ) ) {
1046 $strict = sanitize_key( $strict );
1047
1048 // Use custom key
1049 if ( ! empty( $strict ) ) {
1050 $hook_name = $strict;
1051 $option_name = "{$strict}_keys";
1052
1053 // Key was invalid, so default to moderation keys
1054 } else {
1055 $strict = false;
1056 }
1057 }
1058
1059 // Strict mode uses WordPress "blacklist" settings
1060 if ( true === $strict ) {
1061 $hook_name = 'blacklist';
1062 $option_name = 'disallowed_keys';
1063
1064 // Non-strict uses WordPress "moderation" settings
1065 } elseif ( false === $strict ) {
1066 $hook_name = 'moderation';
1067 $option_name = 'moderation_keys';
1068 }
1069
1070 // Allow for moderation check to be skipped
1071 if ( apply_filters( "bbp_bypass_check_for_{$hook_name}", false, $anonymous_data, $author_id, $title, $content, $strict ) ) {
1072 return true;
1073 }
1074
1075 // Maybe perform some author-specific capability checks
1076 if ( ! empty( $author_id ) ) {
1077
1078 // Bail if user is a keymaster
1079 if ( bbp_is_user_keymaster( $author_id ) ) {
1080 return true;
1081
1082 // Bail if user can moderate
1083 // https://bbpress.trac.wordpress.org/ticket/2726
1084 } elseif ( ( false === $strict ) && user_can( $author_id, 'moderate' ) ) {
1085 return true;
1086 }
1087 }
1088
1089 // Define local variable(s)
1090 $_post = array();
1091 $match_out = '';
1092
1093 /** Max Links *************************************************************/
1094
1095 // Only check max_links when not being strict
1096 if ( false === $strict ) {
1097 $max_links = get_option( 'comment_max_links' );
1098 if ( ! empty( $max_links ) ) {
1099
1100 // How many links?
1101 $num_links = preg_match_all( '/(http|ftp|https):\/\//i', $content, $match_out );
1102
1103 // Allow for bumping the max to include the user's URL
1104 if ( ! empty( $_post['url'] ) ) {
1105 $num_links = apply_filters( 'comment_max_links_url', $num_links, $_post['url'], $content );
1106 }
1107
1108 // Das ist zu viele links!
1109 if ( $num_links >= $max_links ) {
1110 return false;
1111 }
1112 }
1113 }
1114
1115 /** Moderation ************************************************************/
1116
1117 /**
1118 * Filters the bbPress moderation keys.
1119 *
1120 * @since 2.6.0 bbPress (r6050)
1121 *
1122 * @param string $moderation List of moderation keys. One per new line.
1123 */
1124 $moderation = apply_filters( "bbp_{$hook_name}_keys", trim( get_option( $option_name ) ) );
1125
1126 // Bail if no words to look for
1127 if ( empty( $moderation ) ) {
1128 return true;
1129 }
1130
1131 /** User Data *************************************************************/
1132
1133 // Map anonymous user data
1134 if ( ! empty( $anonymous_data ) ) {
1135 $_post['author'] = $anonymous_data['bbp_anonymous_name'];
1136 $_post['email'] = $anonymous_data['bbp_anonymous_email'];
1137 $_post['url'] = $anonymous_data['bbp_anonymous_website'];
1138
1139 // Map current user data
1140 } elseif ( ! empty( $author_id ) ) {
1141
1142 // Get author data
1143 $user = get_userdata( $author_id );
1144
1145 // If data exists, map it
1146 if ( ! empty( $user ) ) {
1147 $_post['author'] = $user->display_name;
1148 $_post['email'] = $user->user_email;
1149 $_post['url'] = $user->user_url;
1150 }
1151 }
1152
1153 // Current user IP and user agent
1154 $_post['user_ip'] = bbp_current_author_ip();
1155 $_post['user_ua'] = bbp_current_author_ua();
1156
1157 // Post title and content
1158 $_post['title'] = $title;
1159 $_post['content'] = $content;
1160
1161 // Ensure HTML tags are not being used to bypass the moderation list.
1162 $_post['comment_without_html'] = wp_strip_all_tags( $content );
1163
1164 /** Words *****************************************************************/
1165
1166 // Get words separated by new lines
1167 $words = explode( "\n", $moderation );
1168
1169 // Loop through words
1170 foreach ( (array) $words as $word ) {
1171
1172 // Trim the whitespace from the word
1173 $word = trim( $word );
1174
1175 // Skip empty lines
1176 if ( empty( $word ) ) {
1177 continue;
1178 }
1179
1180 // Do some escaping magic so that '#' chars in the
1181 // spam words don't break things:
1182 $word = preg_quote( $word, '#' );
1183 $pattern = "#{$word}#iu";
1184
1185 // Loop through post data
1186 foreach ( $_post as $post_data ) {
1187
1188 // Check each user data for current word
1189 if ( preg_match( $pattern, $post_data ) ) {
1190
1191 // Post does not pass
1192 return false;
1193 }
1194 }
1195 }
1196
1197 // Check passed successfully
1198 return true;
1199 }
1200
1201 /**
1202 * Deprecated. Use bbp_check_for_moderation() with strict flag set.
1203 *
1204 * @since 2.0.0 bbPress (r3446)
1205 * @since 2.6.0 bbPress (r6854)
1206 * @deprecated 2.6.0 Use bbp_check_for_moderation() with strict flag set
1207 */
1208 function bbp_check_for_blacklist( $anonymous_data = array(), $author_id = 0, $title = '', $content = '' ) {
1209 return bbp_check_for_moderation( $anonymous_data, $author_id, $title, $content, true );
1210 }
1211
1212 /** Subscriptions *************************************************************/
1213
1214 /**
1215 * Get the "Do Not Reply" email address to use when sending subscription emails.
1216 *
1217 * We make some educated guesses here based on the home URL. Filters are
1218 * available to customize this address further. In the future, we may consider
1219 * using `admin_email` instead, though this is not normally publicized.
1220 *
1221 * We use `$_SERVER['SERVER_NAME']` here to mimic similar functionality in
1222 * WordPress core. Previously, we used `get_home_url()` to use already validated
1223 * user input, but it was causing issues in some installations.
1224 *
1225 * @since 2.6.0 bbPress (r5409)
1226 *
1227 * @see wp_mail
1228 * @see wp_notify_postauthor
1229 * @link https://bbpress.trac.wordpress.org/ticket/2618
1230 *
1231 * @return string
1232 */
1233 function bbp_get_do_not_reply_address() {
1234 $sitename = strtolower( $_SERVER['SERVER_NAME'] );
1235 if ( substr( $sitename, 0, 4 ) === 'www.' ) {
1236 $sitename = substr( $sitename, 4 );
1237 }
1238
1239 // Filter & return
1240 return apply_filters( 'bbp_get_do_not_reply_address', 'noreply@' . $sitename );
1241 }
1242
1243 /**
1244 * Remove subscribers who cannot read notification content.
1245 *
1246 * Subscription relationships can outlive a user's access to a forum. Check
1247 * current access immediately before preparing a notification so restricted
1248 * content is not sent to former participants.
1249 *
1250 * @since 2.6.17
1251 *
1252 * @param array $user_ids Subscriber user IDs.
1253 * @param int $forum_id Forum ID.
1254 * @param int $topic_id Topic ID.
1255 * @param int $reply_id Reply ID.
1256 * @return array User IDs that can read the notification content.
1257 */
1258 function bbp_filter_subscription_user_ids( $user_ids = array(), $forum_id = 0, $topic_id = 0, $reply_id = 0 ) {
1259 $forum_id = bbp_get_forum_id( $forum_id );
1260 $topic_id = bbp_get_topic_id( $topic_id );
1261 $reply_id = bbp_get_reply_id( $reply_id );
1262
1263 foreach ( $user_ids as $key => $user_id ) {
1264 $can_view_forum = user_can( $user_id, 'read_forum', $forum_id );
1265
1266 /**
1267 * Filters whether a subscription recipient can view a forum.
1268 *
1269 * @since 2.6.17
1270 *
1271 * @param bool $can_view Whether the user can view the forum.
1272 * @param int $user_id User ID.
1273 * @param int $forum_id Forum ID.
1274 * @param int $topic_id Topic ID.
1275 * @param int $reply_id Reply ID.
1276 */
1277 $can_view_forum = (bool) apply_filters( 'bbp_subscription_user_can_view_forum', $can_view_forum, $user_id, $forum_id, $topic_id, $reply_id );
1278
1279 $can_view = user_can( $user_id, 'spectate' ) && $can_view_forum;
1280
1281 if ( ! empty( $topic_id ) ) {
1282 $can_view = $can_view && user_can( $user_id, 'read_topic', $topic_id );
1283 }
1284
1285 if ( ! empty( $reply_id ) ) {
1286 $can_view = $can_view && user_can( $user_id, 'read_reply', $reply_id );
1287 }
1288
1289 if ( false === $can_view ) {
1290 unset( $user_ids[ $key ] );
1291 }
1292 }
1293
1294 return $user_ids;
1295 }
1296
1297 /**
1298 * Sends notification emails for new replies to subscribed topics
1299 *
1300 * Gets new post ID and check if there are subscribed users to that topic, and
1301 * if there are, send notifications
1302 *
1303 * Note: in bbPress 2.6, we've moved away from 1 email per subscriber to 1 email
1304 * with everyone BCC'd. This may have negative repercussions for email services
1305 * that limit the number of addresses in a BCC field (often to around 500.) In
1306 * those cases, we recommend unhooking this function and creating your own
1307 * custom email script.
1308 *
1309 * @since 2.6.0 bbPress (r5413)
1310 *
1311 * @param int $reply_id ID of the newly made reply
1312 * @param int $topic_id ID of the topic of the reply
1313 * @param int $forum_id ID of the forum of the reply
1314 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
1315 * supply if supplying $author_id. Should be
1316 * sanitized (see {@link bbp_filter_anonymous_post_data()}
1317 * @param int $reply_author ID of the topic author ID
1318 * @return bool True on success, false on failure
1319 */
1320 function bbp_notify_topic_subscribers( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $reply_author = 0 ) {
1321
1322 // Bail if subscriptions are turned off
1323 if ( ! bbp_is_subscriptions_active() ) {
1324 return false;
1325 }
1326
1327 // Bail if importing
1328 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
1329 return false;
1330 }
1331
1332 /** Validation ************************************************************/
1333
1334 $reply_id = bbp_get_reply_id( $reply_id );
1335 $topic_id = bbp_get_topic_id( $topic_id );
1336 $forum_id = bbp_get_forum_id( $forum_id );
1337 $password_protected = bbp_is_password_protected( $reply_id );
1338
1339 /** Topic *****************************************************************/
1340
1341 // Bail if topic is not public (includes closed)
1342 if ( ! bbp_is_topic_public( $topic_id ) ) {
1343 return false;
1344 }
1345
1346 /** Reply *****************************************************************/
1347
1348 // Bail if reply is not published
1349 if ( ! bbp_is_reply_published( $reply_id ) ) {
1350 return false;
1351 }
1352
1353 // Poster name
1354 $reply_author_name = bbp_get_reply_author_display_name( $reply_id );
1355
1356 /** Users *****************************************************************/
1357
1358 // Get topic subscribers and bail if empty
1359 $user_ids = bbp_get_subscribers( $topic_id );
1360
1361 // Remove the reply author from the list.
1362 $reply_author_key = array_search( (int) $reply_author, $user_ids, true );
1363 if ( false !== $reply_author_key ) {
1364 unset( $user_ids[ $reply_author_key ] );
1365 }
1366
1367 // Dedicated filter to manipulate user ID's to send emails to
1368 $user_ids = (array) apply_filters( 'bbp_topic_subscription_user_ids', $user_ids, $reply_id, $topic_id );
1369
1370 // Remove subscribers who cannot read the notification content
1371 $user_ids = bbp_filter_subscription_user_ids( $user_ids, $forum_id, $topic_id, $reply_id );
1372
1373 // Bail of the reply author was the only one subscribed.
1374 if ( empty( $user_ids ) ) {
1375 return false;
1376 }
1377
1378 // Get email addresses, bail if empty
1379 $email_addresses = bbp_get_email_addresses_from_user_ids( $user_ids );
1380 if ( empty( $email_addresses ) ) {
1381 return false;
1382 }
1383
1384 /** Mail ******************************************************************/
1385
1386 // Remove filters from reply content and topic title to prevent content
1387 // from being encoded with HTML entities, wrapped in paragraph tags, etc...
1388 bbp_remove_all_filters( 'bbp_get_reply_content' );
1389 bbp_remove_all_filters( 'bbp_get_topic_title' );
1390 bbp_remove_all_filters( 'the_title' );
1391
1392 // Strip tags from text and setup mail data
1393 $forum_title = wp_specialchars_decode( strip_tags( bbp_get_forum_title( $forum_id ) ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1394 $topic_title = wp_specialchars_decode( strip_tags( bbp_get_topic_title( $topic_id ) ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1395 $reply_url = bbp_get_reply_url( $reply_id );
1396
1397 // Do not include protected content in subscription emails
1398 if ( ! empty( $password_protected ) ) {
1399 $message = sprintf(
1400
1401 /* translators: %s: Reply URL */
1402 esc_html__(
1403 'A new reply was posted in a password-protected discussion.
1404
1405 Post Link: %s
1406
1407 -----------
1408
1409 You are receiving this email because you subscribed to a forum topic.
1410
1411 Login and visit the topic to unsubscribe from these emails.',
1412 'bbpress'
1413 ),
1414 $reply_url
1415 );
1416
1417 // Include the reply details in normal subscription emails
1418 } else {
1419 $reply_author_name = wp_specialchars_decode( strip_tags( $reply_author_name ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1420 $reply_content = wp_specialchars_decode( strip_tags( bbp_get_reply_content( $reply_id ) ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1421 $message = sprintf(
1422
1423 /* translators: 1: Reply author name, 2: Reply content, 3: Reply URL */
1424 esc_html__(
1425 '%1$s wrote:
1426
1427 %2$s
1428
1429 Post Link: %3$s
1430
1431 -----------
1432
1433 You are receiving this email because you subscribed to a forum topic.
1434
1435 Login and visit the topic to unsubscribe from these emails.',
1436 'bbpress'
1437 ),
1438 $reply_author_name,
1439 $reply_content,
1440 $reply_url
1441 );
1442 }
1443
1444 // For plugins to filter messages per reply/topic/user
1445 $message = apply_filters( 'bbp_subscription_mail_message', $message, $reply_id, $topic_id );
1446 if ( empty( $message ) ) {
1447 return;
1448 }
1449
1450 // For plugins to filter titles per reply/topic/user
1451 $subject = apply_filters( 'bbp_subscription_mail_title', '[' . $forum_title . '] ' . $topic_title, $reply_id, $topic_id );
1452 if ( empty( $subject ) ) {
1453 return;
1454 }
1455
1456 /** Headers ***************************************************************/
1457
1458 // Default bbPress X-header
1459 $headers = array( bbp_get_email_header() );
1460
1461 // Get the noreply@ address
1462 $no_reply = bbp_get_do_not_reply_address();
1463
1464 // Setup "From" email address
1465 $from_email = apply_filters( 'bbp_subscription_from_email', $no_reply );
1466
1467 // Setup the From header
1468 $headers[] = 'From: ' . get_bloginfo( 'name' ) . ' <' . $from_email . '>';
1469
1470 // Loop through addresses
1471 foreach ( (array) $email_addresses as $address ) {
1472 $headers[] = 'Bcc: ' . $address;
1473 }
1474
1475 /** Send it ***************************************************************/
1476
1477 // Custom headers
1478 $headers = apply_filters( 'bbp_subscription_mail_headers', $headers );
1479 $to_email = apply_filters( 'bbp_subscription_to_email', $no_reply );
1480
1481 // Before
1482 do_action( 'bbp_pre_notify_subscribers', $reply_id, $topic_id, $user_ids );
1483
1484 // Send notification email
1485 wp_mail( $to_email, $subject, $message, $headers );
1486
1487 // After
1488 do_action( 'bbp_post_notify_subscribers', $reply_id, $topic_id, $user_ids );
1489
1490 // Restore previously removed filters
1491 bbp_restore_all_filters( 'bbp_get_topic_content' );
1492 bbp_restore_all_filters( 'bbp_get_topic_title' );
1493 bbp_restore_all_filters( 'the_title' );
1494
1495 return true;
1496 }
1497
1498 /**
1499 * Sends notification emails for new topics to subscribed forums
1500 *
1501 * Gets new post ID and check if there are subscribed users to that forum, and
1502 * if there are, send notifications
1503 *
1504 * Note: in bbPress 2.6, we've moved away from 1 email per subscriber to 1 email
1505 * with everyone BCC'd. This may have negative repercussions for email services
1506 * that limit the number of addresses in a BCC field (often to around 500.) In
1507 * those cases, we recommend unhooking this function and creating your own
1508 * custom email script.
1509 *
1510 * @since 2.5.0 bbPress (r5156)
1511 *
1512 * @param int $topic_id ID of the newly made reply
1513 * @param int $forum_id ID of the forum for the topic
1514 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
1515 * supply if supplying $author_id. Should be
1516 * sanitized (see {@link bbp_filter_anonymous_post_data()}
1517 * @param int $topic_author ID of the topic author ID
1518 * @return bool True on success, false on failure
1519 */
1520 function bbp_notify_forum_subscribers( $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $topic_author = 0 ) {
1521
1522 // Bail if subscriptions are turned off
1523 if ( ! bbp_is_subscriptions_active() ) {
1524 return false;
1525 }
1526
1527 // Bail if importing
1528 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
1529 return false;
1530 }
1531
1532 /** Validation ************************************************************/
1533
1534 $topic_id = bbp_get_topic_id( $topic_id );
1535 $forum_id = bbp_get_forum_id( $forum_id );
1536 $password_protected = bbp_is_password_protected( $topic_id );
1537
1538 /**
1539 * Necessary for backwards compatibility
1540 *
1541 * @see https://bbpress.trac.wordpress.org/ticket/2620
1542 */
1543 $user_id = 0;
1544
1545 /** Topic *****************************************************************/
1546
1547 // Bail if topic is not public (includes closed)
1548 if ( ! bbp_is_topic_public( $topic_id ) ) {
1549 return false;
1550 }
1551
1552 // Poster name
1553 $topic_author_name = bbp_get_topic_author_display_name( $topic_id );
1554
1555 /** Users *****************************************************************/
1556
1557 // Get topic subscribers and bail if empty
1558 $user_ids = bbp_get_subscribers( $forum_id );
1559
1560 // Remove the topic author from the list.
1561 $topic_author_key = array_search( (int) $topic_author, $user_ids, true );
1562 if ( false !== $topic_author_key ) {
1563 unset( $user_ids[ $topic_author_key ] );
1564 }
1565
1566 // Dedicated filter to manipulate user ID's to send emails to
1567 $user_ids = (array) apply_filters( 'bbp_forum_subscription_user_ids', $user_ids, $topic_id, $forum_id );
1568
1569 // Remove subscribers who cannot read the notification content
1570 $user_ids = bbp_filter_subscription_user_ids( $user_ids, $forum_id, $topic_id );
1571
1572 // Bail of the reply author was the only one subscribed.
1573 if ( empty( $user_ids ) ) {
1574 return false;
1575 }
1576
1577 // Get email addresses, bail if empty
1578 $email_addresses = bbp_get_email_addresses_from_user_ids( $user_ids );
1579 if ( empty( $email_addresses ) ) {
1580 return false;
1581 }
1582
1583 /** Mail ******************************************************************/
1584
1585 // Remove filters from reply content and topic title to prevent content
1586 // from being encoded with HTML entities, wrapped in paragraph tags, etc...
1587 bbp_remove_all_filters( 'bbp_get_topic_content' );
1588 bbp_remove_all_filters( 'bbp_get_topic_title' );
1589 bbp_remove_all_filters( 'the_title' );
1590
1591 // Strip tags from text and setup mail data
1592 $forum_title = wp_specialchars_decode( strip_tags( bbp_get_forum_title( $forum_id ) ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1593 $topic_title = wp_specialchars_decode( strip_tags( bbp_get_topic_title( $topic_id ) ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1594 $topic_url = get_permalink( $topic_id );
1595
1596 // Do not include protected content in subscription emails
1597 if ( ! empty( $password_protected ) ) {
1598 $message = sprintf(
1599
1600 /* translators: %s: Topic URL */
1601 esc_html__(
1602 'A new topic was posted in a password-protected discussion.
1603
1604 Topic Link: %s
1605
1606 -----------
1607
1608 You are receiving this email because you subscribed to a forum.
1609
1610 Login and visit the topic to unsubscribe from these emails.',
1611 'bbpress'
1612 ),
1613 $topic_url
1614 );
1615
1616 // Include the topic details in normal subscription emails
1617 } else {
1618 $topic_author_name = wp_specialchars_decode( strip_tags( $topic_author_name ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1619 $topic_content = wp_specialchars_decode( strip_tags( bbp_get_topic_content( $topic_id ) ), ENT_QUOTES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
1620 $message = sprintf(
1621
1622 /* translators: 1: Topic author name, 2: Topic content, 3: Topic URL */
1623 esc_html__(
1624 '%1$s wrote:
1625
1626 %2$s
1627
1628 Topic Link: %3$s
1629
1630 -----------
1631
1632 You are receiving this email because you subscribed to a forum.
1633
1634 Login and visit the topic to unsubscribe from these emails.',
1635 'bbpress'
1636 ),
1637 $topic_author_name,
1638 $topic_content,
1639 $topic_url
1640 );
1641 }
1642
1643 // For plugins to filter messages per reply/topic/user
1644 $message = apply_filters( 'bbp_forum_subscription_mail_message', $message, $topic_id, $forum_id, $user_id );
1645 if ( empty( $message ) ) {
1646 return;
1647 }
1648
1649 // For plugins to filter titles per reply/topic/user
1650 $subject = apply_filters( 'bbp_forum_subscription_mail_title', '[' . $forum_title . '] ' . $topic_title, $topic_id, $forum_id, $user_id );
1651 if ( empty( $subject ) ) {
1652 return;
1653 }
1654
1655 /** Headers ***************************************************************/
1656
1657 // Default bbPress X-header
1658 $headers = array( bbp_get_email_header() );
1659
1660 // Get the noreply@ address
1661 $no_reply = bbp_get_do_not_reply_address();
1662
1663 // Setup "From" email address
1664 $from_email = apply_filters( 'bbp_subscription_from_email', $no_reply );
1665
1666 // Setup the From header
1667 $headers[] = 'From: ' . get_bloginfo( 'name' ) . ' <' . $from_email . '>';
1668
1669 // Loop through addresses
1670 foreach ( (array) $email_addresses as $address ) {
1671 $headers[] = 'Bcc: ' . $address;
1672 }
1673
1674 /** Send it ***************************************************************/
1675
1676 // Custom headers
1677 $headers = apply_filters( 'bbp_subscription_mail_headers', $headers );
1678 $to_email = apply_filters( 'bbp_subscription_to_email', $no_reply );
1679
1680 // Before
1681 do_action( 'bbp_pre_notify_forum_subscribers', $topic_id, $forum_id, $user_ids );
1682
1683 // Send notification email
1684 wp_mail( $to_email, $subject, $message, $headers );
1685
1686 // After
1687 do_action( 'bbp_post_notify_forum_subscribers', $topic_id, $forum_id, $user_ids );
1688
1689 // Restore previously removed filters
1690 bbp_restore_all_filters( 'bbp_get_topic_content' );
1691 bbp_restore_all_filters( 'bbp_get_topic_title' );
1692 bbp_restore_all_filters( 'the_title' );
1693
1694 return true;
1695 }
1696
1697 /**
1698 * Sends notification emails for new replies to subscribed topics
1699 *
1700 * This function is deprecated. Please use: bbp_notify_topic_subscribers()
1701 *
1702 * @since 2.0.0 bbPress (r2668)
1703 *
1704 * @deprecated 2.6.0 bbPress (r5412)
1705 *
1706 * @param int $reply_id ID of the newly made reply
1707 * @param int $topic_id ID of the topic of the reply
1708 * @param int $forum_id ID of the forum of the reply
1709 * @param array $anonymous_data Optional - if it's an anonymous post. Do not
1710 * supply if supplying $author_id. Should be
1711 * sanitized (see {@link bbp_filter_anonymous_post_data()}
1712 * @param int $reply_author ID of the topic author ID
1713 *
1714 * @return bool True on success, false on failure
1715 */
1716 function bbp_notify_subscribers( $reply_id = 0, $topic_id = 0, $forum_id = 0, $anonymous_data = array(), $reply_author = 0 ) {
1717 return bbp_notify_topic_subscribers( $reply_id, $topic_id, $forum_id, $anonymous_data, $reply_author );
1718 }
1719
1720 /**
1721 * Return an array of user email addresses from an array of user IDs
1722 *
1723 * @since 2.6.0 bbPress (r6722)
1724 *
1725 * @param array $user_ids
1726 * @return array
1727 */
1728 function bbp_get_email_addresses_from_user_ids( $user_ids = array() ) {
1729
1730 // Default return value
1731 $retval = array();
1732
1733 // Maximum number of users to get per database query
1734 $limit = apply_filters( 'bbp_get_users_chunk_limit', 100 );
1735
1736 // Only do the work if there are user IDs to query for
1737 if ( ! empty( $user_ids ) ) {
1738
1739 // Get total number of sets
1740 $steps = ceil( count( $user_ids ) / $limit );
1741 $range = array_map( 'intval', range( 1, $steps ) );
1742
1743 // Loop through users
1744 foreach ( $range as $loop ) {
1745
1746 // Initial loop has no offset
1747 $offset = $limit * ( $loop - 1 );
1748
1749 // Calculate user IDs to include
1750 $loop_ids = array_slice( $user_ids, $offset, $limit );
1751
1752 // Skip if something went wrong
1753 if ( empty( $loop_ids ) ) {
1754 continue;
1755 }
1756
1757 // Call get_users() in a way that users are cached
1758 $loop_users = get_users(
1759 array(
1760 'blog_id' => 0,
1761 'fields' => 'all_with_meta',
1762 'include' => $loop_ids
1763 )
1764 );
1765
1766 // Pluck emails from users
1767 $loop_emails = wp_list_pluck( $loop_users, 'user_email' );
1768
1769 // Clean-up memory, for big user sets
1770 unset( $loop_users );
1771
1772 // Merge users into return value
1773 if ( ! empty( $loop_emails ) ) {
1774 $retval = array_merge( $retval, $loop_emails );
1775 }
1776 }
1777
1778 // No duplicates
1779 $retval = bbp_get_unique_array_values( $retval );
1780 }
1781
1782 // Filter & return
1783 return apply_filters( 'bbp_get_email_addresses_from_user_ids', $retval, $user_ids, $limit );
1784 }
1785
1786 /**
1787 * Automatically splits bbPress emails with many Bcc recipients into chunks.
1788 *
1789 * This middleware is useful because topics and forums with many subscribers
1790 * run into problems with Bcc limits, and many hosting companies & third-party
1791 * services limit the size of a Bcc audience to prevent spamming.
1792 *
1793 * The default "chunk" size is 40 users per iteration, and can be filtered if
1794 * desired. A future version of bbPress will introduce a setting to more easily
1795 * tune this.
1796 *
1797 * @since 2.6.0 bbPress (r6918)
1798 *
1799 * @param array $args Original arguments passed to wp_mail().
1800 * @return array
1801 */
1802 function bbp_chunk_emails( $args = array() ) {
1803
1804 // Get the maximum number of Bcc's per chunk
1805 $max_num = apply_filters( 'bbp_get_bcc_chunk_limit', 40, $args );
1806
1807 // Look for "bcc: " in a case-insensitive way, and split into 2 sets
1808 $match = '/^bcc: (\w+)/i';
1809 $old_headers = preg_grep( $match, $args['headers'], PREG_GREP_INVERT );
1810 $bcc_headers = preg_grep( $match, $args['headers'] );
1811
1812 // Bail if less than $max_num recipients
1813 if ( empty( $bcc_headers ) || ( count( $bcc_headers ) < $max_num ) ) {
1814 return $args;
1815 }
1816
1817 // Reindex the headers arrays
1818 $old_headers = array_values( $old_headers );
1819 $bcc_headers = array_values( $bcc_headers );
1820
1821 // Break the Bcc emails into chunks
1822 foreach ( array_chunk( $bcc_headers, $max_num ) as $i => $chunk ) {
1823
1824 // Skip the first chunk (it will get used in the original wp_mail() call)
1825 if ( 0 === $i ) {
1826 $first_chunk = $chunk;
1827 continue;
1828 }
1829
1830 // Send out the chunk
1831 $chunk_headers = array_merge( $old_headers, $chunk );
1832
1833 // Recursion alert, but should be OK!
1834 wp_mail(
1835 $args['to'],
1836 $args['subject'],
1837 $args['message'],
1838 $chunk_headers,
1839 $args['attachments']
1840 );
1841 }
1842
1843 // Set headers to old headers + the $first_chunk of Bcc's
1844 $args['headers'] = array_merge( $old_headers, $first_chunk );
1845
1846 // Return the reduced args, with the first chunk of Bcc's
1847 return $args;
1848 }
1849
1850 /**
1851 * Return the string used for the bbPress specific X-header.
1852 *
1853 * @since 2.6.0 bbPress (r6919)
1854 *
1855 * @return string
1856 */
1857 function bbp_get_email_header() {
1858 return apply_filters( 'bbp_get_email_header', 'X-bbPress: ' . bbp_get_version() );
1859 }
1860
1861 /** Login *********************************************************************/
1862
1863 /**
1864 * Return a clean and reliable logout URL
1865 *
1866 * This function is used to filter `logout_url`. If no $redirect_to value is
1867 * passed, it will default to the request uri, then the forum root.
1868 *
1869 * See: `wp_logout_url()`
1870 *
1871 * @since 2.1.0 bbPress (2815)
1872 *
1873 * @param string $url URL used to log out
1874 * @param string $redirect_to Where to redirect to?
1875 *
1876 * @return string The url
1877 */
1878 function bbp_logout_url( $url = '', $redirect_to = '' ) {
1879
1880 // If there is no redirect in the URL, let's add one...
1881 if ( ! strstr( $url, 'redirect_to' ) ) {
1882
1883 // Get the forum root, to maybe use as a default
1884 $forum_root = bbp_get_root_url();
1885
1886 // No redirect passed, so check referer and fallback to request uri
1887 if ( empty( $redirect_to ) ) {
1888
1889 // Check for a valid referer
1890 $redirect_to = wp_get_referer();
1891
1892 // Fallback to request uri if invalid referer
1893 if ( false === $redirect_to ) {
1894 $redirect_to = bbp_get_url_scheme() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
1895 }
1896 }
1897
1898 // Filter the $redirect_to destination
1899 $filtered = apply_filters( 'bbp_logout_url_redirect_to', $redirect_to );
1900
1901 // Validate $redirect_to, default to root
1902 $validated = wp_validate_redirect( $filtered, $forum_root );
1903
1904 // Assemble $redirect_to and add it (encoded) to full $url
1905 $appended = add_query_arg( array( 'loggedout' => 'true' ), $validated );
1906 $encoded = urlencode( $appended );
1907 $url = add_query_arg( array( 'redirect_to' => $encoded ), $url );
1908 }
1909
1910 // Filter & return
1911 return apply_filters( 'bbp_logout_url', $url, $redirect_to );
1912 }
1913
1914 /** Queries *******************************************************************/
1915
1916 /**
1917 * Merge user defined arguments into defaults array.
1918 *
1919 * This function is used throughout bbPress to allow for either a string or array
1920 * to be merged into another array. It is identical to wp_parse_args() except
1921 * it allows for arguments to be passively or aggressively filtered using the
1922 * optional $filter_key parameter.
1923 *
1924 * @since 2.1.0 bbPress (r3839)
1925 *
1926 * @param string|array $args Value to merge with $defaults
1927 * @param array $defaults Array that serves as the defaults.
1928 * @param string $filter_key String to key the filters from
1929 * @return array Merged user defined values with defaults.
1930 */
1931 function bbp_parse_args( $args, $defaults = array(), $filter_key = '' ) {
1932
1933 // Setup a temporary array from $args
1934 if ( is_object( $args ) ) {
1935 $r = get_object_vars( $args );
1936 } elseif ( is_array( $args ) ) {
1937 $r =& $args;
1938 } else {
1939 wp_parse_str( $args, $r );
1940 }
1941
1942 // Passively filter the args before the parse
1943 if ( ! empty( $filter_key ) ) {
1944 $r = apply_filters( "bbp_before_{$filter_key}_parse_args", $r, $args, $defaults );
1945 }
1946
1947 // Parse
1948 if ( is_array( $defaults ) && ! empty( $defaults ) ) {
1949 $r = array_merge( $defaults, $r );
1950 }
1951
1952 // Aggressively filter the args after the parse
1953 if ( ! empty( $filter_key ) ) {
1954 $r = apply_filters( "bbp_after_{$filter_key}_parse_args", $r, $args, $defaults );
1955 }
1956
1957 // Return the parsed results
1958 return $r;
1959 }
1960
1961 /**
1962 * Adds ability to include or exclude specific post_parent ID's
1963 *
1964 * @since 2.0.0 bbPress (r2996)
1965 *
1966 * @deprecated 2.5.8 bbPress (r5814)
1967 *
1968 * @global WP $wp
1969 * @param string $where
1970 * @param WP_Query $object
1971 * @return string
1972 */
1973 function bbp_query_post_parent__in( $where, $object = '' ) {
1974 global $wp;
1975
1976 // Noop if WP core supports this already
1977 if ( in_array( 'post_parent__in', $wp->private_query_vars, true ) ) {
1978 return $where;
1979 }
1980
1981 // Bail if no object passed
1982 if ( empty( $object ) ) {
1983 return $where;
1984 }
1985
1986 // Only 1 post_parent so return $where
1987 if ( is_numeric( $object->query_vars['post_parent'] ) ) {
1988 return $where;
1989 }
1990
1991 // Get the DB
1992 $bbp_db = bbp_db();
1993
1994 // Including specific post_parent's
1995 if ( ! empty( $object->query_vars['post_parent__in'] ) ) {
1996 $ids = implode( ',', wp_parse_id_list( $object->query_vars['post_parent__in'] ) );
1997 $where .= " AND {$bbp_db->posts}.post_parent IN ($ids)";
1998
1999 // Excluding specific post_parent's
2000 } elseif ( ! empty( $object->query_vars['post_parent__not_in'] ) ) {
2001 $ids = implode( ',', wp_parse_id_list( $object->query_vars['post_parent__not_in'] ) );
2002 $where .= " AND {$bbp_db->posts}.post_parent NOT IN ($ids)";
2003 }
2004
2005 // Return possibly modified $where
2006 return $where;
2007 }
2008
2009 /**
2010 * Query the DB and get the last public post_id that has parent_id as post_parent
2011 *
2012 * @since 2.0.0 bbPress (r2868)
2013 * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
2014 *
2015 * @param int $parent_id Parent id.
2016 * @param string $post_type Post type. Defaults to 'post'.
2017 * @return int The last active post_id
2018 */
2019 function bbp_get_public_child_last_id( $parent_id = 0, $post_type = 'post' ) {
2020
2021 // Bail if nothing passed
2022 if ( empty( $parent_id ) ) {
2023 return false;
2024 }
2025
2026 // Which statuses
2027 switch ( $post_type ) {
2028
2029 // Forum
2030 case bbp_get_forum_post_type() :
2031 $post_status = bbp_get_public_forum_statuses();
2032 break;
2033
2034 // Topic
2035 case bbp_get_topic_post_type() :
2036 $post_status = bbp_get_public_topic_statuses();
2037 break;
2038
2039 // Reply
2040 case bbp_get_reply_post_type() :
2041 default :
2042 $post_status = bbp_get_public_reply_statuses();
2043 break;
2044 }
2045
2046 $query = new WP_Query(
2047 array(
2048 'fields' => 'ids',
2049 'post_parent' => $parent_id,
2050 'post_status' => $post_status,
2051 'post_type' => $post_type,
2052 'posts_per_page' => 1,
2053 'orderby' => array(
2054 'post_date' => 'DESC',
2055 'ID' => 'DESC'
2056 ),
2057
2058 // Performance
2059 'suppress_filters' => true,
2060 'update_post_term_cache' => false,
2061 'update_post_meta_cache' => false,
2062 'ignore_sticky_posts' => true,
2063 'no_found_rows' => true
2064 )
2065 );
2066 $child_id = array_shift( $query->posts );
2067 unset( $query );
2068
2069 // Filter & return
2070 return (int) apply_filters( 'bbp_get_public_child_last_id', $child_id, $parent_id, $post_type );
2071 }
2072
2073 /**
2074 * Query the database for child counts, grouped by type & status
2075 *
2076 * @since 2.6.0 bbPress (r6826)
2077 *
2078 * @param int $parent_id
2079 */
2080 function bbp_get_child_counts( $parent_id = 0 ) {
2081
2082 // Create cache key
2083 $parent_id = absint( $parent_id );
2084 $key = md5(
2085 serialize(
2086 array(
2087 'parent_id' => $parent_id,
2088 'post_type' => bbp_get_post_types()
2089 )
2090 )
2091 );
2092 $last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
2093 $cache_key = "bbp_child_counts:{$key}:{$last_changed}";
2094
2095 // Check for cache and set if needed
2096 $retval = wp_cache_get( $cache_key, 'bbpress_posts' );
2097 if ( false === $retval ) {
2098
2099 // Setup the DB & query
2100 $bbp_db = bbp_db();
2101 $sql = "SELECT
2102 p.post_type AS type,
2103 p.post_status AS status,
2104 COUNT( * ) AS count
2105 FROM {$bbp_db->posts} AS p
2106 LEFT JOIN {$bbp_db->postmeta} AS pm
2107 ON p.ID = pm.post_id
2108 AND pm.meta_key = %s
2109 WHERE pm.meta_value = %s
2110 GROUP BY p.post_status, p.post_type";
2111
2112 // Get prepare vars
2113 $post_type = get_post_type( $parent_id );
2114 $meta_key = "_bbp_{$post_type}_id";
2115
2116 // Prepare & get results
2117 $query = $bbp_db->prepare( $sql, $meta_key, $parent_id );
2118 $results = $bbp_db->get_results( $query, ARRAY_A );
2119
2120 // Setup return value
2121 $retval = wp_list_pluck( $results, 'type', 'type' );
2122 $statuses = get_post_stati();
2123
2124 // Loop through results
2125 foreach ( $results as $row ) {
2126
2127 // Setup empties
2128 if ( ! is_array( $retval[ $row['type'] ] ) ) {
2129 $retval[ $row['type'] ] = array_fill_keys( $statuses, 0 );
2130 }
2131
2132 // Set statuses
2133 $retval[ $row['type'] ][ $row['status'] ] = bbp_number_not_negative( $row['count'] );
2134 }
2135
2136 // Always cache the results
2137 wp_cache_set( $cache_key, $retval, 'bbpress_posts' );
2138 }
2139
2140 // Make sure results are INTs
2141 return (array) apply_filters( 'bbp_get_child_counts', $retval, $parent_id );
2142 }
2143
2144 /**
2145 * Filter a list of child counts, from `bbp_get_child_counts()`
2146 *
2147 * @since 2.6.0 bbPress (r6826)
2148 *
2149 * @param int $parent_id ID of post to get child counts from
2150 * @param array $types Optional. An array of post types to filter by
2151 * @param array $statuses Optional. An array of post statuses to filter by
2152 *
2153 * @return array A list of objects or object fields.
2154 */
2155 function bbp_filter_child_counts_list( $parent_id = 0, $types = array( 'post' ), $statuses = array() ) {
2156
2157 // Setup local vars
2158 $retval = array();
2159 $types = array_flip( (array) $types );
2160 $statuses = array_flip( (array) $statuses );
2161 $counts = bbp_get_child_counts( $parent_id );
2162
2163 // Loop through counts by type
2164 foreach ( $counts as $type => $type_counts ) {
2165
2166 // Skip if not this type
2167 if ( ! isset( $types[ $type ] ) ) {
2168 continue;
2169 }
2170
2171 // Maybe filter statuses
2172 if ( ! empty( $statuses ) ) {
2173 $type_counts = array_intersect_key( $type_counts, $statuses );
2174 }
2175
2176 // Add type counts to return array
2177 $retval[ $type ] = $type_counts;
2178 }
2179
2180 // Filter & return
2181 return (array) apply_filters( 'bbp_filter_child_counts_list', $retval, $parent_id, $types, $statuses );
2182 }
2183
2184 /**
2185 * Query the DB and get a count of public children
2186 *
2187 * @since 2.0.0 bbPress (r2868)
2188 * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
2189 *
2190 * @param int $parent_id Parent id.
2191 * @param string $post_type Post type. Defaults to 'post'.
2192 * @return int The number of children
2193 */
2194 function bbp_get_public_child_count( $parent_id = 0, $post_type = 'post' ) {
2195
2196 // Bail if nothing passed
2197 if ( empty( $post_type ) ) {
2198 return false;
2199 }
2200
2201 // Which statuses
2202 switch ( $post_type ) {
2203
2204 // Forum
2205 case bbp_get_forum_post_type() :
2206 $post_status = bbp_get_public_forum_statuses();
2207 break;
2208
2209 // Topic
2210 case bbp_get_topic_post_type() :
2211 $post_status = bbp_get_public_topic_statuses();
2212 break;
2213
2214 // Reply
2215 case bbp_get_reply_post_type() :
2216 default :
2217 $post_status = bbp_get_public_reply_statuses();
2218 break;
2219 }
2220
2221 // Get counts
2222 $counts = bbp_filter_child_counts_list( $parent_id, $post_type, $post_status );
2223 $child_count = isset( $counts[ $post_type ] )
2224 ? bbp_number_not_negative( array_sum( array_values( $counts[ $post_type ] ) ) )
2225 : 0;
2226
2227 // Filter & return
2228 return (int) apply_filters( 'bbp_get_public_child_count', $child_count, $parent_id, $post_type );
2229 }
2230 /**
2231 * Query the DB and get a count of public children
2232 *
2233 * @since 2.0.0 bbPress (r2868)
2234 * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
2235 *
2236 * @param int $parent_id Parent id.
2237 * @param string $post_type Post type. Defaults to 'post'.
2238 * @return int The number of children
2239 */
2240 function bbp_get_non_public_child_count( $parent_id = 0, $post_type = 'post' ) {
2241
2242 // Bail if nothing passed
2243 if ( empty( $parent_id ) || empty( $post_type ) ) {
2244 return false;
2245 }
2246
2247 // Which statuses
2248 switch ( $post_type ) {
2249
2250 // Forum
2251 case bbp_get_forum_post_type() :
2252 $post_status = bbp_get_non_public_forum_statuses();
2253 break;
2254
2255 // Topic
2256 case bbp_get_topic_post_type() :
2257 $post_status = bbp_get_non_public_topic_statuses();
2258 break;
2259
2260 // Reply
2261 case bbp_get_reply_post_type() :
2262 $post_status = bbp_get_non_public_reply_statuses();
2263 break;
2264
2265 // Any
2266 default :
2267 $post_status = bbp_get_public_status_id();
2268 break;
2269 }
2270
2271 // Get counts
2272 $counts = bbp_filter_child_counts_list( $parent_id, $post_type, $post_status );
2273 $child_count = isset( $counts[ $post_type ] )
2274 ? bbp_number_not_negative( array_sum( array_values( $counts[ $post_type ] ) ) )
2275 : 0;
2276
2277 // Filter & return
2278 return (int) apply_filters( 'bbp_get_non_public_child_count', $child_count, $parent_id, $post_type );
2279 }
2280
2281 /**
2282 * Query the DB and get the child id's of public children
2283 *
2284 * @since 2.0.0 bbPress (r2868)
2285 * @since 2.6.0 bbPress (r5954) Replace direct queries with WP_Query() objects
2286 *
2287 * @param int $parent_id Parent id.
2288 * @param string $post_type Post type. Defaults to 'post'.
2289 *
2290 * @return array The array of children
2291 */
2292 function bbp_get_public_child_ids( $parent_id = 0, $post_type = 'post' ) {
2293
2294 // Bail if nothing passed
2295 if ( empty( $parent_id ) || empty( $post_type ) ) {
2296 return array();
2297 }
2298
2299 // Which statuses
2300 switch ( $post_type ) {
2301
2302 // Forum
2303 case bbp_get_forum_post_type() :
2304 $post_status = bbp_get_public_forum_statuses();
2305 break;
2306
2307 // Topic
2308 case bbp_get_topic_post_type() :
2309 $post_status = bbp_get_public_topic_statuses();
2310 break;
2311
2312 // Reply
2313 case bbp_get_reply_post_type() :
2314 default :
2315 $post_status = bbp_get_public_reply_statuses();
2316 break;
2317 }
2318
2319 $query = new WP_Query(
2320 array(
2321 'fields' => 'ids',
2322 'post_parent' => $parent_id,
2323 'post_status' => $post_status,
2324 'post_type' => $post_type,
2325 'posts_per_page' => -1,
2326 'orderby' => array(
2327 'post_date' => 'DESC',
2328 'ID' => 'DESC'
2329 ),
2330
2331 // Performance
2332 'nopaging' => true,
2333 'suppress_filters' => true,
2334 'update_post_term_cache' => false,
2335 'update_post_meta_cache' => false,
2336 'ignore_sticky_posts' => true,
2337 'no_found_rows' => true
2338 )
2339 );
2340
2341 $child_ids = ! empty( $query->posts )
2342 ? $query->posts
2343 : array();
2344
2345 unset( $query );
2346
2347 // Filter & return
2348 return (array) apply_filters( 'bbp_get_public_child_ids', $child_ids, $parent_id, $post_type );
2349 }
2350
2351 /**
2352 * Query the DB and get the child id's of all children
2353 *
2354 * @since 2.0.0 bbPress (r3325)
2355 *
2356 * @param int $parent_id Parent id
2357 * @param string $post_type Post type. Defaults to 'post'
2358 *
2359 * @return array The array of children
2360 */
2361 function bbp_get_all_child_ids( $parent_id = 0, $post_type = 'post' ) {
2362
2363 // Bail if nothing passed
2364 if ( empty( $parent_id ) || empty( $post_type ) ) {
2365 return array();
2366 }
2367
2368 // Make cache key
2369 $not_in = array( 'draft', 'future' );
2370 $key = md5(
2371 serialize(
2372 array(
2373 'parent_id' => $parent_id,
2374 'post_type' => $post_type,
2375 'post_status' => $not_in
2376 )
2377 )
2378 );
2379
2380 // Check last changed
2381 $last_changed = wp_cache_get_last_changed( 'bbpress_posts' );
2382 $cache_key = "bbp_child_ids:{$key}:{$last_changed}";
2383
2384 // Check for cache and set if needed
2385 $child_ids = wp_cache_get( $cache_key, 'bbpress_posts' );
2386
2387 // Not already cached
2388 if ( false === $child_ids ) {
2389
2390 // Join post statuses to specifically exclude together
2391 $post_status = "'" . implode( "', '", $not_in ) . "'";
2392 $bbp_db = bbp_db();
2393
2394 // Note that we can't use WP_Query here thanks to post_status assumptions
2395 $query = $bbp_db->prepare( "SELECT ID FROM {$bbp_db->posts} WHERE post_parent = %d AND post_status NOT IN ( {$post_status} ) AND post_type = %s ORDER BY ID DESC", $parent_id, $post_type );
2396 $child_ids = (array) $bbp_db->get_col( $query );
2397
2398 // Always cache the results
2399 wp_cache_set( $cache_key, $child_ids, 'bbpress_posts' );
2400 }
2401
2402 // Make sure results are INTs
2403 $child_ids = wp_parse_id_list( $child_ids );
2404
2405 // Filter & return
2406 return (array) apply_filters( 'bbp_get_all_child_ids', $child_ids, $parent_id, $post_type );
2407 }
2408
2409 /**
2410 * Prime familial post caches.
2411 *
2412 * This function uses _prime_post_caches() to prepare the object cache for
2413 * imminent requests to post objects that aren't naturally cached by the primary
2414 * WP_Query calls themselves. Post author caches are also primed.
2415 *
2416 * This is triggered when a `update_post_family_cache` argument is set to true.
2417 *
2418 * Also see: bbp_update_post_author_caches()
2419 *
2420 * @since 2.6.0 bbPress (r6699)
2421 *
2422 * @param array $objects Array of objects, fresh from a query
2423 *
2424 * @return bool True if some IDs were cached
2425 */
2426 function bbp_update_post_family_caches( $objects = array() ) {
2427
2428 // Bail if no posts
2429 if ( empty( $objects ) ) {
2430 return false;
2431 }
2432
2433 // Default value
2434 $post_ids = array();
2435
2436 // Filter the types of IDs to prime
2437 $ids = apply_filters(
2438 'bbp_update_post_family_caches',
2439 array(
2440 '_bbp_last_active_id',
2441 '_bbp_last_reply_id',
2442 '_bbp_last_topic_id',
2443 '_bbp_reply_to'
2444 ),
2445 $objects
2446 );
2447
2448 // Get the last active IDs
2449 foreach ( $objects as $object ) {
2450 $object = get_post( $object );
2451
2452 // Skip if post ID is empty.
2453 if ( empty( $object->ID ) ) {
2454 continue;
2455 }
2456
2457 // Meta IDs
2458 foreach ( $ids as $key ) {
2459 $post_ids[] = get_post_meta( $object->ID, $key, true );
2460 }
2461
2462 // This post ID is already cached, but the post author may not be
2463 $post_ids[] = $object->ID;
2464 }
2465
2466 // Unique, non-zero values
2467 $post_ids = bbp_get_unique_array_values( $post_ids );
2468
2469 // Bail if no IDs to prime
2470 if ( empty( $post_ids ) ) {
2471 return false;
2472 }
2473
2474 // Prime post caches
2475 _prime_post_caches( $post_ids, true, true );
2476
2477 // Prime post author caches
2478 bbp_update_post_author_caches( $post_ids );
2479
2480 // Return
2481 return true;
2482 }
2483
2484 /**
2485 * Prime post author caches.
2486 *
2487 * This function uses cache_users() to prepare the object cache for
2488 * imminent requests to user objects that aren't naturally cached by the primary
2489 * WP_Query calls themselves.
2490 *
2491 * This is triggered when a `update_post_author_cache` argument is set to true.
2492 *
2493 * @since 2.6.0 bbPress (r6699)
2494 *
2495 * @param array $objects Array of objects, fresh from a query
2496 *
2497 * @return bool True if some IDs were cached
2498 */
2499 function bbp_update_post_author_caches( $objects = array() ) {
2500
2501 // Bail if no posts
2502 if ( empty( $objects ) ) {
2503 return false;
2504 }
2505
2506 // Default value
2507 $user_ids = array();
2508
2509 // Get the user IDs (could use wp_list_pluck() if this is ever a bottleneck)
2510 foreach ( $objects as $object ) {
2511 $object = get_post( $object );
2512
2513 // Skip if post does not have an author ID.
2514 if ( empty( $object->post_author ) ) {
2515 continue;
2516 }
2517
2518 // If post exists, add post author to the array.
2519 $user_ids[] = (int) $object->post_author;
2520 }
2521
2522 // Unique, non-zero values
2523 $user_ids = bbp_get_unique_array_values( $user_ids );
2524
2525 // Bail if no IDs to prime
2526 if ( empty( $user_ids ) ) {
2527 return false;
2528 }
2529
2530 // Try to prime user caches
2531 cache_users( $user_ids );
2532
2533 // Return
2534 return true;
2535 }
2536
2537 /** Globals *******************************************************************/
2538
2539 /**
2540 * Get the unfiltered value of a global $post's key
2541 *
2542 * Used most frequently when editing a forum/topic/reply
2543 *
2544 * @since 2.1.0 bbPress (r3694)
2545 *
2546 * @param string $field Name of the key
2547 * @param string $context How to sanitize - raw|edit|db|display|attribute|js
2548 * @return string Field value
2549 */
2550 function bbp_get_global_post_field( $field = 'ID', $context = 'edit' ) {
2551
2552 // Get the post, and maybe get a field from it
2553 $post = get_post();
2554 $retval = isset( $post->{$field} )
2555 ? sanitize_post_field( $field, $post->{$field}, $post->ID, $context )
2556 : '';
2557
2558 // Filter & return
2559 return apply_filters( 'bbp_get_global_post_field', $retval, $post, $field, $context );
2560 }
2561
2562 /** Nonces ********************************************************************/
2563
2564 /**
2565 * Makes sure the user requested an action from another page on this site.
2566 *
2567 * To avoid security exploits within the theme.
2568 *
2569 * @since 2.1.0 bbPress (r4022)
2570 *
2571 * @param string $action Action nonce
2572 * @param string $query_arg where to look for nonce in $_REQUEST
2573 */
2574 function bbp_verify_nonce_request( $action = '', $query_arg = '_wpnonce' ) {
2575
2576 /** Home URL **************************************************************/
2577
2578 // Parse home_url() into pieces to remove query-strings, strange characters,
2579 // and other funny things that plugins might to do to it.
2580 $parsed_home = parse_url( home_url( '/', ( is_ssl() ? 'https' : 'http' ) ) );
2581
2582 // Maybe include the port, if it's included
2583 if ( isset( $parsed_home['port'] ) ) {
2584 $parsed_host = $parsed_home['host'] . ':' . $parsed_home['port'];
2585 } else {
2586 $parsed_host = $parsed_home['host'];
2587 }
2588
2589 // Set the home URL for use in comparisons
2590 $home_url = trim( strtolower( $parsed_home['scheme'] . '://' . $parsed_host . $parsed_home['path'] ), '/' );
2591
2592 /** Requested URL *********************************************************/
2593
2594 // Maybe include the port, if it's included in home_url()
2595 if ( isset( $parsed_home['port'] ) && false === strpos( $_SERVER['HTTP_HOST'], ':' ) ) {
2596 $request_host = $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'];
2597 } else {
2598 $request_host = $_SERVER['HTTP_HOST'];
2599 }
2600
2601 // Build the currently requested URL
2602 $scheme = bbp_get_url_scheme();
2603 $requested_url = strtolower( $scheme . $request_host . $_SERVER['REQUEST_URI'] );
2604
2605 /** Look for match ********************************************************/
2606
2607 /**
2608 * Filters the requested URL being nonce-verified.
2609 *
2610 * Useful for configurations like reverse proxying.
2611 *
2612 * @since 2.2.0 bbPress (r4361)
2613 *
2614 * @param string $requested_url The requested URL.
2615 */
2616 $matched_url = apply_filters( 'bbp_verify_nonce_request_url', $requested_url );
2617
2618 // Check the nonce
2619 $result = isset( $_REQUEST[ $query_arg ] )
2620 ? wp_verify_nonce( $_REQUEST[ $query_arg ], $action )
2621 : false;
2622
2623 // Nonce check failed
2624 if ( empty( $result ) || empty( $action ) || ( strpos( $matched_url, $home_url ) !== 0 ) ) {
2625 $result = false;
2626 }
2627
2628 /**
2629 * Fires at the end of the nonce verification check.
2630 *
2631 * @since 2.1.0 bbPress (r4023)
2632 *
2633 * @param string $action Action nonce.
2634 * @param bool $result Boolean result of nonce verification.
2635 */
2636 do_action( 'bbp_verify_nonce_request', $action, $result );
2637
2638 return $result;
2639 }
2640
2641 /** Feeds *********************************************************************/
2642
2643 /**
2644 * This function is hooked into the WordPress 'request' action and is
2645 * responsible for sniffing out the query vars and serving up RSS2 feeds if
2646 * the stars align and the user has requested a feed of any bbPress type.
2647 *
2648 * @since 2.0.0 bbPress (r3171)
2649 *
2650 * @param array $query_vars
2651 * @return array
2652 */
2653 function bbp_request_feed_trap( $query_vars = array() ) {
2654
2655 // Looking at a feed
2656 if ( isset( $query_vars['feed'] ) ) {
2657
2658 // Forum/Topic/Reply Feed
2659 if ( isset( $query_vars['post_type'] ) ) {
2660
2661 // Matched post type
2662 $post_type = false;
2663
2664 // Post types to check
2665 $post_types = array(
2666 bbp_get_forum_post_type(),
2667 bbp_get_topic_post_type(),
2668 bbp_get_reply_post_type()
2669 );
2670
2671 // Cast query vars as array outside of foreach loop
2672 $qv_array = (array) $query_vars['post_type'];
2673
2674 // Check if this query is for a bbPress post type
2675 foreach ( $post_types as $bbp_pt ) {
2676 if ( in_array( $bbp_pt, $qv_array, true ) ) {
2677 $post_type = $bbp_pt;
2678 break;
2679 }
2680 }
2681
2682 // Looking at a bbPress post type
2683 if ( ! empty( $post_type ) ) {
2684
2685 // Supported select query vars
2686 $select_query_vars = array(
2687 'p' => false,
2688 'name' => false,
2689 $post_type => false,
2690 );
2691
2692 // Setup matched variables to select
2693 foreach ( $query_vars as $key => $value ) {
2694 if ( isset( $select_query_vars[ $key ] ) ) {
2695 $select_query_vars[ $key ] = $value;
2696 }
2697 }
2698
2699 // Remove any empties
2700 $select_query_vars = array_filter( $select_query_vars );
2701
2702 // What bbPress post type are we looking for feeds on?
2703 switch ( $post_type ) {
2704
2705 // Forum
2706 case bbp_get_forum_post_type() :
2707
2708 // Define local variable(s)
2709 $meta_query = array();
2710
2711 // Single forum
2712 if ( ! empty( $select_query_vars ) ) {
2713
2714 // Load up our own query
2715 // phpcs:ignore WordPress.WP.DiscouragedFunctions.query_posts_query_posts
2716 query_posts(
2717 array_merge(
2718 array(
2719 'post_type' => bbp_get_forum_post_type(),
2720 'feed' => true
2721 ),
2722 $select_query_vars
2723 )
2724 );
2725
2726 // Restrict to specific forum ID
2727 $meta_query = array(
2728 array(
2729 'key' => '_bbp_forum_id',
2730 'value' => bbp_get_forum_id(),
2731 'type' => 'NUMERIC',
2732 'compare' => '='
2733 )
2734 );
2735 }
2736
2737 // Only forum replies
2738 if ( ! empty( $_GET['type'] ) && ( bbp_get_reply_post_type() === $_GET['type'] ) ) {
2739
2740 // The query
2741 $the_query = array(
2742 'author' => 0,
2743 'feed' => true,
2744 'post_type' => bbp_get_reply_post_type(),
2745 'post_parent' => 'any',
2746 'post_status' => bbp_get_public_reply_statuses(),
2747 'posts_per_page' => bbp_get_replies_per_rss_page(),
2748 'order' => 'DESC',
2749 'meta_query' => $meta_query
2750 );
2751
2752 // Output the feed
2753 bbp_display_replies_feed_rss2( $the_query );
2754
2755 // Only forum topics
2756 } elseif ( ! empty( $_GET['type'] ) && ( bbp_get_topic_post_type() === $_GET['type'] ) ) {
2757
2758 // The query
2759 $the_query = array(
2760 'author' => 0,
2761 'feed' => true,
2762 'post_type' => bbp_get_topic_post_type(),
2763 'post_parent' => bbp_get_forum_id(),
2764 'post_status' => bbp_get_public_topic_statuses(),
2765 'posts_per_page' => bbp_get_topics_per_rss_page(),
2766 'order' => 'DESC'
2767 );
2768
2769 // Output the feed
2770 bbp_display_topics_feed_rss2( $the_query );
2771
2772 // All forum topics and replies
2773 } else {
2774
2775 // Exclude private/hidden forums if not looking at single
2776 if ( empty( $select_query_vars ) ) {
2777 $meta_query = array( bbp_exclude_forum_ids( 'meta_query' ) );
2778 }
2779
2780 // The query
2781 $the_query = array(
2782 'author' => 0,
2783 'feed' => true,
2784 'post_type' => array( bbp_get_reply_post_type(), bbp_get_topic_post_type() ),
2785 'post_parent' => 'any',
2786 'post_status' => bbp_get_public_topic_statuses(),
2787 'posts_per_page' => bbp_get_replies_per_rss_page(),
2788 'order' => 'DESC',
2789 'meta_query' => $meta_query
2790 );
2791
2792 // Output the feed
2793 bbp_display_replies_feed_rss2( $the_query );
2794 }
2795
2796 break;
2797
2798 // Topic feed - Show replies
2799 case bbp_get_topic_post_type() :
2800
2801 // Single topic
2802 if ( ! empty( $select_query_vars ) ) {
2803
2804 // Load up our own query
2805 // phpcs:ignore WordPress.WP.DiscouragedFunctions.query_posts_query_posts
2806 query_posts(
2807 array_merge(
2808 array(
2809 'post_type' => bbp_get_topic_post_type(),
2810 'feed' => true
2811 ),
2812 $select_query_vars
2813 )
2814 );
2815
2816 // Output the feed
2817 bbp_display_replies_feed_rss2( array( 'feed' => true ) );
2818
2819 // All topics
2820 } else {
2821
2822 // The query
2823 $the_query = array(
2824 'author' => 0,
2825 'feed' => true,
2826 'post_parent' => 'any',
2827 'posts_per_page' => bbp_get_topics_per_rss_page(),
2828 'show_stickies' => false
2829 );
2830
2831 // Output the feed
2832 bbp_display_topics_feed_rss2( $the_query );
2833 }
2834
2835 break;
2836
2837 // Replies
2838 case bbp_get_reply_post_type() :
2839
2840 // The query
2841 $the_query = array(
2842 'posts_per_page' => bbp_get_replies_per_rss_page(),
2843 'meta_query' => array( array() ),
2844 'feed' => true
2845 );
2846
2847 // All replies
2848 if ( empty( $select_query_vars ) ) {
2849 bbp_display_replies_feed_rss2( $the_query );
2850 }
2851
2852 break;
2853 }
2854 }
2855
2856 // Single Topic Vview
2857 } elseif ( isset( $query_vars[ bbp_get_view_rewrite_id() ] ) ) {
2858
2859 // Get the view
2860 $view = $query_vars[ bbp_get_view_rewrite_id() ];
2861
2862 // We have a view to display a feed
2863 if ( ! empty( $view ) ) {
2864
2865 // Get the view query
2866 $the_query = bbp_get_view_query_args( $view );
2867
2868 // Output the feed if view exists
2869 if ( ! empty( $the_query ) ) {
2870 bbp_display_topics_feed_rss2( $the_query );
2871 }
2872 }
2873 }
2874
2875 // @todo User profile feeds
2876 }
2877
2878 // No feed so continue on
2879 return $query_vars;
2880 }
2881
2882 /** Templates ******************************************************************/
2883
2884 /**
2885 * Used to guess if page exists at requested path
2886 *
2887 * @since 2.0.0 bbPress (r3304)
2888 *
2889 * @param string $path
2890 * @return mixed False if no page, Page object if true
2891 */
2892 function bbp_get_page_by_path( $path = '' ) {
2893
2894 // Default to false
2895 $retval = false;
2896
2897 // Path is not empty
2898 if ( ! empty( $path ) ) {
2899
2900 // Pretty permalinks are on so path might exist
2901 if ( get_option( 'permalink_structure' ) ) {
2902 $retval = get_page_by_path( $path );
2903 }
2904 }
2905
2906 // Filter & return
2907 return apply_filters( 'bbp_get_page_by_path', $retval, $path );
2908 }
2909
2910 /**
2911 * Prevent WordPress from guessing permalinks for bbPress post types.
2912 *
2913 * bbPress post types are excluded from search because their visibility depends
2914 * on forum access. Older versions of WordPress may otherwise guess a restricted
2915 * forum or topic permalink from a partial slug and expose its full title.
2916 *
2917 * @since 2.6.17 bbPress
2918 *
2919 * @param bool $do_redirect_guess Whether to attempt to guess a redirect URL.
2920 *
2921 * @return bool Whether to attempt to guess a redirect URL.
2922 */
2923 function bbp_do_not_guess_404_permalink( $do_redirect_guess = true ) {
2924 $post_types = (array) get_query_var( 'post_type' );
2925
2926 if ( bbp_is_custom_post_type( $post_types ) ) {
2927 $do_redirect_guess = false;
2928 }
2929
2930 return $do_redirect_guess;
2931 }
2932
2933 /**
2934 * Sets the 404 status.
2935 *
2936 * Used primarily with topics/replies inside hidden forums.
2937 *
2938 * @since 2.0.0 bbPress (r3051)
2939 * @since 2.6.0 bbPress (r6583) Use status_header() & nocache_headers()
2940 *
2941 * @param WP_Query $query The query being checked
2942 *
2943 * @return bool Always returns true
2944 */
2945 function bbp_set_404( $query = null ) {
2946
2947 // Global fallback
2948 if ( empty( $query ) ) {
2949 $query = bbp_get_wp_query();
2950 }
2951
2952 // Setup environment
2953 $query->set_404();
2954
2955 // Setup request
2956 status_header( 404 );
2957 nocache_headers();
2958 }
2959
2960 /**
2961 * Sets the 200 status header.
2962 *
2963 * @since 2.6.0 bbPress (r6583)
2964 */
2965 function bbp_set_200() {
2966 status_header( 200 );
2967 }
2968
2969 /**
2970 * Maybe handle the default 404 handling for some bbPress conditions
2971 *
2972 * Some conditions (like private/hidden forums and edits) have their own checks
2973 * on `bbp_template_redirect` and are not currently 404s.
2974 *
2975 * @since 2.6.0 bbPress (r6555)
2976 *
2977 * @param bool $override Whether to override the default handler
2978 * @param WP_Query $wp_query The posts query being referenced
2979 *
2980 * @return bool False to leave alone, true to override
2981 */
2982 function bbp_pre_handle_404( $override = false, $wp_query = false ) {
2983
2984 // Handle a bbPress 404 condition
2985 if ( isset( $wp_query->bbp_is_404 ) ) {
2986
2987 // Either force a 404 when 200, or a 200 when 404
2988 if ( true === $wp_query->bbp_is_404 ) {
2989 bbp_set_404( $wp_query );
2990 } else {
2991 bbp_set_200();
2992 }
2993
2994 // Overridden
2995 $override = true;
2996 }
2997
2998 // Return, maybe overridden
2999 return $override;
3000 }
3001
3002 /**
3003 * Maybe pre-assign the posts that are returned from a WP_Query.
3004 *
3005 * This effectively short-circuits the default query for posts, which is
3006 * currently only used to avoid calling the main query when it's not necessary.
3007 *
3008 * @since 2.6.0 bbPress (r6580)
3009 *
3010 * @param mixed $posts Default null. Array of posts (possibly empty)
3011 * @param WP_Query $wp_query
3012 *
3013 * @return mixed Null if no override. Array if overridden.
3014 */
3015 function bbp_posts_pre_query( $posts = null, $wp_query = false ) {
3016
3017 // Custom 404 handler is set, so set posts to empty array to avoid 2 queries
3018 if ( ! empty( $wp_query->bbp_is_404 ) ) {
3019 $posts = array();
3020 }
3021
3022 // Return, maybe overridden
3023 return $posts;
3024 }
3025
3026 /**
3027 * Get scheme for a URL based on is_ssl() results.
3028 *
3029 * @since 2.6.0 bbPress (r6759)
3030 *
3031 * @return string https:// if is_ssl(), otherwise http://
3032 */
3033 function bbp_get_url_scheme() {
3034 return is_ssl()
3035 ? 'https://'
3036 : 'http://';
3037 }
3038
3039 /** Titles ********************************************************************/
3040
3041 /**
3042 * Is a title longer that the maximum title length?
3043 *
3044 * Uses mb_strlen() in `8bit` mode to treat strings as raw. This matches the
3045 * behavior present in Comments, PHPMailer, RandomCompat, and others.
3046 *
3047 * @since 2.6.0 bbPress (r6783)
3048 *
3049 * @param string $title
3050 * @return bool
3051 */
3052 function bbp_is_title_too_long( $title = '' ) {
3053 $max = bbp_get_title_max_length();
3054 $len = mb_strlen( $title, '8bit' );
3055 $result = ( $len > $max );
3056
3057 // Filter & return
3058 return (bool) apply_filters( 'bbp_is_title_too_long', $result, $title, $max, $len );
3059 }
3060