PluginProbe
bbPress / 2.6.2
bbPress v2.6.2
trunk 2.0 2.0-beta-1 2.0-beta-2b 2.0-beta-3 2.0-beta-3b 2.0-rc-2 2.0-rc-3 2.0-rc-4 2.0-rc-5 2.0.1 2.0.2 2.0.3 2.1 2.1-beta-1 2.1-rc1 2.1-rc2 2.1-rc3 2.1-rc4 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.2.2 All 71 releases
bbpress / includes / common / functions.php

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

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