PluginProbe
bbPress / 2.6.17
bbPress v2.6.17
2.6.17 trunk 2.0 2.0-beta-1 2.0-beta-2b 2.0-beta-3 2.0-beta-3b 2.0-rc-2 2.0-rc-3 2.0-rc-4 2.0-rc-5 2.0.1 2.0.2 2.0.3 2.1 2.1-beta-1 2.1-rc1 2.1-rc2 2.1-rc3 2.1-rc4 2.1.1 2.1.2 2.1.3 2.2 2.2.1 All 72 releases
bbpress / includes / extend / akismet.php

akismet.php in bbPress 2.6.17, at includes/extend/akismet.php

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