[ Index ]

PHP Cross Reference of BBPress

title

Body

[close]

/src/includes/extend/ -> akismet.php (source)

   1  <?php
   2  
   3  /**
   4   * Main bbPress Akismet Class
   5   *
   6   * @package bbPress
   7   * @subpackage Akismet
   8   */
   9  
  10  // Exit if accessed directly
  11  defined( 'ABSPATH' ) || exit;
  12  
  13  if ( ! class_exists( 'BBP_Akismet' ) ) :
  14  /**
  15   * Loads Akismet extension
  16   *
  17   * @since 2.0.0 bbPress (r3277)
  18   *
  19   * @package bbPress
  20   * @subpackage Akismet
  21   */
  22  class BBP_Akismet {
  23  
  24      /**
  25       * The main bbPress Akismet loader
  26       *
  27       * @since 2.0.0 bbPress (r3277)
  28       */
  29  	public function __construct() {
  30          $this->setup_actions();
  31      }
  32  
  33      /**
  34       * Setup the admin hooks
  35       *
  36       * @since 2.0.0 bbPress (r3277)
  37       *
  38       * @access private
  39       */
  40  	private function setup_actions() {
  41  
  42          // Prevent debug notices
  43          $checks = array();
  44  
  45          // bbPress functions to check for spam
  46          $checks['check']  = array(
  47              'bbp_new_topic_pre_insert'  => 1,  // New topic check
  48              'bbp_new_reply_pre_insert'  => 1,  // New reply check
  49              'bbp_edit_topic_pre_insert' => 1,  // Edit topic check
  50              'bbp_edit_reply_pre_insert' => 1   // Edit reply check
  51          );
  52  
  53          // bbPress functions for spam and ham submissions
  54          $checks['submit'] = array(
  55              'bbp_spammed_topic'   => 10, // Spammed topic
  56              'bbp_unspammed_topic' => 10, // Unspammed reply
  57              'bbp_spammed_reply'   => 10, // Spammed reply
  58              'bbp_unspammed_reply' => 10, // Unspammed reply
  59          );
  60  
  61          // Add the checks
  62          foreach ( $checks as $type => $functions ) {
  63              foreach ( $functions as $function => $priority ) {
  64                  add_filter( $function, array( $this, $type . '_post'  ), $priority );
  65              }
  66          }
  67  
  68          // Update post meta
  69          add_action( 'wp_insert_post', array( $this, 'update_post_meta' ), 10, 2 );
  70  
  71          // Cleanup
  72          add_action( 'akismet_scheduled_delete', array( $this, 'delete_old_spam' ) );
  73          add_action( 'akismet_scheduled_delete', array( $this, 'delete_old_spam_meta' ) );
  74          add_action( 'akismet_scheduled_delete', array( $this, 'delete_orphaned_spam_meta' ) );
  75  
  76          // Admin
  77          if ( is_admin() ) {
  78              add_action( 'add_meta_boxes', array( $this, 'add_metaboxes' ) );
  79          }
  80      }
  81  
  82      /**
  83       * Converts topic/reply data into Akismet comment checking format
  84       *
  85       * @since 2.0.0 bbPress (r3277)
  86       *
  87       * @param array $post_data
  88       *
  89       * @return array Array of post data
  90       */
  91  	public function check_post( $post_data = array() ) {
  92  
  93          // Define local variables
  94          $user_data = array();
  95          $post_permalink = '';
  96  
  97          // Cast the post_author to 0 if it's empty
  98          if ( empty( $post_data['post_author'] ) ) {
  99              $post_data['post_author'] = 0;
 100          }
 101  
 102          /** Author ************************************************************/
 103  
 104          $user_data['last_active'] = '';
 105          $user_data['registered']  = date( 'Y-m-d H:i:s');
 106          $user_data['total_posts'] = (int) bbp_get_user_post_count( $post_data['post_author'] );
 107  
 108          // Get user data
 109          $userdata       = get_userdata( $post_data['post_author'] );
 110          $anonymous_data = bbp_filter_anonymous_post_data();
 111  
 112          // Author is anonymous
 113          if ( ! bbp_has_errors() ) {
 114              $user_data['name']    = $anonymous_data['bbp_anonymous_name'];
 115              $user_data['email']   = $anonymous_data['bbp_anonymous_email'];
 116              $user_data['website'] = $anonymous_data['bbp_anonymous_website'];
 117  
 118          // Author is logged in
 119          } elseif ( ! empty( $userdata ) ) {
 120              $user_data['name']       = $userdata->display_name;
 121              $user_data['email']      = $userdata->user_email;
 122              $user_data['website']    = $userdata->user_url;
 123              $user_data['registered'] = $userdata->user_registered;
 124  
 125          // Missing author data, so set some empty strings
 126          } else {
 127              $user_data['name']    = '';
 128              $user_data['email']   = '';
 129              $user_data['website'] = '';
 130          }
 131  
 132          /** Post **************************************************************/
 133  
 134          if ( ! empty( $post_data['post_parent'] ) ) {
 135  
 136              // Use post parent for permalink
 137              $post_permalink = get_permalink( $post_data['post_parent'] );
 138  
 139              // Use post parent to get datetime of last reply on this topic
 140              $reply_id = bbp_get_topic_last_reply_id( $post_data['post_parent'] );
 141              if ( ! empty( $reply_id ) ) {
 142                  $user_data['last_active'] = get_post_field( 'post_date', $reply_id );
 143              }
 144          }
 145  
 146          // Pass title & content together into comment content
 147          $_post_content = trim( $post_data['post_title'] . "\n\n" . $post_data['post_content'] );
 148  
 149          // Check if the post data is spammy...
 150          $_post = $this->maybe_spam( array(
 151              'comment_author'                 => $user_data['name'],
 152              'comment_author_email'           => $user_data['email'],
 153              'comment_author_url'             => $user_data['website'],
 154              'comment_content'                => $_post_content,
 155              'comment_post_ID'                => $post_data['post_parent'],
 156              'comment_type'                   => $post_data['post_type'],
 157              'comment_total'                  => $user_data['total_posts'],
 158              'comment_last_active_gmt'        => $user_data['last_active'],
 159              'comment_account_registered_gmt' => $user_data['registered'],
 160              'permalink'                      => $post_permalink,
 161              'referrer'                       => wp_get_raw_referer(),
 162              'user_agent'                     => bbp_current_author_ua(),
 163              'user_ID'                        => $post_data['post_author'],
 164              'user_ip'                        => bbp_current_author_ip(),
 165              'user_role'                      => $this->get_user_roles( $post_data['post_author'] ),
 166          ) );
 167  
 168          // Set the results (from maybe_spam() above)
 169          $post_data['bbp_akismet_result_headers'] = $_post['bbp_akismet_result_headers'];
 170          $post_data['bbp_akismet_result']         = $_post['bbp_akismet_result'];
 171          $post_data['bbp_post_as_submitted']      = $_post;
 172  
 173          // Avoid recursion by unsetting results from post-as-submitted
 174          unset(
 175              $post_data['bbp_post_as_submitted']['bbp_akismet_result_headers'],
 176              $post_data['bbp_post_as_submitted']['bbp_akismet_result']
 177          );
 178  
 179          // Allow post_data to be manipulated
 180          $post_data = apply_filters( 'bbp_akismet_check_post', $post_data );
 181  
 182          // Parse and log the last response
 183          $this->last_post = $this->parse_response( $post_data );
 184  
 185          // Return the last response back to the filter
 186          return $this->last_post;
 187      }
 188  
 189      /**
 190       * Parse the response from the Akismet service, and alter the post data as
 191       * necessary. For example, switch the status to `spam` if spammy.
 192       *
 193       * Note: this method also is responsible for allowing users who can moderate to
 194       * never have their posts marked as spam. This is because they are "trusted"
 195       * users. However, their posts are still sent to Akismet to be checked.
 196       *
 197       * @since 2.6.0 bbPress (r6873)
 198       *
 199       * @param array $post_data
 200       *
 201       * @return array
 202       */
 203  	private function parse_response( $post_data = array() ) {
 204  
 205          // Get the parent ID of the post as submitted
 206          $parent_id = ! empty( $post_data['bbp_post_as_submitted']['comment_post_ID'] )
 207              ? absint( $post_data['bbp_post_as_submitted']['comment_post_ID'] )
 208              : 0;
 209  
 210          // Allow moderators to skip spam (includes per-forum moderators via $parent_id)
 211          $skip_spam = current_user_can( 'moderate', $parent_id );
 212  
 213          // Bail early if current user can skip spam enforcement
 214          if ( apply_filters( 'bbp_bypass_spam_enforcement', $skip_spam, $post_data ) ) {
 215              return $post_data;
 216          }
 217  
 218          // Discard obvious spam
 219          if ( get_option( 'akismet_strictness' ) ) {
 220  
 221              // Akismet is 100% confident this is spam
 222              if (
 223                  ! empty( $post_data['bbp_akismet_result_headers']['x-akismet-pro-tip'] )
 224                  &&
 225                  ( 'discard' === $post_data['bbp_akismet_result_headers']['x-akismet-pro-tip'] )
 226              ) {
 227  
 228                  // URL to redirect to (current, or forum root)
 229                  $redirect_to = ( ! empty( $_SERVER['HTTP_HOST'] ) && ! empty( $_SERVER['REQUEST_URI'] ) )
 230                      ? bbp_get_url_scheme() . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']
 231                      : bbp_get_root_url();
 232  
 233                  // Do the redirect (post data not saved!)
 234                  bbp_redirect( $redirect_to );
 235              }
 236          }
 237  
 238          // Result is spam, so set the status as such
 239          if ( 'true' === $post_data['bbp_akismet_result'] ) {
 240  
 241              // Let plugins do their thing
 242              do_action( 'bbp_akismet_spam_caught' );
 243  
 244              // Set post_status to spam
 245              $post_data['post_status'] = bbp_get_spam_status_id();
 246  
 247              // Filter spammy tags into meta data
 248              add_filter( 'bbp_new_reply_pre_set_terms', array( $this, 'filter_post_terms' ), 1, 3 );
 249          }
 250  
 251          // Return the (potentially modified) post data
 252          return $post_data;
 253      }
 254  
 255      /**
 256       * Submit a post for spamming or hamming
 257       *
 258       * @since 2.0.0 bbPress (r3277)
 259       *
 260       * @param int $post_id
 261       *
 262       * @global string $akismet_api_host
 263       * @global string $akismet_api_port
 264       * @global object $current_user
 265       * @global object $current_site
 266       *
 267       * @return array Array of existing topic terms
 268       */
 269  	public function submit_post( $post_id = 0 ) {
 270          global $current_user, $current_site;
 271  
 272          // Innocent until proven guilty
 273          $request_type   = 'ham';
 274          $current_filter = current_filter();
 275  
 276          // Check this filter and adjust the $request_type accordingly
 277          switch ( $current_filter ) {
 278  
 279              // Mysterious, and straight from the can
 280              case 'bbp_spammed_topic' :
 281              case 'bbp_spammed_reply' :
 282                  $request_type = 'spam';
 283                  break;
 284  
 285              // Honey-glazed, a straight off the bone
 286              case 'bbp_unspammed_topic' :
 287              case 'bbp_unspammed_reply' :
 288                  $request_type = 'ham';
 289                  break;
 290  
 291              // Possibly poison...
 292              default :
 293                  return;
 294          }
 295  
 296          // Setup some variables
 297          $post_id = (int) $post_id;
 298  
 299          // Make sure we have a post
 300          $_post = get_post( $post_id );
 301  
 302          // Bail if get_post() fails
 303          if ( empty( $_post ) ) {
 304              return;
 305          }
 306  
 307          // Bail if we're spamming, but the post_status isn't spam
 308          if ( ( 'spam' === $request_type ) && ( bbp_get_spam_status_id() !== $_post->post_status ) ) {
 309              return;
 310          }
 311  
 312          // Pass title & content together into comment content
 313          $_post_content = trim( $_post->post_title . "\n\n" . $_post->post_content );
 314  
 315          // Set some default post_data
 316          $post_data = array(
 317              'comment_approved'     => $_post->post_status,
 318              'comment_author'       => $_post->post_author ? get_the_author_meta( 'display_name', $_post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_name',    true ),
 319              'comment_author_email' => $_post->post_author ? get_the_author_meta( 'email',        $_post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_email',   true ),
 320              'comment_author_url'   => $_post->post_author ? bbp_get_user_profile_url(            $_post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_website', true ),
 321              'comment_content'      => $_post_content,
 322              'comment_date_gmt'     => $_post->post_date_gmt,
 323              'comment_ID'           => $post_id,
 324              'comment_post_ID'      => $_post->post_parent,
 325              'comment_type'         => $_post->post_type,
 326              'permalink'            => get_permalink( $post_id ),
 327              'user_ID'              => $_post->post_author,
 328              'user_ip'              => get_post_meta( $post_id, '_bbp_author_ip', true ),
 329              'user_role'            => $this->get_user_roles( $_post->post_author ),
 330          );
 331  
 332          // Use the original version stored in post_meta if available
 333          $as_submitted = get_post_meta( $post_id, '_bbp_akismet_as_submitted', true );
 334          if ( $as_submitted && is_array( $as_submitted ) && isset( $as_submitted['comment_content'] ) ) {
 335              $post_data = array_merge( $post_data, $as_submitted );
 336          }
 337  
 338          // Add the reporter IP address
 339          $post_data['reporter_ip']  = bbp_current_author_ip();
 340  
 341          // Add some reporter info
 342          if ( is_object( $current_user ) ) {
 343              $post_data['reporter'] = $current_user->user_login;
 344          }
 345  
 346          // Add the current site domain
 347          if ( is_object( $current_site ) ) {
 348              $post_data['site_domain'] = $current_site->domain;
 349          }
 350  
 351          // Place your slide beneath the microscope
 352          $post_data = $this->maybe_spam( $post_data, 'submit', $request_type );
 353  
 354          // Manual user action
 355          if ( isset( $post_data['reporter'] ) ) {
 356  
 357              // What kind of action
 358              switch ( $request_type ) {
 359  
 360                  // Spammy
 361                  case 'spam' :
 362                      if ( 'topic' === $post_data['comment_type'] ) {
 363                          /* translators: %s: reporter name */
 364                          $message = sprintf( esc_html__( '%s reported this topic as spam', 'bbpress' ),
 365                              $post_data['reporter']
 366                          );
 367                      } elseif ( 'reply' === $post_data['comment_type'] ) {
 368                          /* translators: %s: reporter name */
 369                          $message = sprintf( esc_html__( '%s reported this reply as spam', 'bbpress' ),
 370                              $post_data['reporter']
 371                          );
 372                      } else {
 373                          /* translators: 1: reporter name, 2: comment type */
 374                          $message = sprintf( esc_html__( '%1$s reported this %2$s as spam', 'bbpress' ),
 375                              $post_data['reporter'],
 376                              $post_data['comment_type']
 377                          );
 378                      }
 379  
 380                      $this->update_post_history( $post_id, $message, 'report-spam' );
 381                      update_post_meta( $post_id, '_bbp_akismet_user_result', 'true'                 );
 382                      update_post_meta( $post_id, '_bbp_akismet_user',        $post_data['reporter'] );
 383                      break;
 384  
 385                  // Hammy
 386                  case 'ham' :
 387                      if ( 'topic' === $post_data['comment_type'] ) {
 388                          /* translators: %s: reporter name */
 389                          $message = sprintf( esc_html__( '%s reported this topic as not spam', 'bbpress' ),
 390                              $post_data['reporter']
 391                          );
 392                      } elseif ( 'reply' === $post_data['comment_type'] ) {
 393                          /* translators: %s: reporter name */
 394                          $message = sprintf( esc_html__( '%s reported this reply as not spam', 'bbpress' ),
 395                              $post_data['reporter']
 396                          );
 397                      } else {
 398                          /* translators: 1: reporter name, 2: comment type */
 399                          $message = sprintf( esc_html__( '%1$s reported this %2$s as not spam', 'bbpress' ),
 400                              $post_data['reporter'],
 401                              $post_data['comment_type']
 402                          );
 403                      }
 404  
 405                      $this->update_post_history( $post_id, $message, 'report-ham' );
 406                      update_post_meta( $post_id, '_bbp_akismet_user_result', 'false'                 );
 407                      update_post_meta( $post_id, '_bbp_akismet_user',         $post_data['reporter'] );
 408  
 409                      // @todo Topic term revision history
 410                      break;
 411  
 412                  // Possible other actions
 413                  default :
 414                      break;
 415              }
 416          }
 417  
 418          do_action( 'bbp_akismet_submit_' . $request_type . '_post', $post_id, $post_data['bbp_akismet_result'] );
 419      }
 420  
 421      /**
 422       * Ping Akismet service and check for spam/ham response
 423       *
 424       * @since 2.0.0 bbPress (r3277)
 425       *
 426       * @param array $post_data
 427       * @param string $check Accepts check|submit
 428       * @param string $spam Accepts spam|ham
 429       *
 430       * @global string $akismet_api_host
 431       * @global string $akismet_api_port
 432       *
 433       * @return array Array of post data
 434       */
 435  	private function maybe_spam( $post_data = array(), $check = 'check', $spam = 'spam' ) {
 436          global $akismet_api_host, $akismet_api_port;
 437  
 438          // Define variables
 439          $query_string = $path = '';
 440          $response = array( '', '' );
 441  
 442          // Make sure post data is an array
 443          if ( ! is_array( $post_data ) ) {
 444              $post_data = array();
 445          }
 446  
 447          // Populate post data
 448          $post_data['blog']         = get_option( 'home' );
 449          $post_data['blog_charset'] = get_option( 'blog_charset' );
 450          $post_data['blog_lang']    = get_locale();
 451          $post_data['referrer']     = wp_get_raw_referer();
 452          $post_data['user_agent']   = bbp_current_author_ua();
 453  
 454          // Loop through _POST args and rekey strings
 455          if ( ! empty( $_POST ) && is_countable( $_POST ) ) {
 456              foreach ( $_POST as $key => $value ) {
 457                  if ( is_string( $value ) ) {
 458                      $post_data[ 'POST_' . $key ] = $value;
 459                  }
 460              }
 461          }
 462  
 463          // Loop through _SERVER args and remove allowed keys
 464          if ( ! empty( $_SERVER ) && is_countable( $_SERVER ) ) {
 465  
 466              // Keys to ignore
 467              $ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' );
 468  
 469              foreach ( $_SERVER as $key => $value ) {
 470  
 471                  // Key should not be ignored
 472                  if ( ! in_array( $key, $ignore, true ) && is_string( $value ) ) {
 473                      $post_data[ $key ] = $value;
 474  
 475                  // Key should be ignored
 476                  } else {
 477                      $post_data[ $key ] = '';
 478                  }
 479              }
 480          }
 481  
 482          // Encode post data
 483          if ( ! empty( $post_data ) && is_countable( $post_data ) ) {
 484              foreach ( $post_data as $key => $data ) {
 485                  $query_string .= $key . '=' . urlencode( wp_unslash( $data ) ) . '&';
 486              }
 487          }
 488  
 489          // Only accepts spam|ham
 490          if ( ! in_array( $spam, array( 'spam', 'ham' ), true ) ) {
 491              $spam = 'spam';
 492          }
 493  
 494          // Setup the API route
 495          if ( 'check' === $check ) {
 496              $path = '/1.1/comment-check';
 497          } elseif ( 'submit' === $check ) {
 498              $path = '/1.1/submit-' . $spam;
 499          }
 500  
 501          // Send data to Akismet
 502          if ( ! apply_filters( 'bbp_bypass_check_for_spam', false, $post_data ) ) {
 503              $response = $this->http_post( $query_string, $akismet_api_host, $path, $akismet_api_port );
 504          }
 505  
 506          // Set the result headers
 507          $post_data['bbp_akismet_result_headers'] = ! empty( $response[0] )
 508              ? $response[0] // raw
 509              : esc_html__( 'No response', 'bbpress' );
 510  
 511          // Set the result
 512          $post_data['bbp_akismet_result'] = ! empty( $response[1] )
 513              ? $response[1] // raw
 514              : esc_html__( 'No response', 'bbpress' );
 515  
 516          // Return the post data, with the results of the external Akismet request
 517          return $post_data;
 518      }
 519  
 520      /**
 521       * Update post meta after a spam check
 522       *
 523       * @since 2.0.0 bbPress (r3308)
 524       *
 525       * @param int $post_id
 526       * @param object $_post
 527       *
 528       * @global object $this->last_post
 529       */
 530  	public function update_post_meta( $post_id = 0, $_post = false ) {
 531  
 532          // Define local variable(s)
 533          $as_submitted = false;
 534  
 535          // Setup some variables
 536          $post_id = (int) $post_id;
 537  
 538          // Ensure we have a post object
 539          if ( empty( $_post ) ) {
 540              $_post = get_post( $post_id );
 541          }
 542  
 543          // Set up Akismet last post data
 544          if ( ! empty( $this->last_post['bbp_post_as_submitted'] ) ) {
 545              $as_submitted = $this->last_post['bbp_post_as_submitted'];
 546          }
 547  
 548          // wp_insert_post() might be called in other contexts. Ensure this is
 549          // the same topic/reply as was checked by BBP_Akismet::check_post()
 550          if ( is_object( $_post ) && ! empty( $this->last_post ) && is_array( $as_submitted ) ) {
 551  
 552              // Get user data
 553              $userdata       = get_userdata( $_post->post_author );
 554              $anonymous_data = bbp_filter_anonymous_post_data();
 555  
 556              // Which name?
 557              $name = ! empty( $anonymous_data['bbp_anonymous_name'] )
 558                  ? $anonymous_data['bbp_anonymous_name']
 559                  : $userdata->display_name;
 560  
 561              // Which email?
 562              $email = ! empty( $anonymous_data['bbp_anonymous_email'] )
 563                  ? $anonymous_data['bbp_anonymous_email']
 564                  : $userdata->user_email;
 565  
 566              // More checks
 567              if (
 568  
 569                  // Post matches
 570                  ( intval( $as_submitted['comment_post_ID'] ) === intval( $_post->post_parent ) )
 571  
 572                  &&
 573  
 574                  // Name matches
 575                  ( $as_submitted['comment_author'] === $name )
 576  
 577                  &&
 578  
 579                  // Email matches
 580                  ( $as_submitted['comment_author_email'] === $email )
 581              ) {
 582  
 583                  // Delete old content daily
 584                  if ( ! wp_next_scheduled( 'akismet_scheduled_delete' ) ) {
 585                      wp_schedule_event( time(), 'daily', 'akismet_scheduled_delete' );
 586                  }
 587  
 588                  // Normal result: true
 589                  if ( ! empty( $this->last_post['bbp_akismet_result'] ) && ( $this->last_post['bbp_akismet_result'] === 'true' ) ) {
 590  
 591                      // Leave a trail so other's know what we did
 592                      update_post_meta( $post_id, '_bbp_akismet_result', 'true' );
 593                      $this->update_post_history(
 594                          $post_id,
 595                          esc_html__( 'Akismet caught this post as spam', 'bbpress' ),
 596                          'check-spam'
 597                      );
 598  
 599                      // If post_status isn't the spam status, as expected, leave a note
 600                      if ( bbp_get_spam_status_id() !== $_post->post_status ) {
 601                          $this->update_post_history(
 602                              $post_id,
 603                              sprintf(
 604                                  esc_html__( 'Post status was changed to %s', 'bbpress' ),
 605                                  $_post->post_status
 606                              ),
 607                              'status-changed-' . $_post->post_status
 608                          );
 609                      }
 610  
 611                  // Normal result: false
 612                  } elseif ( ! empty( $this->last_post['bbp_akismet_result'] ) && ( $this->last_post['bbp_akismet_result'] === 'false' ) ) {
 613  
 614                      // Leave a trail so other's know what we did
 615                      update_post_meta( $post_id, '_bbp_akismet_result', 'false' );
 616                      $this->update_post_history(
 617                          $post_id,
 618                          esc_html__( 'Akismet cleared this post as not spam', 'bbpress' ),
 619                          'check-ham'
 620                      );
 621  
 622                      // If post_status is the spam status, which isn't expected, leave a note
 623                      if ( bbp_get_spam_status_id() === $_post->post_status ) {
 624                          $this->update_post_history(
 625                              $post_id,
 626                              sprintf(
 627                                  esc_html__( 'Post status was changed to %s', 'bbpress' ),
 628                                  $_post->post_status
 629                              ),
 630                              'status-changed-' . $_post->post_status
 631                          );
 632                      }
 633  
 634                  // Abnormal result: error
 635                  } else {
 636                      // Leave a trail so other's know what we did
 637                      update_post_meta( $post_id, '_bbp_akismet_error', time() );
 638                      $this->update_post_history(
 639                          $post_id,
 640                          sprintf(
 641                              esc_html__( 'Akismet was unable to check this post (response: %s), will automatically retry again later.', 'bbpress' ),
 642                              $this->last_post['bbp_akismet_result']
 643                          ),
 644                          'check-error'
 645                      );
 646                  }
 647  
 648                  // Record the complete original data as submitted for checking
 649                  if ( isset( $this->last_post['bbp_post_as_submitted'] ) ) {
 650                      update_post_meta(
 651                          $post_id,
 652                          '_bbp_akismet_as_submitted',
 653                          $this->last_post['bbp_post_as_submitted']
 654                      );
 655                  }
 656              }
 657          }
 658      }
 659  
 660      /**
 661       * Update Akismet history of a Post
 662       *
 663       * @since 2.0.0 bbPress (r3308)
 664       *
 665       * @param int $post_id
 666       * @param string $message
 667       * @param string $event
 668       */
 669  	private function update_post_history( $post_id = 0, $message = null, $event = null ) {
 670  
 671          // Define local variable(s)
 672          $user = '';
 673  
 674          // Get the current user
 675          $current_user = wp_get_current_user();
 676  
 677          // Get the user's login name if possible
 678          if ( is_object( $current_user ) && isset( $current_user->user_login ) ) {
 679              $user = $current_user->user_login;
 680          }
 681  
 682          // This used to be akismet_microtime() but it was removed in 3.0
 683          $mtime        = explode( ' ', microtime() );
 684          $message_time = $mtime[1] + $mtime[0];
 685  
 686          // Setup the event to be saved
 687          $event = array(
 688              'time'    => $message_time,
 689              'message' => $message,
 690              'event'   => $event,
 691              'user'    => $user,
 692          );
 693  
 694          // Save the event data
 695          add_post_meta( $post_id, '_bbp_akismet_history', $event );
 696      }
 697  
 698      /**
 699       * Get the Akismet history of a Post
 700       *
 701       * @since 2.0.0 bbPress (r3308)
 702       *
 703       * @param int $post_id
 704       *
 705       * @return array Array of Akismet history
 706       */
 707  	public function get_post_history( $post_id = 0 ) {
 708  
 709          // Retrieve any previous history
 710          $history = get_post_meta( $post_id, '_bbp_akismet_history' );
 711  
 712          // Sort it by the time recorded
 713          usort( $history, 'akismet_cmp_time' );
 714  
 715          return $history;
 716      }
 717  
 718      /**
 719       * Handle any terms submitted with a post flagged as spam
 720       *
 721       * @since 2.0.0 bbPress (r3308)
 722       *
 723       * @param string $terms Comma-separated list of terms
 724       * @param int $topic_id
 725       * @param int $reply_id
 726       *
 727       * @return array Array of existing topic terms
 728       */
 729  	public function filter_post_terms( $terms = '', $topic_id = 0, $reply_id = 0 ) {
 730  
 731          // Validate the reply_id and topic_id
 732          $reply_id = bbp_get_reply_id( $reply_id );
 733          $topic_id = bbp_get_topic_id( $topic_id );
 734  
 735          // Get any pre-existing terms
 736          $existing_terms = bbp_get_topic_tag_names( $topic_id );
 737  
 738          // Save the terms for later in case the reply gets hammed
 739          if ( ! empty( $terms ) ) {
 740              update_post_meta( $reply_id, '_bbp_akismet_spam_terms', $terms );
 741          }
 742  
 743          // Keep the topic tags the same for now
 744          return $existing_terms;
 745      }
 746  
 747      /**
 748       * Submit data to Akismet service with unique bbPress User Agent
 749       *
 750       * This code is directly taken from the akismet_http_post() function and
 751       * documented to bbPress 2.0 standard.
 752       *
 753       * @since 2.0.0 bbPress (r3466)
 754       *
 755       * @param string $request The request we are sending
 756       * @param string $host The host to send our request to
 757       * @param string $path The path from the host
 758       * @param string $port The port to use
 759       * @param string $ip Optional Override $host with an IP address
 760       * @return mixed WP_Error on error, array on success, empty on failure
 761       */
 762  	private function http_post( $request, $host, $path, $port = 80, $ip = '' ) {
 763  
 764          // Preload required variables
 765          $bbp_version  = bbp_get_version();
 766          $ak_version   = constant( 'AKISMET_VERSION' );
 767          $http_host    = $host;
 768          $blog_charset = get_option( 'blog_charset' );
 769  
 770          // User Agent & Content Type
 771          $akismet_ua   = "bbPress/{$bbp_version} | Akismet/{$ak_version}";
 772          $content_type = 'application/x-www-form-urlencoded; charset=' . $blog_charset;
 773  
 774          // Use specific IP (if provided)
 775          if ( ! empty( $ip ) && long2ip( ip2long( $ip ) ) ) {
 776              $http_host = $ip;
 777          }
 778  
 779          // Setup the arguments
 780          $http_args = array(
 781              'httpversion' => '1.0',
 782              'timeout'     => 15,
 783              'body'        => $request,
 784              'headers'     => array(
 785                  'Content-Type' => $content_type,
 786                  'Host'         => $host,
 787                  'User-Agent'   => $akismet_ua
 788              )
 789          );
 790  
 791          // Return the response
 792          return $this->get_response( $http_host . $path, $http_args );
 793      }
 794  
 795      /**
 796       * Handles the repeated calls to wp_remote_post(), including SSL support.
 797       *
 798       * @since 2.6.7 (bbPress r7194)
 799       *
 800       * @param string $host_and_path Scheme-less URL
 801       * @param array  $http_args     Array of arguments for wp_remote_post()
 802       * @return array
 803       */
 804  	private function get_response( $host_and_path = '', $http_args = array() ) {
 805  
 806          // Default variables
 807          $akismet_url = $http_akismet_url = 'http://' . $host_and_path;
 808          $is_ssl = $ssl_failed = false;
 809          $now = time();
 810  
 811          // Check if SSL requests were disabled fewer than 24 hours ago
 812          $ssl_disabled_time = get_option( 'akismet_ssl_disabled' );
 813  
 814          // Clean-up if 24 hours have passed
 815          if ( ! empty( $ssl_disabled_time ) && ( $ssl_disabled_time < ( $now - DAY_IN_SECONDS ) ) ) {
 816              delete_option( 'akismet_ssl_disabled' );
 817              $ssl_disabled_time = false;
 818          }
 819  
 820          // Maybe HTTPS if not disabled
 821          if ( empty( $ssl_disabled_time ) && ( $is_ssl = wp_http_supports( array( 'ssl' ) ) ) ) {
 822              $akismet_url = set_url_scheme( $akismet_url, 'https' );
 823          }
 824  
 825          // Initial remote request
 826          $response = wp_remote_post( $akismet_url, $http_args );
 827  
 828          // Initial request produced an error, so retry...
 829          if ( ! empty( $is_ssl ) && is_wp_error( $response ) ) {
 830  
 831              // Intermittent connection problems may cause the first HTTPS
 832              // request to fail and subsequent HTTP requests to succeed randomly.
 833              // Retry the HTTPS request once before disabling SSL for a time.
 834              $response = wp_remote_post( $akismet_url, $http_args );
 835  
 836              // SSL request failed twice, so try again without it
 837              if ( is_wp_error( $response ) ) {
 838                  $response   = wp_remote_post( $http_akismet_url, $http_args );
 839                  $ssl_failed = true;
 840              }
 841          }
 842  
 843          // Bail if errored
 844          if ( is_wp_error( $response ) ) {
 845              return array( '', '' );
 846          }
 847  
 848          // Maybe disable SSL for future requests
 849          if ( ! empty( $ssl_failed ) ) {
 850              update_option( 'akismet_ssl_disabled', $now );
 851          }
 852  
 853          // No errors so return response
 854          return array(
 855              $response['headers'],
 856              $response['body']
 857          );
 858      }
 859  
 860      /**
 861       * Return a user's roles on this site (including super_admin)
 862       *
 863       * @since 2.3.0 bbPress (r4812)
 864       *
 865       * @param int $user_id
 866       *
 867       * @return boolean
 868       */
 869  	private function get_user_roles( $user_id = 0 ) {
 870  
 871          // Default return value
 872          $roles = array();
 873  
 874          // Bail if cannot query the user
 875          if ( ! class_exists( 'WP_User' ) || empty( $user_id ) ) {
 876              return false;
 877          }
 878  
 879          // User ID
 880          $user = new WP_User( $user_id );
 881          if ( isset( $user->roles ) ) {
 882              $roles = (array) $user->roles;
 883          }
 884  
 885          // Super admin
 886          if ( is_multisite() && is_super_admin( $user_id ) ) {
 887              $roles[] = 'super_admin';
 888          }
 889  
 890          return implode( ',', $roles );
 891      }
 892  
 893      /** Admin *****************************************************************/
 894  
 895      /**
 896       * Add Aksimet History meta-boxes to topics and replies
 897       *
 898       * @since 2.4.0 bbPress (r5049)
 899       */
 900  	public function add_metaboxes() {
 901  
 902          // Topics
 903          add_meta_box(
 904              'bbp_akismet_topic_history',
 905              __( 'Akismet History', 'bbpress' ),
 906              array( $this, 'history_metabox' ),
 907              bbp_get_topic_post_type(),
 908              'normal',
 909              'core'
 910          );
 911  
 912          // Replies
 913          add_meta_box(
 914              'bbp_akismet_reply_history',
 915              __( 'Akismet History', 'bbpress' ),
 916              array( $this, 'history_metabox' ),
 917              bbp_get_reply_post_type(),
 918              'normal',
 919              'core'
 920          );
 921      }
 922  
 923      /**
 924       * Output for Akismet History meta-box
 925       *
 926       * @since 2.4.0 bbPress (r5049)
 927       */
 928  	public function history_metabox() {
 929  
 930          // Post ID
 931          $history = $this->get_post_history( get_the_ID() ); ?>
 932  
 933          <div class="akismet-history" style="margin: 13px 0;">
 934  
 935              <?php if ( ! empty( $history ) ) : ?>
 936  
 937                  <table>
 938                      <tbody>
 939  
 940                          <?php foreach ( $history as $row ) : ?>
 941  
 942                              <tr>
 943                                  <td style="color: #999; text-align: right; white-space: nowrap;">
 944                                      <span title="<?php echo esc_attr( date( 'D d M Y @ h:i:m a', $row['time'] ) . ' GMT' ); ?>">
 945                                          <?php bbp_time_since( $row['time'], false, true ); ?>
 946                                      </span>
 947                                  </td>
 948                                  <td style="padding-left: 5px;">
 949                                      <?php echo esc_html( $row['message'] ); ?>
 950                                  </td>
 951                              </tr>
 952  
 953                          <?php endforeach; ?>
 954                      </tbody>
 955                  </table>
 956  
 957              <?php else : ?>
 958  
 959                  <p><?php esc_html_e( 'No recorded history. Akismet has not checked this post.', 'bbpress' ); ?></p>
 960  
 961              <?php endif; ?>
 962  
 963          </div>
 964  
 965          <?php
 966      }
 967  
 968      /**
 969       * Get the number of rows to delete in a single clean-up query.
 970       *
 971       * @since 2.6.9 bbPress (r7225)
 972       *
 973       * @param string $filter The name of the filter to run.
 974       * @return int
 975       */
 976  	public function get_delete_limit( $filter = '' ) {
 977  
 978          // Default filter
 979          if ( empty( $filter ) ) {
 980              $filter = '_bbp_akismet_delete_spam_limit';
 981          }
 982  
 983          /**
 984           * Determines how many rows will be deleted in each batch.
 985           *
 986           * @param int The number of rows. Default 1000.
 987           */
 988          $delete_limit = (int) apply_filters( $filter, 1000 );
 989  
 990          // Validate and return the deletion limit
 991          return max( 1, $delete_limit );
 992      }
 993  
 994      /**
 995       * Get the interval (in days) for spam to remain in the queue.
 996       *
 997       * @since 2.6.9 bbPress (r7225)
 998       *
 999       * @param string $filter The name of the filter to run.
1000       * @return int
1001       */
1002  	public function get_delete_interval( $filter = '' ) {
1003  
1004          // Default filter
1005          if ( empty( $filter ) ) {
1006              $filter = '_bbp_akismet_delete_spam_interval';
1007          }
1008  
1009          /**
1010           * Determines how many days a piece of spam will be left in the Spam
1011           * queue before being deleted.
1012           *
1013           * @param int The number of days. Default 15.
1014           */
1015          $delete_interval = (int) apply_filters( $filter, 15 );
1016  
1017          // Validate and return the deletion interval
1018          return max( 1, $delete_interval );
1019      }
1020  
1021      /**
1022       * Deletes old spam topics & replies from the queue after 15 days
1023       * (determined by `_bbp_akismet_delete_spam_interval` filter)
1024       * since they are not useful in the long term.
1025       *
1026       * @since 2.6.7 bbPress (r7203)
1027       *
1028       * @global wpdb $wpdb
1029       */
1030  	public function delete_old_spam() {
1031          global $wpdb;
1032  
1033          // Get the deletion limit & interval
1034          $delete_limit    = $this->get_delete_limit( '_bbp_akismet_delete_spam_limit' );
1035          $delete_interval = $this->get_delete_interval( '_bbp_akismet_delete_spam_interval' );
1036  
1037          // Setup the query
1038          $sql = "SELECT id FROM {$wpdb->posts} WHERE post_type IN ('topic', 'reply') AND post_status = 'spam' AND DATE_SUB(NOW(), INTERVAL %d DAY) > post_date_gmt LIMIT %d";
1039  
1040          // Query loop of topic & reply IDs
1041          while ( $spam_ids = $wpdb->get_col( $wpdb->prepare( $sql, $delete_interval, $delete_limit ) ) ) {
1042  
1043              // Exit loop if no spam IDs
1044              if ( empty( $spam_ids ) ) {
1045                  break;
1046              }
1047  
1048              // Reset queries
1049              $wpdb->queries = array();
1050  
1051              // Loop through each of the topic/reply IDs
1052              foreach ( $spam_ids as $spam_id ) {
1053  
1054                  /**
1055                   * Perform a single action on the single topic/reply ID for
1056                   * simpler batch processing.
1057                   *
1058                   * Maybe we should run the bbp_delete_topic or bbp_delete_reply
1059                   * actions here, too?
1060                   *
1061                   * @param string The current function.
1062                   * @param int    The current topic/reply ID.
1063                   */
1064                  do_action( '_bbp_akismet_batch_delete', __FUNCTION__, $spam_id );
1065              }
1066  
1067              // Prepared as strings since id is an unsigned BIGINT, and using %
1068              // will constrain the value to the maximum signed BIGINT.
1069              $format_string = implode( ', ', array_fill( 0, count( $spam_ids ), '%s' ) );
1070  
1071              // Run the delete queries
1072              $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->posts} WHERE ID IN ( {$format_string} )", $spam_ids ) );
1073              $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->postmeta} WHERE post_id IN ( {$format_string} )", $spam_ids ) );
1074  
1075              // Clean the post cache for these topics & replies
1076              clean_post_cache( $spam_ids );
1077  
1078              /**
1079               * Single action that encompasses all topic/reply IDs after the
1080               * delete queries have been run.
1081               *
1082               * @param int   Count of topic/reply IDs
1083               * @param array Array of topic/reply IDs
1084               */
1085              do_action( '_bbp_akismet_delete_spam_count', count( $spam_ids ), $spam_ids );
1086          }
1087  
1088          /**
1089           * Determines whether tables should be optimized.
1090           *
1091           * @param int Random number between 1 and 5000.
1092           */
1093          $optimize = (int) apply_filters( '_bbp_akismet_optimize_tables', mt_rand( 1, 5000 ), array( $wpdb->posts, $wpdb->postmeta ) );
1094  
1095          // Lucky number 11
1096          if ( 11 === $optimize ) {
1097              $wpdb->query( "OPTIMIZE TABLE {$wpdb->posts}" );
1098              $wpdb->query( "OPTIMIZE TABLE {$wpdb->postmeta}" );
1099          }
1100      }
1101  
1102      /**
1103       * Deletes `_bbp_akismet_as_submitted` meta keys after 15 days
1104       * (determined by `_bbp_akismet_delete_spam_meta_interval` filter)
1105       * since they are large and not useful in the long term.
1106       *
1107       * @since 2.6.7 bbPress (r7203)
1108       *
1109       * @global wpdb $wpdb
1110       */
1111  	public function delete_old_spam_meta() {
1112          global $wpdb;
1113  
1114          // Get the deletion limit & interval
1115          $delete_limit    = $this->get_delete_limit( '_bbp_akismet_delete_spam_meta_limit' );
1116          $delete_interval = $this->get_delete_interval( '_bbp_akismet_delete_spam_meta_interval' );
1117  
1118          // Setup the query
1119          $sql = "SELECT m.post_id FROM {$wpdb->postmeta} as m INNER JOIN {$wpdb->posts} as p ON m.post_id = p.ID WHERE m.meta_key = '_bbp_akismet_as_submitted' AND DATE_SUB(NOW(), INTERVAL %d DAY) > p.post_date_gmt LIMIT %d";
1120  
1121          // Query loop of topic & reply IDs
1122          while ( $spam_ids = $wpdb->get_col( $wpdb->prepare( $sql, $delete_interval, $delete_limit ) ) ) {
1123  
1124              // Exit loop if no spam IDs
1125              if ( empty( $spam_ids ) ) {
1126                  break;
1127              }
1128  
1129              // Reset queries
1130              $wpdb->queries = array();
1131  
1132              // Loop through each of the topic/reply IDs
1133              foreach ( $spam_ids as $spam_id ) {
1134  
1135                  // Delete the as_submitted meta data
1136                  delete_post_meta( $spam_id, '_bbp_akismet_as_submitted' );
1137  
1138                  /**
1139                   * Perform a single action on the single topic/reply ID for
1140                   * simpler batch processing.
1141                   *
1142                   * @param string The current function.
1143                   * @param int    The current topic/reply ID.
1144                   */
1145                  do_action( '_bbp_akismet_batch_delete', __FUNCTION__, $spam_id );
1146              }
1147  
1148              /**
1149               * Single action that encompasses all topic/reply IDs after the
1150               * delete queries have been run.
1151               *
1152               * @param int   Count of topic/reply IDs
1153               * @param array Array of topic/reply IDs
1154               */
1155              do_action( '_bbp_akismet_delete_spam_meta_count', count( $spam_ids ), $spam_ids );
1156          }
1157  
1158          // Maybe optimize
1159          $this->maybe_optimize_postmeta();
1160      }
1161  
1162      /**
1163       * Clears post meta that no longer has corresponding posts in the database
1164       * (determined by `_bbp_akismet_delete_spam_orphaned_limit` filter)
1165       * since it is not useful in the long term.
1166       *
1167       * @since 2.6.7 bbPress (r7203)
1168       *
1169       * @global wpdb $wpdb
1170       */
1171  	public function delete_orphaned_spam_meta() {
1172          global $wpdb;
1173  
1174          // Get the deletion limit
1175          $delete_limit = $this->get_delete_limit( '_bbp_akismet_delete_spam_orphaned_limit' );
1176  
1177          // Default last meta ID
1178          $last_meta_id = 0;
1179  
1180          // Start time (float)
1181          $start_time = isset( $_SERVER['REQUEST_TIME_FLOAT'] )
1182              ? (float) $_SERVER['REQUEST_TIME_FLOAT']
1183              : microtime( true );
1184  
1185          // Maximum time
1186          $max_exec_time = (float) max( ini_get( 'max_execution_time' ) - 5, 3 );
1187  
1188          // Setup the query
1189          $sql = "SELECT m.meta_id, m.post_id, m.meta_key FROM {$wpdb->postmeta} as m LEFT JOIN {$wpdb->posts} as p ON m.post_id = p.ID WHERE p.ID IS NULL AND m.meta_id > %d ORDER BY m.meta_id LIMIT %d";
1190  
1191          // Query loop of topic & reply IDs
1192          while ( $spam_meta_results = $wpdb->get_results( $wpdb->prepare( $sql, $last_meta_id, $delete_limit ) ) ) {
1193  
1194              // Exit loop if no spam IDs
1195              if ( empty( $spam_meta_results ) ) {
1196                  break;
1197              }
1198  
1199              // Reset queries
1200              $wpdb->queries = array();
1201  
1202              // Reset deleted meta count
1203              $spam_meta_deleted = array();
1204  
1205              // Loop through each of the metas
1206              foreach ( $spam_meta_results as $spam_meta ) {
1207  
1208                  // Skip if not an Akismet key
1209                  if ( 'akismet_' !== substr( $spam_meta->meta_key, 0, 8 ) ) {
1210                      continue;
1211                  }
1212  
1213                  // Delete the meta
1214                  delete_post_meta( $spam_meta->post_id, $spam_meta->meta_key );
1215  
1216                  /**
1217                   * Perform a single action on the single topic/reply ID for
1218                   * simpler batch processing.
1219                   *
1220                   * @param string The current function.
1221                   * @param int    The current topic/reply ID.
1222                   */
1223                  do_action( '_bbp_akismet_batch_delete', __FUNCTION__, $spam_meta );
1224  
1225                  // Stash the meta ID being deleted
1226                  $spam_meta_deleted[] = $last_meta_id = $spam_meta->meta_id;
1227              }
1228  
1229              /**
1230               * Single action that encompasses all topic/reply IDs after the
1231               * delete queries have been run.
1232               *
1233               * @param int   Count of spam meta IDs
1234               * @param array Array of spam meta IDs
1235               */
1236              do_action( '_bbp_akismet_delete_spam_meta_count', count( $spam_meta_deleted ), $spam_meta_deleted );
1237  
1238              // Break if getting close to max_execution_time.
1239              if ( ( microtime( true ) - $start_time ) > $max_exec_time ) {
1240                  break;
1241              }
1242          }
1243  
1244          // Maybe optimize
1245          $this->maybe_optimize_postmeta();
1246      }
1247  
1248      /**
1249       * Maybe OPTIMIZE the _postmeta database table.
1250       *
1251       * @since 2.7.0 bbPress (r7203)
1252       *
1253       * @global wpdb $wpdb
1254       */
1255  	private function maybe_optimize_postmeta() {
1256          global $wpdb;
1257  
1258          /**
1259           * Determines whether tables should be optimized.
1260           *
1261           * @param int Random number between 1 and 5000.
1262           */
1263          $optimize = (int) apply_filters( '_bbp_akismet_optimize_table', mt_rand( 1, 5000 ), $wpdb->postmeta );
1264  
1265          // Lucky number 11
1266          if ( 11 === $optimize ) {
1267              $wpdb->query( "OPTIMIZE TABLE {$wpdb->postmeta}" );
1268          }
1269      }
1270  }
1271  endif;


Generated: Sun Apr 28 01:00:59 2024 Cross-referenced by PHPXref 0.7.1