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