Akismet Anti-Spam - Version 5.0

Version Description

Release Date - 26 July 2022

  • Added a new feature to catch spammers by observing how they interact with the page.
Download this release

Release Info

Developer cfinke
Plugin Icon 128x128 Akismet Anti-Spam
Version 5.0
Comparing to
See all releases

Code changes from version 4.2.5 to 5.0

Files changed (5) hide show
  1. .htaccess +1 -1
  2. _inc/akismet-frontend.js +332 -0
  3. akismet.php +2 -2
  4. class.akismet.php +36 -6
  5. readme.txt +6 -1
.htaccess CHANGED
@@ -12,7 +12,7 @@
12
  </IfModule>
13
 
14
  # Akismet CSS and JS
15
- <FilesMatch "^(form\.js|akismet\.js|akismet\.css)$">
16
  <IfModule !mod_authz_core.c>
17
  Allow from all
18
  </IfModule>
12
  </IfModule>
13
 
14
  # Akismet CSS and JS
15
+ <FilesMatch "^(form\.js|akismet\.js|akismet-frontend\.js|akismet\.css)$">
16
  <IfModule !mod_authz_core.c>
17
  Allow from all
18
  </IfModule>
_inc/akismet-frontend.js ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Observe how the user enters content into the comment form in order to determine whether it's a bot or not.
3
+ *
4
+ * Note that no actual input is being saved here, only counts and timings between events.
5
+ */
6
+
7
+ ( function() {
8
+ function init() {
9
+ var input_begin = '';
10
+
11
+ var keydowns = {};
12
+ var lastKeyup = null;
13
+ var lastKeydown = null;
14
+ var keypresses = [];
15
+
16
+ var modifierKeys = [];
17
+ var correctionKeys = [];
18
+
19
+ var lastMouseup = null;
20
+ var lastMousedown = null;
21
+ var mouseclicks = [];
22
+
23
+ var mousemoveTimer = null;
24
+ var lastMousemoveX = null;
25
+ var lastMousemoveY = null;
26
+ var mousemoveStart = null;
27
+ var mousemoves = [];
28
+
29
+ var touchmoveCountTimer = null;
30
+ var touchmoveCount = 0;
31
+
32
+ var lastTouchEnd = null;
33
+ var lastTouchStart = null;
34
+ var touchEvents = [];
35
+
36
+ var scrollCountTimer = null;
37
+ var scrollCount = 0;
38
+
39
+ var correctionKeyCodes = [ 'Backspace', 'Delete', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown' ];
40
+ var modifierKeyCodes = [ 'Shift', 'CapsLock' ];
41
+
42
+ var forms = document.querySelectorAll( 'form[method=post]' );
43
+
44
+ for ( var i = 0; i < forms.length; i++ ) {
45
+ var form = forms[i];
46
+
47
+ form.addEventListener( 'submit', function () {
48
+ var ak_bkp = prepare_timestamp_array_for_request( keypresses );
49
+ var ak_bmc = prepare_timestamp_array_for_request( mouseclicks );
50
+ var ak_bte = prepare_timestamp_array_for_request( touchEvents );
51
+ var ak_bmm = prepare_timestamp_array_for_request( mousemoves );
52
+
53
+ var input_fields = {
54
+ // When did the user begin entering any input?
55
+ 'ak_bib': input_begin,
56
+
57
+ // When was the form submitted?
58
+ 'ak_bfs': Date.now(),
59
+
60
+ // How many keypresses did they make?
61
+ 'ak_bkpc': keypresses.length,
62
+
63
+ // How quickly did they press a sample of keys, and how long between them?
64
+ 'ak_bkp': ak_bkp,
65
+
66
+ // How quickly did they click the mouse, and how long between clicks?
67
+ 'ak_bmc': ak_bmc,
68
+
69
+ // How many mouseclicks did they make?
70
+ 'ak_bmcc': mouseclicks.length,
71
+
72
+ // When did they press modifier keys (like Shift or Capslock)?
73
+ 'ak_bmk': modifierKeys.join( ';' ),
74
+
75
+ // When did they correct themselves? e.g., press Backspace, or use the arrow keys to move the cursor back
76
+ 'ak_bck': correctionKeys.join( ';' ),
77
+
78
+ // How many times did they move the mouse?
79
+ 'ak_bmmc': mousemoves.length,
80
+
81
+ // How many times did they move around using a touchscreen?
82
+ 'ak_btmc': touchmoveCount,
83
+
84
+ // How many times did they scroll?
85
+ 'ak_bsc': scrollCount,
86
+
87
+ // How quickly did they perform touch events, and how long between them?
88
+ 'ak_bte': ak_bte,
89
+
90
+ // How many touch events were there?
91
+ 'ak_btec' : touchEvents.length,
92
+
93
+ // How quickly did they move the mouse, and how long between moves?
94
+ 'ak_bmm' : ak_bmm
95
+ };
96
+
97
+ for ( var field_name in input_fields ) {
98
+ var field = document.createElement( 'input' );
99
+ field.setAttribute( 'type', 'hidden' );
100
+ field.setAttribute( 'name', field_name );
101
+ field.setAttribute( 'value', input_fields[ field_name ] );
102
+ this.appendChild( field );
103
+ }
104
+ } );
105
+
106
+ form.addEventListener( 'keydown', function ( e ) {
107
+ // If you hold a key down, some browsers send multiple keydown events in a row.
108
+ // Ignore any keydown events for a key that hasn't come back up yet.
109
+ if ( e.key in keydowns ) {
110
+ return;
111
+ }
112
+
113
+ var keydownTime = ( new Date() ).getTime();
114
+ keydowns[ e.key ] = [ keydownTime ];
115
+
116
+ if ( ! input_begin ) {
117
+ input_begin = keydownTime;
118
+ }
119
+
120
+ // In some situations, we don't want to record an interval since the last keypress -- for example,
121
+ // on the first keypress, or on a keypress after focus has changed to another element. Normally,
122
+ // we want to record the time between the last keyup and this keydown. But if they press a
123
+ // key while already pressing a key, we want to record the time between the two keydowns.
124
+
125
+ var lastKeyEvent = Math.max( lastKeydown, lastKeyup );
126
+
127
+ if ( lastKeyEvent ) {
128
+ keydowns[ e.key ].push( keydownTime - lastKeyEvent );
129
+ }
130
+
131
+ lastKeydown = keydownTime;
132
+ } );
133
+
134
+ form.addEventListener( 'keyup', function ( e ) {
135
+ if ( ! ( e.key in keydowns ) ) {
136
+ // This key was pressed before this script was loaded, or a mouseclick happened during the keypress, or...
137
+ return;
138
+ }
139
+
140
+ var keyupTime = ( new Date() ).getTime();
141
+
142
+ if ( 'TEXTAREA' === e.target.nodeName || 'INPUT' === e.target.nodeName ) {
143
+ if ( -1 !== modifierKeyCodes.indexOf( e.key ) ) {
144
+ modifierKeys.push( keypresses.length - 1 );
145
+ } else if ( -1 !== correctionKeyCodes.indexOf( e.key ) ) {
146
+ correctionKeys.push( keypresses.length - 1 );
147
+ } else {
148
+ // ^ Don't record timings for keys like Shift or backspace, since they
149
+ // typically get held down for longer than regular typing.
150
+
151
+ var keydownTime = keydowns[ e.key ][0];
152
+
153
+ var keypress = [];
154
+
155
+ // Keypress duration.
156
+ keypress.push( keyupTime - keydownTime );
157
+
158
+ // Amount of time between this keypress and the previous keypress.
159
+ if ( keydowns[ e.key ].length > 1 ) {
160
+ keypress.push( keydowns[ e.key ][1] );
161
+ }
162
+
163
+ keypresses.push( keypress );
164
+ }
165
+ }
166
+
167
+ delete keydowns[ e.key ];
168
+
169
+ lastKeyup = keyupTime;
170
+ } );
171
+
172
+ form.addEventListener( "focusin", function ( e ) {
173
+ lastKeydown = null;
174
+ lastKeyup = null;
175
+ keydowns = {};
176
+ } );
177
+
178
+ form.addEventListener( "focusout", function ( e ) {
179
+ lastKeydown = null;
180
+ lastKeyup = null;
181
+ keydowns = {};
182
+ } );
183
+ }
184
+
185
+ document.addEventListener( 'mousedown', function ( e ) {
186
+ lastMousedown = ( new Date() ).getTime();
187
+ } );
188
+
189
+ document.addEventListener( 'mouseup', function ( e ) {
190
+ if ( ! lastMousedown ) {
191
+ // If the mousedown happened before this script was loaded, but the mouseup happened after...
192
+ return;
193
+ }
194
+
195
+ var now = ( new Date() ).getTime();
196
+
197
+ var mouseclick = [];
198
+ mouseclick.push( now - lastMousedown );
199
+
200
+ if ( lastMouseup ) {
201
+ mouseclick.push( lastMousedown - lastMouseup );
202
+ }
203
+
204
+ mouseclicks.push( mouseclick );
205
+
206
+ lastMouseup = now;
207
+
208
+ // If the mouse has been clicked, don't record this time as an interval between keypresses.
209
+ lastKeydown = null;
210
+ lastKeyup = null;
211
+ keydowns = {};
212
+ } );
213
+
214
+ document.addEventListener( 'mousemove', function ( e ) {
215
+ if ( mousemoveTimer ) {
216
+ clearTimeout( mousemoveTimer );
217
+ mousemoveTimer = null;
218
+ }
219
+ else {
220
+ mousemoveStart = ( new Date() ).getTime();
221
+ lastMousemoveX = e.offsetX;
222
+ lastMousemoveY = e.offsetY;
223
+ }
224
+
225
+ mousemoveTimer = setTimeout( function ( theEvent, originalMousemoveStart ) {
226
+ var now = ( new Date() ).getTime() - 250; // To account for the timer delay.
227
+
228
+ var mousemove = [];
229
+ mousemove.push( now - originalMousemoveStart );
230
+ mousemove.push(
231
+ Math.round(
232
+ Math.sqrt(
233
+ Math.pow( theEvent.offsetX - lastMousemoveX, 2 ) +
234
+ Math.pow( theEvent.offsetY - lastMousemoveY, 2 )
235
+ )
236
+ )
237
+ );
238
+
239
+ if ( mousemove[1] > 0 ) {
240
+ // If there was no measurable distance, then it wasn't really a move.
241
+ mousemoves.push( mousemove );
242
+ }
243
+
244
+ mousemoveStart = null;
245
+ mousemoveTimer = null;
246
+ }, 250, e, mousemoveStart );
247
+ } );
248
+
249
+ document.addEventListener( 'touchmove', function ( e ) {
250
+ if ( touchmoveCountTimer ) {
251
+ clearTimeout( touchmoveCountTimer );
252
+ }
253
+
254
+ touchmoveCountTimer = setTimeout( function () {
255
+ touchmoveCount++;
256
+ }, 250 );
257
+ } );
258
+
259
+ document.addEventListener( 'touchstart', function ( e ) {
260
+ lastTouchStart = ( new Date() ).getTime();
261
+ } );
262
+
263
+ document.addEventListener( 'touchend', function ( e ) {
264
+ if ( ! lastTouchStart ) {
265
+ // If the touchstart happened before this script was loaded, but the touchend happened after...
266
+ return;
267
+ }
268
+
269
+ var now = ( new Date() ).getTime();
270
+
271
+ var touchEvent = [];
272
+ touchEvent.push( now - lastTouchStart );
273
+
274
+ if ( lastTouchEnd ) {
275
+ touchEvent.push( lastTouchStart - lastTouchEnd );
276
+ }
277
+
278
+ touchEvents.push( touchEvent );
279
+
280
+ lastTouchEnd = now;
281
+
282
+ // Don't record this time as an interval between keypresses.
283
+ lastKeydown = null;
284
+ lastKeyup = null;
285
+ keydowns = {};
286
+ } );
287
+
288
+ document.addEventListener( 'scroll', function ( e ) {
289
+ if ( scrollCountTimer ) {
290
+ clearTimeout( scrollCountTimer );
291
+ }
292
+
293
+ scrollCountTimer = setTimeout( function () {
294
+ scrollCount++;
295
+ }, 250 );
296
+ } );
297
+ }
298
+
299
+ /**
300
+ * For the timestamp data that is collected, don't send more than `limit` data points in the request.
301
+ * Choose a random slice and send those.
302
+ */
303
+ function prepare_timestamp_array_for_request( a, limit ) {
304
+ if ( ! limit ) {
305
+ limit = 100;
306
+ }
307
+
308
+ var rv = '';
309
+
310
+ if ( a.length > 0 ) {
311
+ var random_starting_point = Math.max( 0, Math.floor( Math.random() * a.length - limit ) );
312
+
313
+ for ( var i = 0; i < limit && i < a.length; i++ ) {
314
+ rv += a[ random_starting_point + i ][0];
315
+
316
+ if ( a[ random_starting_point + i ].length >= 2 ) {
317
+ rv += "," + a[ random_starting_point + i ][1];
318
+ }
319
+
320
+ rv += ";";
321
+ }
322
+ }
323
+
324
+ return rv;
325
+ }
326
+
327
+ if ( document.readyState !== 'loading' ) {
328
+ init();
329
+ } else {
330
+ document.addEventListener( 'DOMContentLoaded', init );
331
+ }
332
+ })();
akismet.php CHANGED
@@ -6,7 +6,7 @@
6
  Plugin Name: Akismet Anti-Spam
7
  Plugin URI: https://akismet.com/
8
  Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. It keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
9
- Version: 4.2.5
10
  Author: Automattic
11
  Author URI: https://automattic.com/wordpress-plugins/
12
  License: GPLv2 or later
@@ -37,7 +37,7 @@ if ( !function_exists( 'add_action' ) ) {
37
  exit;
38
  }
39
 
40
- define( 'AKISMET_VERSION', '4.2.5' );
41
  define( 'AKISMET__MINIMUM_WP_VERSION', '5.0' );
42
  define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
43
  define( 'AKISMET_DELETE_LIMIT', 10000 );
6
  Plugin Name: Akismet Anti-Spam
7
  Plugin URI: https://akismet.com/
8
  Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. It keeps your site protected even while you sleep. To get started: activate the Akismet plugin and then go to your Akismet Settings page to set up your API key.
9
+ Version: 5.0
10
  Author: Automattic
11
  Author URI: https://automattic.com/wordpress-plugins/
12
  License: GPLv2 or later
37
  exit;
38
  }
39
 
40
+ define( 'AKISMET_VERSION', '5.0' );
41
  define( 'AKISMET__MINIMUM_WP_VERSION', '5.0' );
42
  define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
43
  define( 'AKISMET_DELETE_LIMIT', 10000 );
class.akismet.php CHANGED
@@ -35,6 +35,9 @@ class Akismet {
35
  add_filter( 'preprocess_comment', array( 'Akismet', 'auto_check_comment' ), 1 );
36
  add_filter( 'rest_pre_insert_comment', array( 'Akismet', 'rest_auto_check_comment' ), 1 );
37
 
 
 
 
38
  add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments' ) );
39
  add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments_meta' ) );
40
  add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_orphaned_commentmeta' ) );
@@ -42,6 +45,7 @@ class Akismet {
42
 
43
  add_action( 'comment_form', array( 'Akismet', 'add_comment_nonce' ), 1 );
44
  add_action( 'comment_form', array( 'Akismet', 'output_custom_form_fields' ) );
 
45
 
46
  add_filter( 'comment_moderation_recipients', array( 'Akismet', 'disable_moderation_emails_if_unreachable' ), 1000, 2 );
47
  add_filter( 'pre_comment_approved', array( 'Akismet', 'last_comment_status' ), 10, 2 );
@@ -1344,13 +1348,17 @@ class Akismet {
1344
  }
1345
  }
1346
 
1347
- public static function load_form_js() {
1348
- /* deprecated */
1349
- }
1350
-
 
1351
  public static function set_form_js_async( $tag, $handle, $src ) {
1352
- /* deprecated */
1353
- return $tag;
 
 
 
1354
  }
1355
 
1356
  public static function get_akismet_form_fields() {
@@ -1739,4 +1747,26 @@ p {
1739
  ) . '</p>'
1740
  );
1741
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1742
  }
35
  add_filter( 'preprocess_comment', array( 'Akismet', 'auto_check_comment' ), 1 );
36
  add_filter( 'rest_pre_insert_comment', array( 'Akismet', 'rest_auto_check_comment' ), 1 );
37
 
38
+ add_action( 'comment_form', array( 'Akismet', 'load_form_js' ) );
39
+ add_action( 'do_shortcode_tag', array( 'Akismet', 'load_form_js_via_filter' ), 10, 4 );
40
+
41
  add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments' ) );
42
  add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments_meta' ) );
43
  add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_orphaned_commentmeta' ) );
45
 
46
  add_action( 'comment_form', array( 'Akismet', 'add_comment_nonce' ), 1 );
47
  add_action( 'comment_form', array( 'Akismet', 'output_custom_form_fields' ) );
48
+ add_filter( 'script_loader_tag', array( 'Akismet', 'set_form_js_async' ), 10, 3 );
49
 
50
  add_filter( 'comment_moderation_recipients', array( 'Akismet', 'disable_moderation_emails_if_unreachable' ), 1000, 2 );
51
  add_filter( 'pre_comment_approved', array( 'Akismet', 'last_comment_status' ), 10, 2 );
1348
  }
1349
  }
1350
 
1351
+ /**
1352
+ * Mark akismet-frontend.js as deferred. Because nothing depends on it, it can run at any time
1353
+ * after it's loaded, and the browser won't have to wait for it to load to continue
1354
+ * parsing the rest of the page.
1355
+ */
1356
  public static function set_form_js_async( $tag, $handle, $src ) {
1357
+ if ( 'akismet-frontend' !== $handle ) {
1358
+ return $tag;
1359
+ }
1360
+
1361
+ return preg_replace( '/^<script /i', '<script defer ', $tag );
1362
  }
1363
 
1364
  public static function get_akismet_form_fields() {
1747
  ) . '</p>'
1748
  );
1749
  }
1750
+
1751
+ public static function load_form_js() {
1752
+ if (
1753
+ ! is_admin()
1754
+ && ( ! function_exists( 'amp_is_request' ) || ! amp_is_request() )
1755
+ && self::get_api_key()
1756
+ ) {
1757
+ wp_register_script( 'akismet-frontend', plugin_dir_url( __FILE__ ) . '_inc/akismet-frontend.js', array(), filemtime( plugin_dir_path( __FILE__ ) . '_inc/akismet-frontend.js' ), true );
1758
+ wp_enqueue_script( 'akismet-frontend' );
1759
+ }
1760
+ }
1761
+
1762
+ /**
1763
+ * Add the form JavaScript when we detect that a supported form shortcode is being parsed.
1764
+ */
1765
+ public static function load_form_js_via_filter( $return_value, $tag, $attr, $m ) {
1766
+ if ( in_array( $tag, array( 'contact-form', 'gravityform', 'contact-form-7', 'formidable', 'fluentform' ) ) ) {
1767
+ self::load_form_js();
1768
+ }
1769
+
1770
+ return $return_value;
1771
+ }
1772
  }
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eo
3
  Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
4
  Requires at least: 5.0
5
  Tested up to: 6.0.1
6
- Stable tag: 4.2.5
7
  License: GPLv2 or later
8
 
9
  The best anti-spam protection to block spam comments and spam in a contact form. The most trusted antispam solution for WordPress and WooCommerce.
@@ -30,6 +30,11 @@ Upload the Akismet plugin to your blog, activate it, and then enter your Akismet
30
 
31
  == Changelog ==
32
 
 
 
 
 
 
33
  = 4.2.5 =
34
  *Release Date - 11 July 2022*
35
 
3
  Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
4
  Requires at least: 5.0
5
  Tested up to: 6.0.1
6
+ Stable tag: 5.0
7
  License: GPLv2 or later
8
 
9
  The best anti-spam protection to block spam comments and spam in a contact form. The most trusted antispam solution for WordPress and WooCommerce.
30
 
31
  == Changelog ==
32
 
33
+ = 5.0 =
34
+ *Release Date - 26 July 2022*
35
+
36
+ * Added a new feature to catch spammers by observing how they interact with the page.
37
+
38
  = 4.2.5 =
39
  *Release Date - 11 July 2022*
40