[ Index ]

PHP Cross Reference of BBPress

title

Body

[close]

/src/includes/common/ -> functions.php (source)

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


Generated: Sun Nov 17 01:01:25 2019 Cross-referenced by PHPXref 0.7.1