bp-activity-akismet.php 25.1 KB
Newer Older
lechuck's avatar
lechuck committed
1
2
<?php
/**
lechuck's avatar
lechuck committed
3
 * Akismet support for BuddyPress' Activity Stream.
lechuck's avatar
lechuck committed
4
5
 *
 * @package BuddyPress
ale's avatar
ale committed
6
 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
7
8
9
10
 * @subpackage Activity
 */

// Exit if accessed directly
ale's avatar
ale committed
11
defined( 'ABSPATH' ) || exit;
lechuck's avatar
lechuck committed
12

lechuck's avatar
lechuck committed
13
14
15
/**
 * Akismet support for the Activity component.
 *
ale's avatar
ale committed
16
17
 * @since BuddyPress (1.6.0)
 * @since BuddyPress (2.3.0) We only support Akismet 3+.
lechuck's avatar
lechuck committed
18
 */
lechuck's avatar
lechuck committed
19
20
class BP_Akismet {
	/**
lechuck's avatar
lechuck committed
21
	 * The activity last marked as spam.
lechuck's avatar
lechuck committed
22
23
24
	 *
	 * @access protected
	 * @var BP_Activity_Activity
ale's avatar
ale committed
25
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
26
27
28
29
	 */
	protected $last_activity = null;

	/**
lechuck's avatar
lechuck committed
30
	 * Constructor.
lechuck's avatar
lechuck committed
31
	 *
ale's avatar
ale committed
32
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
33
34
35
36
37
38
	 */
	public function __construct() {
		$this->setup_actions();
	}

	/**
lechuck's avatar
lechuck committed
39
	 * Hook Akismet into the activity stream.
lechuck's avatar
lechuck committed
40
	 *
ale's avatar
ale committed
41
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
	 */
	protected function setup_actions() {
		// Add nonces to activity stream lists
		add_action( 'bp_after_activity_post_form', array( $this, 'add_activity_stream_nonce' ) );
		add_action( 'bp_activity_entry_comments',  array( $this, 'add_activity_stream_nonce' ) );

		// Add a "mark as spam" button to individual activity items
		add_action( 'bp_activity_entry_meta',      array( $this, 'add_activity_spam_button' ) );
		add_action( 'bp_activity_comment_options', array( $this, 'add_activity_comment_spam_button' ) );

		// Check activity for spam
		add_action( 'bp_activity_before_save',     array( $this, 'check_activity' ), 4, 1 );

		// Tidy up member's latest (activity) update
		add_action( 'bp_activity_posted_update',   array( $this, 'check_member_activity_update' ), 1, 3 );

		// Hooks to extend Activity core spam/ham functions for Akismet
		add_action( 'bp_activity_mark_as_spam',    array( $this, 'mark_as_spam' ), 10, 2 );
		add_action( 'bp_activity_mark_as_ham',     array( $this, 'mark_as_ham' ),  10, 2 );

		// Hook into the Activity wp-admin screen
		add_action( 'bp_activity_admin_comment_row_actions', array( $this, 'comment_row_action' ), 10, 2 );
		add_action( 'bp_activity_admin_load',                array( $this, 'add_history_metabox' ) );
	}

	/**
	 * Add a history item to the hover links in an activity's row.
	 *
	 * This function lifted with love from the Akismet WordPress plugin's
	 * akismet_comment_row_action() function. Thanks!
	 *
ale's avatar
ale committed
73
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
74
	 *
ale's avatar
ale committed
75
	 * @param array $actions  The hover links.
lechuck's avatar
lechuck committed
76
	 * @param array $activity The activity for the current row being processed.
ale's avatar
ale committed
77
	 *
lechuck's avatar
lechuck committed
78
	 * @return array The hover links.
lechuck's avatar
lechuck committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
	 */
	function comment_row_action( $actions, $activity ) {
		$akismet_result = bp_activity_get_meta( $activity['id'], '_bp_akismet_result' );
		$user_result    = bp_activity_get_meta( $activity['id'], '_bp_akismet_user_result' );
		$desc           = '';

		if ( !$user_result || $user_result == $akismet_result ) {
			// Show the original Akismet result if the user hasn't overridden it, or if their decision was the same
			if ( 'true' == $akismet_result && $activity['is_spam'] )
				$desc = __( 'Flagged as spam by Akismet', 'buddypress' );

			elseif ( 'false' == $akismet_result && !$activity['is_spam'] )
				$desc = __( 'Cleared by Akismet', 'buddypress' );

		} else {
			$who = bp_activity_get_meta( $activity['id'], '_bp_akismet_user' );

			if ( 'true' == $user_result )
				$desc = sprintf( __( 'Flagged as spam by %s', 'buddypress' ), $who );
			else
				$desc = sprintf( __( 'Un-spammed by %s', 'buddypress' ), $who );
		}

		// add a History item to the hover links, just after Edit
		if ( $akismet_result ) {
			$b = array();
			foreach ( $actions as $k => $item ) {
				$b[ $k ] = $item;
				if ( $k == 'edit' )
lechuck's avatar
lechuck committed
108
					$b['history'] = '<a href="' . esc_url( bp_get_admin_url( 'admin.php?page=bp-activity&amp;action=edit&aid=' . $activity['id'] ) ) . '#bp_activity_history"> '. __( 'History', 'buddypress' ) . '</a>';
lechuck's avatar
lechuck committed
109
110
111
112
113
114
			}

			$actions = $b;
		}

		if ( $desc )
lechuck's avatar
lechuck committed
115
			echo '<span class="akismet-status"><a href="' . esc_url( bp_get_admin_url( 'admin.php?page=bp-activity&amp;action=edit&aid=' . $activity['id'] ) ) . '#bp_activity_history">' . htmlspecialchars( $desc ) . '</a></span>';
lechuck's avatar
lechuck committed
116

ale's avatar
ale committed
117
118
119
120
121
122
123
		/**
		 * Filters the list of actions for the current activity's row.
		 *
		 * @since BuddyPress (1.6.0)
		 *
		 * @param array $actions Array of available actions for the current activity item's row.
		 */
lechuck's avatar
lechuck committed
124
125
126
127
		return apply_filters( 'bp_akismet_comment_row_action', $actions );
	}

	/**
lechuck's avatar
lechuck committed
128
129
130
131
132
	 * Generate nonces for activity forms.
	 *
	 * These nonces appear in the member profile status form, as well as in
	 * the reply form of each activity item. The nonces are, in turn, used
	 * by Akismet to help detect spam activity.
lechuck's avatar
lechuck committed
133
	 *
ale's avatar
ale committed
134
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
135
	 *
ale's avatar
ale committed
136
	 * @see https://plugins.trac.wordpress.org/ticket/1232
lechuck's avatar
lechuck committed
137
138
	 */
	public function add_activity_stream_nonce() {
lechuck's avatar
lechuck committed
139
		$form_id = '_bp_as_nonce';
lechuck's avatar
lechuck committed
140
141
142
143
144
145
146
147
148
149
150
151
		$value   = '_bp_as_nonce_' . bp_loggedin_user_id();

		// If we're in the activity stream loop, we can use the current item's ID to make the nonce unique
		if ( 'bp_activity_entry_comments' == current_filter() ) {
			$form_id .= '_' . bp_get_activity_id();
			$value   .= '_' . bp_get_activity_id();
		}

		wp_nonce_field( $value, $form_id, false );
	}

	/**
lechuck's avatar
lechuck committed
152
	 * Clean up the bp_latest_update usermeta in case of spamming.
lechuck's avatar
lechuck committed
153
	 *
lechuck's avatar
lechuck committed
154
155
156
157
158
159
160
161
	 * Run just after an update is posted, this method check to see whether
	 * the newly created update has been marked as spam by Akismet. If so,
	 * the cached update is cleared from the user's 'bp_latest_update'
	 * usermeta, ensuring that it won't appear in the member header and
	 * elsewhere in the theme.
	 *
	 * This can't be done in BP_Akismet::check_activity() due to the
	 * default AJAX implementation; see bp_dtheme_post_update().
lechuck's avatar
lechuck committed
162
	 *
ale's avatar
ale committed
163
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
164
165
166
	 *
	 * @see bp_dtheme_post_update()
	 *
ale's avatar
ale committed
167
168
169
	 * @param string $content     Activity update text.
	 * @param int    $user_id     User ID.
	 * @param int    $activity_id Activity ID.
lechuck's avatar
lechuck committed
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
	 */
	public function check_member_activity_update( $content, $user_id, $activity_id ) {
		// By default, only handle activity updates and activity comments.
		if ( empty( $this->last_activity ) || !in_array( $this->last_activity->type, BP_Akismet::get_activity_types() ) )
			return;

		// Was this $activity_id just marked as spam? If not, bail out.
		if ( !$this->last_activity->id || $activity_id != $this->last_activity->id || 'false' == $this->last_activity->akismet_submission['bp_as_result'] )
			return;

		// It was, so delete the member's latest activity update.
		bp_delete_user_meta( $user_id, 'bp_latest_update' );
	}

	/**
	 * Adds a "mark as spam" button to each activity item for site admins.
	 *
	 * This function is intended to be used inside the activity stream loop.
	 *
ale's avatar
ale committed
189
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
	 */
	public function add_activity_spam_button() {
		if ( !bp_activity_user_can_mark_spam() )
			return;

		// By default, only handle activity updates and activity comments.
		if ( !in_array( bp_get_activity_type(), BP_Akismet::get_activity_types() ) )
			return;

		bp_button(
			array(
				'block_self' => false,
				'component'  => 'activity',
				'id'         => 'activity_make_spam_' . bp_get_activity_id(),
				'link_class' => 'bp-secondary-action spam-activity confirm button item-button',
				'link_href'  => wp_nonce_url( bp_get_root_domain() . '/' . bp_get_activity_slug() . '/spam/' . bp_get_activity_id() . '/', 'bp_activity_akismet_spam_' . bp_get_activity_id() ),
				'link_text'  => __( 'Spam', 'buddypress' ),
				'wrapper'    => false,
			)
		);
	}

	/**
	 * Adds a "mark as spam" button to each activity COMMENT item for site admins.
	 *
	 * This function is intended to be used inside the activity stream loop.
	 *
ale's avatar
ale committed
217
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
	 */
	public function add_activity_comment_spam_button() {
		if ( !bp_activity_user_can_mark_spam() )
			return;

		// By default, only handle activity updates and activity comments.
		$current_comment = bp_activity_current_comment();
		if ( empty( $current_comment ) || !in_array( $current_comment->type, BP_Akismet::get_activity_types() ) )
			return;

		bp_button(
			array(
				'block_self' => false,
				'component'  => 'activity',
				'id'         => 'activity_make_spam_' . bp_get_activity_comment_id(),
				'link_class' => 'bp-secondary-action spam-activity-comment confirm',
				'link_href'  => wp_nonce_url( bp_get_root_domain() . '/' . bp_get_activity_slug() . '/spam/' . bp_get_activity_comment_id() . '/?cid=' . bp_get_activity_comment_id(), 'bp_activity_akismet_spam_' . bp_get_activity_comment_id() ),
				'link_text'  => __( 'Spam', 'buddypress' ),
				'wrapper'    => false,
			)
		);
	}

	/**
lechuck's avatar
lechuck committed
242
	 * Get a filterable list of activity types that Akismet should automatically check for spam.
lechuck's avatar
lechuck committed
243
	 *
ale's avatar
ale committed
244
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
245
	 *
lechuck's avatar
lechuck committed
246
	 * @static
lechuck's avatar
lechuck committed
247
248
	 *
	 * @return array List of activity types.
lechuck's avatar
lechuck committed
249
250
	 */
	public static function get_activity_types() {
ale's avatar
ale committed
251
252
253
254
255
256
257
258

		/**
		 * Filters the list of activity types that Akismet should automatically check for spam.
		 *
		 * @since BuddyPress (1.6.0)
		 *
		 * @param array Array of default activity types for Akismet to check.
		 */
lechuck's avatar
lechuck committed
259
260
261
262
		return apply_filters( 'bp_akismet_get_activity_types', array( 'activity_comment', 'activity_update' ) );
	}

	/**
lechuck's avatar
lechuck committed
263
264
	 * Mark activity item as spam.
	 *
ale's avatar
ale committed
265
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
266
	 *
ale's avatar
ale committed
267
268
269
270
	 * @param BP_Activity_Activity $activity Activity item being spammed.
	 * @param string               $source   Either "by_a_person" (e.g. a person has
	 *                                       manually marked the activity as spam) or
	 *                                       "by_akismet" (automatically spammed).
lechuck's avatar
lechuck committed
271
272
273
274
275
	 */
	public function mark_as_spam( $activity, $source ) {
		// Record this item so we can do some tidyup in BP_Akismet::check_member_activity_update()
		$this->last_activity = $activity;

ale's avatar
ale committed
276
277
278
279
280
281
282
283
284
285
286
		/**
		 * Fires after marking an activity item has been marked as spam.
		 *
		 * @since BuddyPress (1.6.0)
		 *
		 * @param BP_Activity_Activity $activity Activity object being marked as spam.
		 * @param string               $source   Source of the whom marked as spam.
		 *                                       Either "by_a_person" (e.g. a person has
		 *                                       manually marked the activity as spam)
		 *                                       or "by_akismet".
		 */
lechuck's avatar
lechuck committed
287
288
289
290
		do_action( 'bp_activity_akismet_mark_as_spam', $activity, $source );
	}

	/**
lechuck's avatar
lechuck committed
291
292
	 * Mark activity item as ham.
	 *
ale's avatar
ale committed
293
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
294
	 *
ale's avatar
ale committed
295
296
297
298
	 * @param BP_Activity_Activity $activity Activity item being hammed.
	 * @param string               $source   Either "by_a_person" (e.g. a person has
	 *                                       manually marked the activity as ham) or
	 *                                       "by_akismet" (automatically hammed).
lechuck's avatar
lechuck committed
299
300
301
302
303
304
	 */
	public function mark_as_ham( $activity, $source ) {
		// If the activity was, originally, automatically marked as spam by Akismet, run the @mentions filter as it would have been skipped.
		if ( 'true' == bp_activity_get_meta( $activity->id, '_bp_akismet_result' ) && !bp_activity_get_meta( $activity->id, '_bp_akismet_user_result' ) )
			$activity->content = bp_activity_at_name_filter( $activity->content, $activity->id );

ale's avatar
ale committed
305
306
307
308
309
310
311
312
313
314
315
		/**
		 * Fires after marking an activity item has been marked as ham.
		 *
		 * @since BuddyPress (1.6.0)
		 *
		 * @param BP_Activity_Activity $activity Activity object being marked as ham.
		 * @param string               $source   Source of the whom marked as ham.
		 *                                       Either "by_a_person" (e.g. a person has
		 *                                       manually marked the activity as ham) or
		 *                                       "by_akismet" (automatically hammed).
		 */
lechuck's avatar
lechuck committed
316
		do_action( 'bp_activity_akismet_mark_as_ham', $activity, $source );
lechuck's avatar
lechuck committed
317
	}
lechuck's avatar
lechuck committed
318
319

	/**
lechuck's avatar
lechuck committed
320
	 * Build a data package for the Akismet service to inspect.
lechuck's avatar
lechuck committed
321
	 *
ale's avatar
ale committed
322
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
323
324
	 *
	 * @see http://akismet.com/development/api/#comment-check
lechuck's avatar
lechuck committed
325
	 * @static
lechuck's avatar
lechuck committed
326
327
	 *
	 * @param BP_Activity_Activity $activity Activity item data.
ale's avatar
ale committed
328
329
	 *
	 * @return array
lechuck's avatar
lechuck committed
330
331
332
333
334
335
336
337
338
339
340
341
342
	 */
	public static function build_akismet_data_package( $activity ) {
		$userdata = get_userdata( $activity->user_id );

		$activity_data                          = array();
		$activity_data['akismet_comment_nonce'] = 'inactive';
		$activity_data['comment_author']        = $userdata->display_name;
		$activity_data['comment_author_email']  = $userdata->user_email;
		$activity_data['comment_author_url']    = bp_core_get_userlink( $userdata->ID, false, true);
		$activity_data['comment_content']       = $activity->content;
		$activity_data['comment_type']          = $activity->type;
		$activity_data['permalink']             = bp_activity_get_permalink( $activity->id, $activity );
		$activity_data['user_ID']               = $userdata->ID;
ale's avatar
ale committed
343
		$activity_data['user_role']             = Akismet::get_user_roles( $userdata->ID );
lechuck's avatar
lechuck committed
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358

		/**
		 * Get the nonce if the new activity was submitted through the "what's up, Paul?" form.
		 * This helps Akismet ensure that the update was a valid form submission.
		 */
		if ( !empty( $_POST['_bp_as_nonce'] ) )
			$activity_data['akismet_comment_nonce'] = wp_verify_nonce( $_POST['_bp_as_nonce'], "_bp_as_nonce_{$userdata->ID}" ) ? 'passed' : 'failed';

		/**
		 * If the new activity was a reply to an existing item, check the nonce with the activity parent ID.
		 * This helps Akismet ensure that the update was a valid form submission.
		 */
		elseif ( !empty( $activity->secondary_item_id ) && !empty( $_POST['_bp_as_nonce_' . $activity->secondary_item_id] ) )
			$activity_data['akismet_comment_nonce'] = wp_verify_nonce( $_POST["_bp_as_nonce_{$activity->secondary_item_id}"], "_bp_as_nonce_{$userdata->ID}_{$activity->secondary_item_id}" ) ? 'passed' : 'failed';

ale's avatar
ale committed
359
360
361
362
363
364
365
366
		/**
		 * Filters activity data before being sent to Akismet to inspect.
		 *
		 * @since BuddyPress (1.6.0)
		 *
		 * @param array                $activity_data Array of activity data for Akismet to inspect.
		 * @param BP_Activity_Activity $activity      Activity item data.
		 */
lechuck's avatar
lechuck committed
367
368
369
370
		return apply_filters( 'bp_akismet_build_akismet_data_package', $activity_data, $activity );
	}

	/**
lechuck's avatar
lechuck committed
371
	 * Check if the activity item is spam or ham.
lechuck's avatar
lechuck committed
372
	 *
ale's avatar
ale committed
373
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
374
375
	 *
	 * @see http://akismet.com/development/api/
lechuck's avatar
lechuck committed
376
377
	 * @todo Spam counter?
	 * @todo Auto-delete old spam?
lechuck's avatar
lechuck committed
378
379
	 *
	 * @param BP_Activity_Activity $activity The activity item to check.
lechuck's avatar
lechuck committed
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
	 */
	public function check_activity( $activity ) {
		// By default, only handle activity updates and activity comments.
		if ( !in_array( $activity->type, BP_Akismet::get_activity_types() ) )
			return;

		// Make sure last_activity is clear to avoid any confusion
		$this->last_activity = null;

		// Build data package for Akismet
		$activity_data = BP_Akismet::build_akismet_data_package( $activity );

		// Check with Akismet to see if this is spam
		$activity_data = $this->send_akismet_request( $activity_data, 'check', 'spam' );

		// Record this item
		$this->last_activity = $activity;

		// Store a copy of the data that was submitted to Akismet
		$this->last_activity->akismet_submission = $activity_data;

		// Spam
		if ( 'true' == $activity_data['bp_as_result'] ) {
ale's avatar
ale committed
403
404
405
406
407
408
409
410
411
			/**
			 * Fires after an activity item has been proven to be spam, but before officially being marked as spam.
			 *
			 * @since BuddyPress (1.6.0)
			 *
			 * @param BP_Activity_Activity $activity      The activity item proven to be spam.
			 * @param array                $activity_data Array of activity data for item including
			 *                                            Akismet check results data.
			 */
lechuck's avatar
lechuck committed
412
413
414
415
416
417
418
419
420
421
422
			do_action_ref_array( 'bp_activity_akismet_spam_caught', array( &$activity, $activity_data ) );

			// Mark as spam
			bp_activity_mark_as_spam( $activity, 'by_akismet' );
		}

		// Update activity meta after a spam check
		add_action( 'bp_activity_after_save', array( $this, 'update_activity_akismet_meta' ), 1, 1 );
	}

	/**
lechuck's avatar
lechuck committed
423
	 * Update activity meta after a manual spam change (user-initiated).
lechuck's avatar
lechuck committed
424
	 *
ale's avatar
ale committed
425
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
426
427
	 *
	 * @param BP_Activity_Activity $activity The activity to check.
lechuck's avatar
lechuck committed
428
429
430
431
432
433
434
435
436
437
438
439
	 */
	public function update_activity_spam_meta( $activity ) {
		// By default, only handle activity updates and activity comments.
		if ( !in_array( $activity->type, BP_Akismet::get_activity_types() ) )
			return;

		$this->update_activity_history( $activity->id, sprintf( __( '%s reported this activity as spam', 'buddypress' ), bp_get_loggedin_user_username() ), 'report-spam' );
		bp_activity_update_meta( $activity->id, '_bp_akismet_user_result', 'true' );
		bp_activity_update_meta( $activity->id, '_bp_akismet_user', bp_get_loggedin_user_username() );
	}

	/**
lechuck's avatar
lechuck committed
440
	 * Update activity meta after a manual ham change (user-initiated).
lechuck's avatar
lechuck committed
441
	 *
ale's avatar
ale committed
442
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
443
444
	 *
	 * @param BP_Activity_Activity $activity The activity to check.
lechuck's avatar
lechuck committed
445
446
447
448
449
450
451
452
453
454
455
456
	 */
	public function update_activity_ham_meta( $activity ) {
		// By default, only handle activity updates and activity comments.
		if ( !in_array( $activity->type, BP_Akismet::get_activity_types() ) )
			return;

		$this->update_activity_history( $activity->id, sprintf( __( '%s reported this activity as not spam', 'buddypress' ), bp_get_loggedin_user_username() ), 'report-ham' );
		bp_activity_update_meta( $activity->id, '_bp_akismet_user_result', 'false' );
		bp_activity_update_meta( $activity->id, '_bp_akismet_user', bp_get_loggedin_user_username() );
	}

	/**
lechuck's avatar
lechuck committed
457
	 * Update activity meta after an automatic spam check (not user-initiated).
lechuck's avatar
lechuck committed
458
	 *
ale's avatar
ale committed
459
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
460
461
	 *
	 * @param BP_Activity_Activity $activity The activity to check.
lechuck's avatar
lechuck committed
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
	 */
	public function update_activity_akismet_meta( $activity ) {
		// Check we're dealing with what was last updated by Akismet
		if ( empty( $this->last_activity ) || !empty( $this->last_activity ) && $activity->id != $this->last_activity->id )
			return;

		// By default, only handle activity updates and activity comments.
		if ( !in_array( $this->last_activity->type, BP_Akismet::get_activity_types() ) )
			return;

		// Spam
		if ( 'true' == $this->last_activity->akismet_submission['bp_as_result'] ) {
			bp_activity_update_meta( $activity->id, '_bp_akismet_result', 'true' );
			$this->update_activity_history( $activity->id, __( 'Akismet caught this item as spam', 'buddypress' ), 'check-spam' );

		// Not spam
		} elseif ( 'false' == $this->last_activity->akismet_submission['bp_as_result'] ) {
			bp_activity_update_meta( $activity->id, '_bp_akismet_result', 'false' );
			$this->update_activity_history( $activity->id, __( 'Akismet cleared this item', 'buddypress' ), 'check-ham' );

		// Uh oh, something's gone horribly wrong. Unexpected result.
		} else {
			bp_activity_update_meta( $activity->id, '_bp_akismet_error', bp_core_current_time() );
			$this->update_activity_history( $activity->id, sprintf( __( 'Akismet was unable to check this item (response: %s), will automatically retry again later.', 'buddypress' ), $this->last_activity->akismet_submission['bp_as_result'] ), 'check-error' );
		}

		// Record the original data which was submitted to Akismet for checking
		bp_activity_update_meta( $activity->id, '_bp_akismet_submission', $this->last_activity->akismet_submission );
	}

	/**
lechuck's avatar
lechuck committed
493
494
	 * Contact Akismet to check if this is spam or ham.
	 *
ale's avatar
ale committed
495
	 * Props to WordPress core Akismet plugin for a lot of this.
lechuck's avatar
lechuck committed
496
	 *
ale's avatar
ale committed
497
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
498
	 *
ale's avatar
ale committed
499
500
501
	 * @param array  $activity_data Packet of information to submit to Akismet.
	 * @param string $check         "check" or "submit".
	 * @param string $spam          "spam" or "ham".
lechuck's avatar
lechuck committed
502
503
	 *
	 * @return array $activity_data Activity data, with Akismet data added.
lechuck's avatar
lechuck committed
504
505
	 */
	public function send_akismet_request( $activity_data, $check = 'check', $spam = 'spam' ) {
ale's avatar
ale committed
506
		$query_string = $path = '';
lechuck's avatar
lechuck committed
507
508
509
510
511
512
513
514

		$activity_data['blog']         = bp_get_option( 'home' );
		$activity_data['blog_charset'] = bp_get_option( 'blog_charset' );
		$activity_data['blog_lang']    = get_locale();
		$activity_data['referrer']     = $_SERVER['HTTP_REFERER'];
		$activity_data['user_agent']   = bp_core_current_user_ua();
		$activity_data['user_ip']      = bp_core_current_user_ip();

ale's avatar
ale committed
515
		if ( Akismet::is_test_mode() )
lechuck's avatar
lechuck committed
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
			$activity_data['is_test'] = 'true';

		// Loop through _POST args and rekey strings
		foreach ( $_POST as $key => $value )
			if ( is_string( $value ) && 'cookie' != $key )
				$activity_data['POST_' . $key] = $value;

		// Keys to ignore
		$ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' );

		// Loop through _SERVER args and remove whitelisted keys
		foreach ( $_SERVER as $key => $value ) {

			// Key should not be ignored
			if ( !in_array( $key, $ignore ) && is_string( $value ) ) {
				$activity_data[$key] = $value;

			// Key should be ignored
			} else {
				$activity_data[$key] = '';
			}
		}

		foreach ( $activity_data as $key => $data )
			$query_string .= $key . '=' . urlencode( stripslashes( $data ) ) . '&';

		if ( 'check' == $check )
ale's avatar
ale committed
543
			$path = 'comment-check';
lechuck's avatar
lechuck committed
544
		elseif ( 'submit' == $check )
ale's avatar
ale committed
545
			$path = 'submit-' . $spam;
lechuck's avatar
lechuck committed
546
547
548

		// Send to Akismet
		add_filter( 'akismet_ua', array( $this, 'buddypress_ua' ) );
ale's avatar
ale committed
549
		$response = Akismet::http_post( $query_string, $path );
lechuck's avatar
lechuck committed
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
		remove_filter( 'akismet_ua', array( $this, 'buddypress_ua' ) );

		// Get the response
		if ( ! empty( $response[1] ) && ! is_wp_error( $response[1] ) )
			$activity_data['bp_as_result'] = $response[1];
		else
			$activity_data['bp_as_result'] = false;

		// Perform a daily tidy up
		if ( ! wp_next_scheduled( 'bp_activity_akismet_delete_old_metadata' ) )
			wp_schedule_event( time(), 'daily', 'bp_activity_akismet_delete_old_metadata' );

		return $activity_data;
	}

	/**
lechuck's avatar
lechuck committed
566
	 * Filters user agent when sending to Akismet to add BuddyPress info.
lechuck's avatar
lechuck committed
567
	 *
ale's avatar
ale committed
568
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
569
570
	 *
	 * @param string $user_agent User agent string, as generated by Akismet.
ale's avatar
ale committed
571
	 *
lechuck's avatar
lechuck committed
572
	 * @return string $user_agent Modified user agent string.
lechuck's avatar
lechuck committed
573
574
575
576
577
578
579
580
581
	 */
	public function buddypress_ua( $user_agent ) {
		$user_agent = 'BuddyPress/' . bp_get_version() . ' | Akismet/'. constant( 'AKISMET_VERSION' );
		return $user_agent;
	}

	/**
	 * Adds a "History" meta box to the activity edit screen.
	 *
ale's avatar
ale committed
582
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
583
584
	 *
	 * @param string $screen_action The type of screen that has been requested.
lechuck's avatar
lechuck committed
585
586
587
588
589
590
591
592
593
594
595
	 */
	function add_history_metabox( $screen_action ) {
		// Only proceed if we're on the edit screen
		if ( 'edit' != $screen_action )
			return;

		// Display meta box with a low priority (low position on screen by default)
		add_meta_box( 'bp_activity_history',  __( 'Activity History', 'buddypress' ), array( $this, 'history_metabox' ), get_current_screen()->id, 'normal', 'low' );
	}

	/**
lechuck's avatar
lechuck committed
596
	 * History meta box for the Activity admin edit screen.
lechuck's avatar
lechuck committed
597
	 *
ale's avatar
ale committed
598
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
599
	 *
ale's avatar
ale committed
600
	 * @see https://buddypress.trac.wordpress.org/ticket/3907
lechuck's avatar
lechuck committed
601
602
603
	 * @todo Update activity meta to allow >1 record with the same key (iterate through $history).
	 *
	 * @param object $item Activity item.
lechuck's avatar
lechuck committed
604
605
606
607
608
609
610
611
612
613
614
615
616
	 */
	function history_metabox( $item ) {
		$history = BP_Akismet::get_activity_history( $item->id );

		if ( empty( $history ) )
			return;

		echo '<div class="akismet-history"><div>';
		printf( _x( '<span>%1$s</span> &mdash; %2$s', 'x hours ago - akismet cleared this item', 'buddypress' ), bp_core_time_since( $history[2] ), esc_html( $history[1] ) );
		echo '</div></div>';
	}

	/**
lechuck's avatar
lechuck committed
617
	 * Update an activity item's Akismet history.
lechuck's avatar
lechuck committed
618
	 *
ale's avatar
ale committed
619
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
620
	 *
ale's avatar
ale committed
621
622
623
	 * @param int    $activity_id Activity item ID.
	 * @param string $message     Human-readable description of what's changed.
	 * @param string $event       The type of check we were carrying out.
lechuck's avatar
lechuck committed
624
625
626
627
628
	 */
	public function update_activity_history( $activity_id = 0, $message = '', $event = '' ) {
		$event = array(
			'event'   => $event,
			'message' => $message,
ale's avatar
ale committed
629
			'time'    => Akismet::_get_microtime(),
lechuck's avatar
lechuck committed
630
631
632
633
634
635
636
637
			'user'    => bp_loggedin_user_id(),
		);

		// Save the history data
		bp_activity_update_meta( $activity_id, '_bp_akismet_history', $event );
	}

	/**
lechuck's avatar
lechuck committed
638
	 * Get an activity item's Akismet history.
lechuck's avatar
lechuck committed
639
	 *
ale's avatar
ale committed
640
	 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
641
642
	 *
	 * @param int $activity_id Activity item ID.
ale's avatar
ale committed
643
	 *
lechuck's avatar
lechuck committed
644
	 * @return array The activity item's Akismet history.
lechuck's avatar
lechuck committed
645
646
647
648
649
650
651
652
653
654
655
656
657
658
	 */
	public function get_activity_history( $activity_id = 0 ) {
		$history = bp_activity_get_meta( $activity_id, '_bp_akismet_history' );
		if ( $history === false )
			$history = array();

		// Sort it by the time recorded
		usort( $history, 'akismet_cmp_time' );

		return $history;
	}
}

/**
ale's avatar
ale committed
659
 * Delete old spam activity meta data.
lechuck's avatar
lechuck committed
660
661
662
 *
 * This is done as a clean-up mechanism, as _bp_akismet_submission meta can
 * grow to be quite large.
lechuck's avatar
lechuck committed
663
 *
ale's avatar
ale committed
664
 * @since BuddyPress (1.6.0)
lechuck's avatar
lechuck committed
665
666
 *
 * @global wpdb $wpdb WordPress database object.
lechuck's avatar
lechuck committed
667
668
 */
function bp_activity_akismet_delete_old_metadata() {
ale's avatar
ale committed
669
	global $wpdb;
lechuck's avatar
lechuck committed
670

ale's avatar
ale committed
671
672
673
674
675
676
677
678
679
	$bp = buddypress();

	/**
	 * Filters the threshold for how many days old Akismet metadata needs to be before being automatically deleted.
	 *
	 * @since BuddyPress (1.6.0)
	 *
	 * @param integer 15 How many days old metadata needs to be.
	 */
lechuck's avatar
lechuck committed
680
681
682
683
684
	$interval = apply_filters( 'bp_activity_akismet_delete_meta_interval', 15 );

	// Enforce a minimum of 1 day
	$interval = max( 1, absint( $interval ) );

lechuck's avatar
lechuck committed
685
	// _bp_akismet_submission meta values are large, so expire them after $interval days regardless of the activity status
lechuck's avatar
lechuck committed
686
687
688
689
690
691
692
693
	$sql          = $wpdb->prepare( "SELECT a.id FROM {$bp->activity->table_name} a LEFT JOIN {$bp->activity->table_name_meta} m ON a.id = m.activity_id WHERE m.meta_key = %s AND DATE_SUB(%s, INTERVAL {$interval} DAY) > a.date_recorded LIMIT 10000", '_bp_akismet_submission', current_time( 'mysql', 1 ) );
	$activity_ids = $wpdb->get_col( $sql );

	if ( ! empty( $activity_ids ) ) {
		foreach ( $activity_ids as $activity_id )
			bp_activity_delete_meta( $activity_id, '_bp_akismet_submission' );
	}
}
lechuck's avatar
lechuck committed
694
add_action( 'bp_activity_akismet_delete_old_metadata', 'bp_activity_akismet_delete_old_metadata' );