PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.4.2
Forumax – AI Powered Advanced Community Forum Plugin v2.4.2
2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 2.1.0 All 29 releases
bbp-core / lib / bbpress / includes / extend / akismet.php

akismet.php in Forumax – AI Powered Advanced Community Forum Plugin 2.4.2, at lib/bbpress/includes/extend/akismet.php

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