PluginProbe
bbPress / 2.0-rc-4
bbPress v2.0-rc-4
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 / bbp-includes / bbp-extend-akismet.php

bbp-extend-akismet.php in bbPress 2.0-rc-4, at bbp-includes/bbp-extend-akismet.php

660 lines 20.4 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 if ( !defined( 'ABSPATH' ) ) exit;
12
13 if ( !class_exists( 'BBP_Akismet' ) ) :
14 /**
15 * Loads Akismet extension
16 *
17 * @since 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 bbPress (r3277)
28 *
29 * @uses add_filter()
30 */
31 public function __construct() {
32 $this->setup_actions();
33 }
34
35 /**
36 * Setup the admin hooks
37 *
38 * @since bbPress (r3277)
39 * @access private
40 *
41 * @uses add_filter() To add various filters
42 * @uses add_action() To add various actions
43 */
44 private function setup_actions() {
45
46 // bbPress functions to check for spam
47 $checks['check'] = array(
48 'bbp_new_topic_pre_insert' => 1, // Topic check
49 'bbp_new_reply_pre_insert' => 1 // Reply check
50 );
51
52 // bbPress functions for spam and ham submissions
53 $checks['submit'] = array(
54 'bbp_spammed_topic' => 10, // Spammed topic
55 'bbp_unspammed_topic' => 10, // Unspammed reply
56 'bbp_spammed_reply' => 10, // Spammed reply
57 'bbp_unspammed_reply' => 10, // Unspammed reply
58 );
59
60 // Add the checks
61 foreach ( $checks as $type => $functions )
62 foreach( $functions as $function => $priority )
63 add_filter( $function, array( $this, $type . '_post' ), $priority );
64
65 // Update post meta
66 add_action( 'wp_insert_post', array( $this, 'update_post_meta' ), 10, 2 );
67 }
68
69 /**
70 * Converts topic/reply data into Akismet comment checking format
71 *
72 * @since bbPress (r3277)
73 *
74 * @param string $post_data
75 *
76 * @global bbPress $bbp
77 *
78 * @uses get_userdata() To get the user data
79 * @uses bbp_filter_anonymous_user_data() To get anonymous user data
80 * @uses get_permalink() To get the permalink of the post parent
81 * @uses bbp_current_author_ip() To get the IP address of the current user
82 * @uses BBP_Akismet::maybe_spam() To check if post is spam
83 * @uses akismet_get_user_roles() To get the role(s) of the current user
84 * @uses do_action() To call the 'bbp_akismet_spam_caught' hook
85 * @uses add_filter() To call the 'bbp_new_reply_pre_set_terms' hook
86 *
87 * @return array Array of post data
88 */
89 public function check_post( $post_data ) {
90 global $bbp;
91
92 // Post is not published
93 if ( 'publish' != $post_data['post_status'] )
94 return $post_data;
95
96 // Extract post_data into variables
97 extract( $post_data );
98
99 // Get user data
100 $userdata = get_userdata( $post_author );
101 $anonymous_data = bbp_filter_anonymous_post_data();
102
103 // Put post_data back into usable array
104 $post = array(
105 'comment_author' => $anonymous_data ? $anonymous_data['bbp_anonymous_name'] : $userdata->display_name,
106 'comment_author_email' => $anonymous_data ? $anonymous_data['bbp_anonymous_email'] : $userdata->user_email,
107 'comment_author_url' => $anonymous_data ? $anonymous_data['bbp_anonymous_website'] : $userdata->user_url,
108 'comment_content' => $post_content,
109 'comment_post_ID' => $post_parent,
110 'comment_type' => $post_type,
111 'permalink' => get_permalink( $post_parent ),
112 'referrer' => $_SERVER['HTTP_REFERER'],
113 'user_agent' => $_SERVER['HTTP_USER_AGENT'],
114 'user_ID' => $post_author,
115 'user_ip' => bbp_current_author_ip(),
116 'user_role' => akismet_get_user_roles( $post_author ),
117 );
118
119 // Check the post_data
120 $post = $this->maybe_spam( $post );
121
122 // Get the result
123 $post_data['bbp_akismet_result'] = $post['bbp_akismet_result'];
124 unset( $post['bbp_akismet_result'] );
125
126 // Store the data as submitted
127 $post_data['bbp_post_as_submitted'] = $post;
128
129 // Spam
130 if ( 'true' == $post_data['bbp_akismet_result'] ) {
131
132 // Let plugins do their thing
133 do_action( 'bbp_akismet_spam_caught' );
134
135 // This is spam
136 $post_data['post_status'] = $bbp->spam_status_id;
137
138 // We don't want your spam tags here
139 add_filter( 'bbp_new_reply_pre_set_terms', array( $this, 'filter_post_terms' ), 1, 3 );
140
141 // @todo Spam counter?
142 }
143
144 // @todo Topic/reply moderation? No true/false response - 'pending' or 'draft'
145 // @todo Auto-delete old spam?
146
147 // Log the last post
148 $this->last_post = $post_data;
149
150 // Pass the data back to the filter
151 return $post_data;
152 }
153
154 /**
155 * Submit a post for spamming or hamming
156 *
157 * @since bbPress ({unknown})
158 *
159 * @param int $post_id
160 *
161 * @global bbPress $bbp
162 * @global WP_Query $wpdb
163 * @global string $akismet_api_host
164 * @global string $akismet_api_port
165 * @global object $current_user
166 * @global object $current_site
167 *
168 * @uses current_filter() To get the reply_id
169 * @uses get_post() To get the post object
170 * @uses get_the_author_meta() To get the author meta
171 * @uses get_post_meta() To get the post meta
172 * @uses bbp_get_user_profile_url() To get a user's profile url
173 * @uses get_permalink() To get the permalink of the post_parent
174 * @uses akismet_get_user_roles() To get the role(s) of the post_author
175 * @uses bbp_current_author_ip() To get the IP address of the current user
176 * @uses BBP_Akismet::maybe_spam() To submit the post as ham or spam
177 * @uses update_post_meta() To update the post meta with some Akismet data
178 * @uses do_action() To call the 'bbp_akismet_submit_spam_post' and 'bbp_akismet_submit_ham_post' hooks
179 *
180 * @return array Array of existing topic terms
181 */
182 public function submit_post( $post_id = 0 ) {
183 global $bbp, $wpdb, $akismet_api_host, $akismet_api_port, $current_user, $current_site;
184
185 switch ( current_filter() ) {
186
187 // Mysterious, and straight from the can
188 case 'bbp_spammed_topic' :
189 case 'bbp_spammed_reply' :
190 $request_type = 'spam';
191 break;
192
193 // Honey-glazed, a straight off the bone
194 case 'bbp_unspammed_topic' :
195 case 'bbp_unspammed_reply' :
196 $request_type = 'ham';
197 break;
198
199 // Possibly poison...
200 default :
201 return;
202 }
203
204 // Setup some variables
205 $post_id = (int) $post_id;
206
207 // Make sure we have a post
208 if ( !$post = get_post( $post_id ) )
209 return;
210
211 // Bail if we're spamming, but the post_status isn't spam
212 if ( ( 'spam' == $request_type ) && ( $bbp->spam_status_id != $post->post_status ) )
213 return;
214
215 // Set some default post_data
216 $post_data = array(
217 'comment_approved' => $post->post_status,
218 'comment_author' => $post->post_author ? get_the_author_meta( 'display_name', $post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_name', true ),
219 'comment_author_email' => $post->post_author ? get_the_author_meta( 'email', $post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_email', true ),
220 'comment_author_url' => $post->post_author ? bbp_get_user_profile_url( $post->post_author ) : get_post_meta( $post_id, '_bbp_anonymous_website', true ),
221 'comment_content' => $post->post_content,
222 'comment_date' => $post->post_date,
223 'comment_ID' => $post_id,
224 'comment_post_ID' => $post->post_parent,
225 'comment_type' => $post->post_type,
226 'permalink' => get_permalink( $post_id ),
227 'user_ID' => $post->post_author,
228 'user_ip' => get_post_meta( $post_id, '_bbp_author_ip', true ),
229 'user_role' => akismet_get_user_roles( $post->post_author ),
230 );
231
232 // Use the original version stored in post_meta if available
233 $as_submitted = get_post_meta( $post_id, '_bbp_akismet_as_submitted', true );
234 if ( $as_submitted && is_array( $as_submitted ) && isset( $as_submitted['comment_content'] ) )
235 $post_data = array_merge( $post_data, $as_submitted );
236
237 // Add the reporter IP address
238 $post_data['reporter_ip'] = bbp_current_author_ip();
239
240 // Add some reporter info
241 if ( is_object( $current_user ) )
242 $post_data['reporter'] = $current_user->user_login;
243
244 // Add the current site domain
245 if ( is_object( $current_site ) )
246 $post_data['site_domain'] = $current_site->domain;
247
248 // Place your slide beneath the microscope
249 $post_data = $this->maybe_spam( $post_data, 'submit', $request_type );
250
251 // Manual user action
252 if ( isset( $post_data['reporter'] ) ) {
253
254 // What kind of action
255 switch ( $request_type ) {
256
257 // Spammy
258 case 'spam' :
259 $this->update_post_history( $post_id, sprintf( __( '%1$s reported this %2$s as spam', 'bbpress' ), $post_data['reporter'], $post_data['comment_type'] ), 'report-spam' );
260 update_post_meta( $post_id, '_bbp_akismet_user_result', 'true' );
261 update_post_meta( $post_id, '_bbp_akismet_user', $post_data['reporter'] );
262 break;
263
264 // Hammy
265 case 'ham' :
266 $this->update_post_history( $post_id, sprintf( __( '%1$s reported this %2$s as not spam', 'bbpress' ), $post_data['reporter'], $post_data['comment_type'] ), 'report-ham' );
267 update_post_meta( $post_id, '_bbp_akismet_user_result', 'false' );
268 update_post_meta( $post_id, '_bbp_akismet_user', $post_data['reporter'] );
269
270 // @todo Topic term revision history
271 break;
272
273 // Possible other actions
274 default :
275 break;
276 }
277 }
278
279 do_action( 'bbp_akismet_submit_' . $request_type . '_post', $post_id, $post_data['bbp_akismet_result'] );
280 }
281
282 /**
283 * Ping Akismet service and check for spam/ham response
284 *
285 * @since bbPress (r3277)
286 *
287 * @param array $post_data
288 * @param string $check Accepts check|submit
289 * @param string $spam Accepts spam|ham
290 *
291 * @global string $akismet_api_host
292 * @global string $akismet_api_port
293 *
294 * @uses akismet_test_mode() To determine if Akismet is in test mode
295 * @uses akismet_http_post() To send data to the mothership for processing
296 *
297 * @return array Array of post data
298 */
299 private function maybe_spam( $post_data, $check = 'check', $spam = 'spam' ) {
300 global $akismet_api_host, $akismet_api_port;
301
302 // Define variables
303 $query_string = $path = $response = '';
304
305 // Populate post data
306 $post_data['blog'] = get_option( 'home' );
307 $post_data['blog_charset'] = get_option( 'blog_charset' );
308 $post_data['blog_lang'] = get_locale();
309 $post_data['referrer'] = $_SERVER['HTTP_REFERER'];
310 $post_data['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
311
312 // Akismet Test Mode
313 if ( akismet_test_mode() )
314 $post_data['is_test'] = 'true';
315
316 // Loop through _POST args and rekey strings
317 foreach ( $_POST as $key => $value )
318 if ( is_string( $value ) )
319 $post_data['POST_' . $key] = $value;
320
321 // Keys to ignore
322 $ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' );
323
324 // Loop through _SERVER args and remove whitelisted keys
325 foreach ( $_SERVER as $key => $value ) {
326
327 // Key should not be ignored
328 if ( !in_array( $key, $ignore ) && is_string( $value ) ) {
329 $post_data[$key] = $value;
330
331 // Key should be ignored
332 } else {
333 $post_data[$key] = '';
334 }
335 }
336
337 // Ready...
338 foreach ( $post_data as $key => $data )
339 $query_string .= $key . '=' . urlencode( stripslashes( $data ) ) . '&';
340
341 // Aim...
342 if ( 'check' == $check )
343 $path = '/1.1/comment-check';
344 elseif ( 'submit' == $check )
345 $path = '/1.1/submit-' . $spam;
346
347 // Fire!
348 $response = $this->http_post( $query_string, $akismet_api_host, $path, $akismet_api_port );
349
350 // Check the high-speed cam
351 $post_data['bbp_akismet_result'] = $response[1];
352
353 // This is ham
354 return $post_data;
355 }
356
357 /**
358 * Update post meta after a spam check
359 *
360 * @since bbPress (r3308)
361 *
362 * @param int $post_id
363 * @param object $post
364 *
365 * @global bbPress $bbp
366 * @global object $this->last_post
367 *
368 * @uses get_post() To get the post object
369 * @uses get_userdata() To get the user data
370 * @uses bbp_filter_anonymous_user_data() To get anonymous user data
371 * @uses update_post_meta() To update post meta with Akismet data
372 * @uses BBP_Akismet::update_post_history() To update post Akismet history
373 */
374 public function update_post_meta( $post_id = 0, $post = false ) {
375 global $bbp;
376
377 // Define local variable(s)
378 $as_submitted = false;
379
380 // Setup some variables
381 $post_id = (int) $post_id;
382
383 // Make sure we have a post object
384 if ( !$post = get_post( $post_id ) )
385 return;
386
387 // Get user data
388 $userdata = get_userdata( $post->post_author );
389 $anonymous_data = bbp_filter_anonymous_post_data();
390
391 // Set up Akismet last post data
392 if ( !empty( $this->last_post ) )
393 $as_submitted = $this->last_post['bbp_post_as_submitted'];
394
395 // wp_insert_post() might be called in other contexts. Make sure this is
396 // the same topic/reply as was checked by BBP_Akismet::check_post()
397 if ( is_object( $post ) && !empty( $this->last_post ) && is_array( $as_submitted ) ) {
398
399 // More checks
400 if ( intval( $as_submitted['comment_post_ID'] ) == intval( $post->post_parent )
401 && $as_submitted['comment_author'] == ( $anonymous_data ? $anonymous_data['bbp_anonymous_name'] : $userdata->display_name )
402 && $as_submitted['comment_author_email'] == ( $anonymous_data ? $anonymous_data['bbp_anonymous_email'] : $userdata->user_email )
403 ) {
404
405 // Normal result: true
406 if ( $this->last_post['bbp_akismet_result'] == 'true' ) {
407
408 // Leave a trail so other's know what we did
409 update_post_meta( $post_id, '_bbp_akismet_result', 'true' );
410 $this->update_post_history( $post_id, __( 'Akismet caught this post as spam', 'bbpress' ), 'check-spam' );
411
412 // If post_status isn't the spam status, as expected, leave a note
413 if ( $post->post_status != $bbp->spam_status_id ) {
414 $this->update_post_history( $post_id, sprintf( __( 'Post status was changed to %s', 'bbpress' ), $post->post_status ), 'status-changed-' . $post->post_status );
415 }
416
417 // Normal result: false
418 } elseif ( $this->last_post['bbp_akismet_result'] == 'false' ) {
419
420 // Leave a trail so other's know what we did
421 update_post_meta( $post_id, '_bbp_akismet_result', 'false' );
422 $this->update_post_history( $post_id, __( 'Akismet cleared this post', 'bbpress' ), 'check-ham' );
423
424 // If post_status is the spam status, which isn't expected, leave a note
425 if ( $post->post_status == $bbp->spam_status_id ) {
426
427 // @todo Use wp_blacklist_check()
428
429 $this->update_post_history( $post_id, sprintf( __( 'Post status was changed to %s', 'bbpress' ), $post->post_status ), 'status-changed-' . $post->post_status );
430 }
431
432 // Abnormal result: error
433 } else {
434 // Leave a trail so other's know what we did
435 update_post_meta( $post_id, '_bbp_akismet_error', time() );
436 $this->update_post_history( $post_id, sprintf( __( 'Akismet was unable to check this post (response: %s), will automatically retry again later.', 'bbpress' ), $this->last_post['bbp_akismet_result'] ), 'check-error' );
437 }
438
439 // Record the complete original data as submitted for checking
440 if ( isset( $this->last_post['bbp_post_as_submitted'] ) ) {
441 update_post_meta( $post_id, '_bbp_akismet_as_submitted', $this->last_post['bbp_post_as_submitted'] );
442 }
443 }
444 }
445 }
446
447 /**
448 * Update a post's Akismet history
449 *
450 * @since bbPress (r3308)
451 *
452 * @param int $post_id
453 * @param string $message
454 * @param string $event
455 *
456 * @uses wp_get_current_user() To get the current_user object
457 * @uses add_post_meta() Add Akismet post history
458 */
459 private function update_post_history( $post_id = 0, $message = null, $event = null ) {
460
461 // Define local variable(s)
462 $user = '';
463
464 // Get the current user
465 $current_user = wp_get_current_user();
466
467 // Get the user's login name if possible
468 if ( is_object( $current_user ) && isset( $current_user->user_login ) )
469 $user = $current_user->user_login;
470
471 // Setup the event to be saved
472 $event = array(
473 'time' => akismet_microtime(),
474 'message' => $message,
475 'event' => $event,
476 'user' => $user,
477 );
478
479 // Save the event data
480 add_post_meta( $post_id, '_bbp_akismet_history', $event );
481 }
482
483 /**
484 * Get a post's Akismet history
485 *
486 * @since bbPress (r3308)
487 *
488 * @param int $post_id
489 *
490 * @uses wp_get_current_user() To get the current_user object
491 * @uses get_post_meta() Get a post's Akismet history
492 *
493 * @return array Array of a post's Akismet history
494 */
495 public function get_post_history( $post_id = 0 ) {
496
497 // Retrieve any previous history
498 $history = get_post_meta( $post_id, '_bbp_akismet_history' );
499
500 // Sort it by the time recorded
501 usort( $history, 'akismet_cmp_time' );
502
503 return $history;
504 }
505
506 /**
507 * Handle any terms submitted with a post flagged as spam
508 *
509 * @since bbPress (r3308)
510 *
511 * @param string $terms Comma-separated list of terms
512 * @param int $topic_id
513 * @param int $reply_id
514 *
515 * @global bbPress $bbp
516 *
517 * @uses bbp_get_reply_id() To get the reply_id
518 * @uses bbp_get_topic_id() To get the topic_id
519 * @uses wp_get_object_terms() To a post's current terms
520 * @uses update_post_meta() To add spam terms to post meta
521 *
522 * @return array Array of existing topic terms
523 */
524 public function filter_post_terms( $terms = '', $topic_id = 0, $reply_id = 0 ) {
525 global $bbp;
526
527 // Validate the reply_id and topic_id
528 $reply_id = bbp_get_reply_id( $reply_id );
529 $topic_id = bbp_get_topic_id( $topic_id );
530
531 // Get any pre-existing terms
532 $existing_terms = wp_get_object_terms( $topic_id, bbp_get_topic_tag_tax_id(), array( 'fields' => 'names' ) );
533
534 // Save the terms for later in case the reply gets hammed
535 if ( !empty( $terms ) )
536 update_post_meta( $reply_id, '_bbp_akismet_spam_terms', $terms );
537
538 // Keep the topic tags the same for now
539 return $existing_terms;
540 }
541
542 /**
543 * Submit data to Akismet service with unique bbPress User Agent
544 *
545 * This code is directly taken from the akismet_http_post() function and
546 * documented to bbPress 2.0 standard.
547 *
548 * @since bbPress (r3466)
549 *
550 * @param string $request The request we are sending
551 * @param string $host The host to send our request to
552 * @param string $path The path from the host
553 * @param string $port The port to use
554 * @param string $ip Optional Override $host with an IP address
555 * @uses bbp_get_version() To get the current bbPress version
556 * @return mixed WP_Error on error, array on success, empty on failure
557 */
558 private function http_post( $request, $host, $path, $port = 80, $ip = '' ) {
559
560 // Preload required variables
561 $bbp_version = bbp_get_version();
562 $content_length = strlen( $request );
563 $http_host = $host;
564 $blog_charset = get_option( 'blog_charset' );
565 $response = '';
566
567 // Untque User Agent
568 $akismet_ua = "bbPress/{$bbp_version} | ";
569 $akismet_ua .= 'Akismet/' . constant( 'AKISMET_VERSION' );
570
571 // Use specific IP (if provided)
572 if ( !empty( $ip ) && long2ip( ip2long( $ip ) ) )
573 $http_host = $ip;
574
575 // WP HTTP class is available
576 if ( function_exists( 'wp_remote_post' ) ) {
577
578 // Setup the arguments
579 $http_args = array(
580 'body' => $request,
581 'headers' => array(
582 'Content-Type' => 'application/x-www-form-urlencoded; charset=' . $blog_charset,
583 'Host' => $host,
584 'User-Agent' => $akismet_ua
585 ),
586 'httpversion' => '1.0',
587 'timeout' => 15
588 );
589
590 // Where we are sending our request
591 $akismet_url = 'http://' . $http_host . $path;
592
593 // Send the request
594 $response = wp_remote_post( $akismet_url, $http_args );
595
596 // Bail if the response is an error
597 if ( is_wp_error( $response ) )
598 return '';
599
600 // No errors so return response
601 return array( $response['headers'], $response['body'] );
602
603 // WP HTTP class is not available (Why not?)
604 } else {
605
606 // Header info to use with our socket
607 $http_request = "POST {$path} HTTP/1.0\r\n";
608 $http_request .= "Host: {$host}\r\n";
609 $http_request .= "Content-Type: application/x-www-form-urlencoded; charset={$blog_charset}\r\n";
610 $http_request .= "Content-Length: {$content_length}\r\n";
611 $http_request .= "User-Agent: {$akismet_ua}\r\n";
612 $http_request .= "\r\n";
613 $http_request .= $request;
614
615 // Open a socket connection
616 if ( false != ( $fs = @fsockopen( $http_host, $port, $errno, $errstr, 10 ) ) ) {
617
618 // Write our request to the pointer
619 fwrite( $fs, $http_request );
620
621 // Loop through pointer and compile a response
622 while ( !feof( $fs ) ) {
623 // One TCP-IP packet at a time
624 $response .= fgets( $fs, 1160 );
625 }
626
627 // Close our socket
628 fclose( $fs );
629
630 // Explode the response into usable data
631 $response = explode( "\r\n\r\n", $response, 2 );
632 }
633
634 // Return the response ('' if error/empty)
635 return $response;
636 }
637 }
638 }
639 endif;
640
641 /**
642 * Loads Akismet inside the bbPress global class
643 *
644 * @since bbPress (r3277)
645 *
646 * @global bbPress $bbp
647 * @return If bbPress is not active
648 */
649 function bbp_setup_akismet() {
650 global $bbp;
651
652 // Bail if no akismet
653 if ( !defined( 'AKISMET_VERSION' ) ) return;
654
655 // Instantiate Akismet for bbPress
656 $bbp->extend->akismet = new BBP_Akismet();
657 }
658
659 ?>
660