Akismet Anti-Spam - Version 4.1.10

Version Description

Release Date - 6 July 2021

  • Simplified the code around checking comments in REST API and XML-RPC requests.
  • Updated Plus plan terminology in notices to match current subscription names.
  • Added rel="noopener" to the widget link to avoid warnings in Google Lighthouse.
  • Set the Akismet JavaScript as deferred instead of async to improve responsiveness.
  • Improved the preloading of screenshot popups on the edit comments admin page.
Download this release

Release Info

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

Code changes from version 4.1.9 to 4.1.10

Files changed (6) hide show
  1. _inc/akismet.js +109 -37
  2. akismet.php +2 -2
  3. class.akismet-widget.php +1 -1
  4. class.akismet.php +20 -24
  5. readme.txt +11 -2
  6. views/notice.php +6 -5
_inc/akismet.js CHANGED
@@ -1,10 +1,12 @@
1
  jQuery( function ( $ ) {
2
  var mshotRemovalTimer = null;
3
- var mshotSecondTryTimer = null
4
- var mshotThirdTryTimer = null
5
-
6
  var mshotEnabledLinkSelector = 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a';
7
-
 
 
8
  $('.akismet-status').each(function () {
9
  var thisId = $(this).attr('commentid');
10
  $(this).prependTo('#comment-' + thisId + ' .column-comment');
@@ -99,13 +101,25 @@ jQuery( function ( $ ) {
99
  }
100
  }
101
 
102
- clearTimeout( mshotSecondTryTimer );
103
- clearTimeout( mshotThirdTryTimer );
104
 
105
- var thisHref = $( this ).attr( 'href' );
 
 
 
 
 
 
 
 
106
 
107
- var mShot = $( '<div class="akismet-mshot mshot-container"><div class="mshot-arrow"></div><img src="' + akismet_mshot_url( thisHref ) + '" width="450" height="338" class="mshot-image" /></div>' );
108
  mShot.data( 'link', this );
 
 
 
 
 
109
 
110
  var offset = $( this ).offset();
111
 
@@ -114,37 +128,91 @@ jQuery( function ( $ ) {
114
  top: offset.top + ( $( this ).height() / 2 ) - 101 // 101 = top offset of the arrow plus the top border thickness
115
  } );
116
 
117
- // These retries appear to be superfluous if .mshot-image has already loaded, but it's because mShots
118
- // can return a "Generating thumbnail..." image if it doesn't have a thumbnail ready, so we need
119
- // to retry to see if we can get the newly generated thumbnail.
120
- mshotSecondTryTimer = setTimeout( function () {
121
- mShot.find( '.mshot-image' ).attr( 'src', akismet_mshot_url( thisHref, 2 ) );
122
- }, 6000 );
123
-
124
- mshotThirdTryTimer = setTimeout( function () {
125
- mShot.find( '.mshot-image' ).attr( 'src', akismet_mshot_url( thisHref, 3 ) );
126
- }, 12000 );
127
-
128
  $( 'body' ).append( mShot );
 
 
129
  } ).on( 'mouseout', 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a', function () {
130
  mshotRemovalTimer = setTimeout( function () {
131
- clearTimeout( mshotSecondTryTimer );
132
- clearTimeout( mshotThirdTryTimer );
133
 
134
  $( '.akismet-mshot' ).remove();
135
  }, 200 );
136
- } ).on( 'mouseover', 'tr', function () {
137
- // When the mouse hovers over a comment row, begin preloading mshots for any links in the comment or the comment author.
138
- var linksToPreloadMshotsFor = $( this ).find( mshotEnabledLinkSelector );
139
-
140
- linksToPreloadMshotsFor.each( function () {
141
- // Don't attempt to preload an mshot for a single link twice. Browser caching should cover this, but in case of
142
- // race conditions, save a flag locally when we've begun trying to preload one.
143
- if ( ! $( this ).data( 'akismet-mshot-preloaded' ) ) {
144
- akismet_preload_mshot( $( this ).attr( 'href' ) );
145
- $( this ).data( 'akismet-mshot-preloaded', true );
146
- }
147
- } );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  } );
149
  }
150
 
@@ -273,12 +341,14 @@ jQuery( function ( $ ) {
273
  * @return string The mShot URL;
274
  */
275
  function akismet_mshot_url( linkUrl, retry ) {
276
- var mshotUrl = '//s0.wordpress.com/mshots/v1/' + encodeURIComponent( linkUrl ) + '?w=900';
277
-
278
- if ( retry ) {
279
  mshotUrl += '&r=' + encodeURIComponent( retry );
280
  }
281
-
 
 
282
  return mshotUrl;
283
  }
284
 
@@ -290,6 +360,8 @@ jQuery( function ( $ ) {
290
  function akismet_preload_mshot( linkUrl ) {
291
  var img = new Image();
292
  img.src = akismet_mshot_url( linkUrl );
 
 
293
  }
294
 
295
  $( '.akismet-could-be-primary' ).each( function () {
1
  jQuery( function ( $ ) {
2
  var mshotRemovalTimer = null;
3
+ var mshotRetryTimer = null;
4
+ var mshotTries = 0;
5
+ var mshotRetryInterval = 1000;
6
  var mshotEnabledLinkSelector = 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a';
7
+
8
+ var preloadedMshotURLs = [];
9
+
10
  $('.akismet-status').each(function () {
11
  var thisId = $(this).attr('commentid');
12
  $(this).prependTo('#comment-' + thisId + ' .column-comment');
101
  }
102
  }
103
 
104
+ clearTimeout( mshotRetryTimer );
 
105
 
106
+ var linkUrl = $( this ).attr( 'href' );
107
+
108
+ if ( preloadedMshotURLs.indexOf( linkUrl ) !== -1 ) {
109
+ // This preview image was already preloaded, so begin with a retry URL so the user doesn't see the placeholder image for the first second.
110
+ mshotTries = 2;
111
+ }
112
+ else {
113
+ mshotTries = 1;
114
+ }
115
 
116
+ var mShot = $( '<div class="akismet-mshot mshot-container"><div class="mshot-arrow"></div><img src="' + akismet_mshot_url( linkUrl, mshotTries ) + '" width="450" height="338" class="mshot-image" /></div>' );
117
  mShot.data( 'link', this );
118
+ mShot.data( 'url', linkUrl );
119
+
120
+ mShot.find( 'img' ).on( 'load', function () {
121
+ $( '.akismet-mshot' ).data( 'pending-request', false );
122
+ } );
123
 
124
  var offset = $( this ).offset();
125
 
128
  top: offset.top + ( $( this ).height() / 2 ) - 101 // 101 = top offset of the arrow plus the top border thickness
129
  } );
130
 
 
 
 
 
 
 
 
 
 
 
 
131
  $( 'body' ).append( mShot );
132
+
133
+ mshotRetryTimer = setTimeout( retryMshotUntilLoaded, mshotRetryInterval );
134
  } ).on( 'mouseout', 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a', function () {
135
  mshotRemovalTimer = setTimeout( function () {
136
+ clearTimeout( mshotRetryTimer );
 
137
 
138
  $( '.akismet-mshot' ).remove();
139
  }, 200 );
140
+ } );
141
+
142
+ var preloadDelayTimer = null;
143
+
144
+ $( window ).on( 'scroll resize', function () {
145
+ clearTimeout( preloadDelayTimer );
146
+
147
+ preloadDelayTimer = setTimeout( preloadMshotsInViewport, 500 );
148
+ } );
149
+
150
+ preloadMshotsInViewport();
151
+ }
152
+
153
+ /**
154
+ * The way mShots works is if there was no screenshot already recently generated for the URL,
155
+ * it returns a "loading..." image for the first request. Then, some subsequent request will
156
+ * receive the actual screenshot, but it's unknown how long it will take. So, what we do here
157
+ * is continually re-request the mShot, waiting a second after every response until we get the
158
+ * actual screenshot.
159
+ */
160
+ function retryMshotUntilLoaded() {
161
+ clearTimeout( mshotRetryTimer );
162
+
163
+ var imageWidth = $( '.akismet-mshot img' ).get(0).naturalWidth;
164
+
165
+ if ( imageWidth == 0 ) {
166
+ // It hasn't finished loading yet the first time. Check again shortly.
167
+ setTimeout( retryMshotUntilLoaded, mshotRetryInterval );
168
+ }
169
+ else if ( imageWidth == 400 ) {
170
+ // It loaded the preview image.
171
+
172
+ if ( mshotTries == 20 ) {
173
+ // Give up if we've requested the mShot 20 times already.
174
+ return;
175
+ }
176
+
177
+ if ( ! $( '.akismet-mshot' ).data( 'pending-request' ) ) {
178
+ $( '.akismet-mshot' ).data( 'pending-request', true );
179
+
180
+ mshotTries++;
181
+
182
+ $( '.akismet-mshot .mshot-image' ).attr( 'src', akismet_mshot_url( $( '.akismet-mshot' ).data( 'url' ), mshotTries ) );
183
+ }
184
+
185
+ mshotRetryTimer = setTimeout( retryMshotUntilLoaded, mshotRetryInterval );
186
+ }
187
+ else {
188
+ // All done.
189
+ }
190
+ }
191
+
192
+ function preloadMshotsInViewport() {
193
+ var windowWidth = $( window ).width();
194
+ var windowHeight = $( window ).height();
195
+
196
+ $( '#the-comment-list' ).find( mshotEnabledLinkSelector ).each( function ( index, element ) {
197
+ var linkUrl = $( this ).attr( 'href' );
198
+
199
+ // Don't attempt to preload an mshot for a single link twice.
200
+ if ( preloadedMshotURLs.indexOf( linkUrl ) !== -1 ) {
201
+ // The URL is already preloaded.
202
+ return true;
203
+ }
204
+
205
+ if ( typeof element.getBoundingClientRect !== 'function' ) {
206
+ // The browser is too old. Return false to stop this preloading entirely.
207
+ return false;
208
+ }
209
+
210
+ var rect = element.getBoundingClientRect();
211
+
212
+ if ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= windowHeight && rect.right <= windowWidth ) {
213
+ akismet_preload_mshot( linkUrl );
214
+ $( this ).data( 'akismet-mshot-preloaded', true );
215
+ }
216
  } );
217
  }
218
 
341
  * @return string The mShot URL;
342
  */
343
  function akismet_mshot_url( linkUrl, retry ) {
344
+ var mshotUrl = '//s0.wp.com/mshots/v1/' + encodeURIComponent( linkUrl ) + '?w=900';
345
+
346
+ if ( retry > 1 ) {
347
  mshotUrl += '&r=' + encodeURIComponent( retry );
348
  }
349
+
350
+ mshotUrl += '&source=akismet';
351
+
352
  return mshotUrl;
353
  }
354
 
360
  function akismet_preload_mshot( linkUrl ) {
361
  var img = new Image();
362
  img.src = akismet_mshot_url( linkUrl );
363
+
364
+ preloadedMshotURLs.push( linkUrl );
365
  }
366
 
367
  $( '.akismet-could-be-primary' ).each( function () {
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.1.9
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.1.9' );
41
  define( 'AKISMET__MINIMUM_WP_VERSION', '4.0' );
42
  define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
43
  define( 'AKISMET_DELETE_LIMIT', 100000 );
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.1.10
10
  Author: Automattic
11
  Author URI: https://automattic.com/wordpress-plugins/
12
  License: GPLv2 or later
37
  exit;
38
  }
39
 
40
+ define( 'AKISMET_VERSION', '4.1.10' );
41
  define( 'AKISMET__MINIMUM_WP_VERSION', '4.0' );
42
  define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
43
  define( 'AKISMET_DELETE_LIMIT', 100000 );
class.akismet-widget.php CHANGED
@@ -99,7 +99,7 @@ class Akismet_Widget extends WP_Widget {
99
  ?>
100
 
101
  <div class="a-stats">
102
- <a href="https://akismet.com" target="_blank" title=""><?php printf( _n( '<strong class="count">%1$s spam</strong> blocked by <strong>Akismet</strong>', '<strong class="count">%1$s spam</strong> blocked by <strong>Akismet</strong>', $count , 'akismet'), number_format_i18n( $count ) ); ?></a>
103
  </div>
104
 
105
  <?php
99
  ?>
100
 
101
  <div class="a-stats">
102
+ <a href="https://akismet.com" target="_blank" rel="noopener" title=""><?php printf( _n( '<strong class="count">%1$s spam</strong> blocked by <strong>Akismet</strong>', '<strong class="count">%1$s spam</strong> blocked by <strong>Akismet</strong>', $count , 'akismet'), number_format_i18n( $count ) ); ?></a>
103
  </div>
104
 
105
  <?php
class.akismet.php CHANGED
@@ -11,13 +11,6 @@ class Akismet {
11
  private static $last_comment_result = null;
12
  private static $comment_as_submitted_allowed_keys = array( 'blog' => '', 'blog_charset' => '', 'blog_lang' => '', 'blog_ua' => '', 'comment_agent' => '', 'comment_author' => '', 'comment_author_IP' => '', 'comment_author_email' => '', 'comment_author_url' => '', 'comment_content' => '', 'comment_date_gmt' => '', 'comment_tags' => '', 'comment_type' => '', 'guid' => '', 'is_test' => '', 'permalink' => '', 'reporter' => '', 'site_domain' => '', 'submit_referer' => '', 'submit_uri' => '', 'user_ID' => '', 'user_agent' => '', 'user_id' => '', 'user_ip' => '' );
13
 
14
- /**
15
- * Is the comment check happening in the context of an API call? Of if false, then it's during the POST that happens after filling out a comment form.
16
- *
17
- * @var type bool
18
- */
19
- private static $is_api_call = false;
20
-
21
  public static function init() {
22
  if ( ! self::$initiated ) {
23
  self::init_hooks();
@@ -137,12 +130,18 @@ class Akismet {
137
  }
138
 
139
  public static function rest_auto_check_comment( $commentdata ) {
140
- self::$is_api_call = true;
141
-
142
- return self::auto_check_comment( $commentdata );
143
  }
144
 
145
- public static function auto_check_comment( $commentdata ) {
 
 
 
 
 
 
 
 
146
  // If no key is configured, then there's no point in doing any of this.
147
  if ( ! self::get_api_key() ) {
148
  return $commentdata;
@@ -246,17 +245,19 @@ class Akismet {
246
  update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr );
247
  }
248
 
249
- if ( self::$is_api_call ) {
250
  return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) );
251
- }
252
- else {
 
 
253
  // Redirect back to the previous page, or failing that, the post permalink, or failing that, the homepage of the blog.
254
  $redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ( $post ? get_permalink( $post ) : home_url() );
255
  wp_safe_redirect( esc_url_raw( $redirect_to ) );
256
  die();
257
  }
258
  }
259
- else if ( self::$is_api_call ) {
260
  // The way the REST API structures its calls, we can set the comment_approved value right away.
261
  $commentdata['comment_approved'] = 'spam';
262
  }
@@ -1286,7 +1287,7 @@ class Akismet {
1286
  }
1287
 
1288
  /**
1289
- * Mark form.js as async. Because nothing depends on it, it can run at any time
1290
  * after it's loaded, and the browser won't have to wait for it to load to continue
1291
  * parsing the rest of the page.
1292
  */
@@ -1295,7 +1296,7 @@ class Akismet {
1295
  return $tag;
1296
  }
1297
 
1298
- return preg_replace( '/^<script /i', '<script async="async" ', $tag );
1299
  }
1300
 
1301
  public static function inject_ak_js( $post_id ) {
@@ -1439,8 +1440,6 @@ p {
1439
  if ( !is_object( $wp_xmlrpc_server ) )
1440
  return false;
1441
 
1442
- self::$is_api_call = true;
1443
-
1444
  $is_multicall = false;
1445
  $multicall_count = 0;
1446
 
@@ -1548,12 +1547,9 @@ p {
1548
  'multicall_count' => $multicall_count,
1549
  );
1550
 
1551
- $comment = Akismet::auto_check_comment( $comment );
1552
 
1553
- if (
1554
- is_wp_error( $comment ) // This triggered a 'discard' directive.
1555
- || ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) // It was just a normal spam response.
1556
- ) {
1557
  // Sad: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything.
1558
  $wp_xmlrpc_server->error( new IXR_Error( 0, 'Invalid discovery target' ) );
1559
 
11
  private static $last_comment_result = null;
12
  private static $comment_as_submitted_allowed_keys = array( 'blog' => '', 'blog_charset' => '', 'blog_lang' => '', 'blog_ua' => '', 'comment_agent' => '', 'comment_author' => '', 'comment_author_IP' => '', 'comment_author_email' => '', 'comment_author_url' => '', 'comment_content' => '', 'comment_date_gmt' => '', 'comment_tags' => '', 'comment_type' => '', 'guid' => '', 'is_test' => '', 'permalink' => '', 'reporter' => '', 'site_domain' => '', 'submit_referer' => '', 'submit_uri' => '', 'user_ID' => '', 'user_agent' => '', 'user_id' => '', 'user_ip' => '' );
13
 
 
 
 
 
 
 
 
14
  public static function init() {
15
  if ( ! self::$initiated ) {
16
  self::init_hooks();
130
  }
131
 
132
  public static function rest_auto_check_comment( $commentdata ) {
133
+ return self::auto_check_comment( $commentdata, 'rest_api' );
 
 
134
  }
135
 
136
+ /**
137
+ * Check a comment for spam.
138
+ *
139
+ * @param array $commentdata
140
+ * @param string $context What kind of request triggered this comment check? Possible values are 'default', 'rest_api', and 'xml-rpc'.
141
+ * @return array|WP_Error Either the $commentdata array with additional entries related to its spam status
142
+ * or a WP_Error, if it's a REST API request and the comment should be discarded.
143
+ */
144
+ public static function auto_check_comment( $commentdata, $context = 'default' ) {
145
  // If no key is configured, then there's no point in doing any of this.
146
  if ( ! self::get_api_key() ) {
147
  return $commentdata;
245
  update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr );
246
  }
247
 
248
+ if ( 'rest_api' === $context ) {
249
  return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) );
250
+ } else if ( 'xml-rpc' === $context ) {
251
+ // If this is a pingback that we're pre-checking, the discard behavior is the same as the normal spam response behavior.
252
+ return $commentdata;
253
+ } else {
254
  // Redirect back to the previous page, or failing that, the post permalink, or failing that, the homepage of the blog.
255
  $redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ( $post ? get_permalink( $post ) : home_url() );
256
  wp_safe_redirect( esc_url_raw( $redirect_to ) );
257
  die();
258
  }
259
  }
260
+ else if ( 'rest_api' === $context ) {
261
  // The way the REST API structures its calls, we can set the comment_approved value right away.
262
  $commentdata['comment_approved'] = 'spam';
263
  }
1287
  }
1288
 
1289
  /**
1290
+ * Mark form.js as deferred. Because nothing depends on it, it can run at any time
1291
  * after it's loaded, and the browser won't have to wait for it to load to continue
1292
  * parsing the rest of the page.
1293
  */
1296
  return $tag;
1297
  }
1298
 
1299
+ return preg_replace( '/^<script /i', '<script defer ', $tag );
1300
  }
1301
 
1302
  public static function inject_ak_js( $post_id ) {
1440
  if ( !is_object( $wp_xmlrpc_server ) )
1441
  return false;
1442
 
 
 
1443
  $is_multicall = false;
1444
  $multicall_count = 0;
1445
 
1547
  'multicall_count' => $multicall_count,
1548
  );
1549
 
1550
+ $comment = self::auto_check_comment( $comment, 'xml-rpc' );
1551
 
1552
+ if ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) {
 
 
 
1553
  // Sad: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything.
1554
  $wp_xmlrpc_server->error( new IXR_Error( 0, 'Invalid discovery target' ) );
1555
 
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau
3
  Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
4
  Requires at least: 4.6
5
- Tested up to: 5.7
6
- Stable tag: 4.1.9
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,15 @@ Upload the Akismet plugin to your blog, activate it, and then enter your Akismet
30
 
31
  == Changelog ==
32
 
 
 
 
 
 
 
 
 
 
33
  = 4.1.9 =
34
  *Release Date - 2 March 2021*
35
 
2
  Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau
3
  Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
4
  Requires at least: 4.6
5
+ Tested up to: 5.8
6
+ Stable tag: 4.1.10
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
+ = 4.1.10 =
34
+ *Release Date - 6 July 2021*
35
+
36
+ * Simplified the code around checking comments in REST API and XML-RPC requests.
37
+ * Updated Plus plan terminology in notices to match current subscription names.
38
+ * Added `rel="noopener"` to the widget link to avoid warnings in Google Lighthouse.
39
+ * Set the Akismet JavaScript as deferred instead of async to improve responsiveness.
40
+ * Improved the preloading of screenshot popups on the edit comments admin page.
41
+
42
  = 4.1.9 =
43
  *Release Date - 2 March 2021*
44
 
views/notice.php CHANGED
@@ -113,7 +113,8 @@
113
  </div>
114
  <?php elseif ( $type == 'existing-key-invalid' ) :?>
115
  <div class="akismet-alert akismet-critical">
116
- <h3 class="akismet-key-status"><?php esc_html_e( 'Your API key is no longer valid. Please enter a new key or contact support@akismet.com.' , 'akismet'); ?></h3>
 
117
  </div>
118
  <?php elseif ( $type == 'new-key-failed' ) :?>
119
  <div class="akismet-alert akismet-critical">
@@ -123,14 +124,14 @@
123
  <?php elseif ( $type == 'limit-reached' && in_array( $level, array( 'yellow', 'red' ) ) ) :?>
124
  <div class="akismet-alert akismet-critical">
125
  <?php if ( $level == 'yellow' ): ?>
126
- <h3 class="akismet-key-status failed"><?php esc_html_e( 'You&#8217;re using your Akismet key on more sites than your Pro subscription allows.', 'akismet' ); ?></h3>
127
  <p class="akismet-description">
128
- <?php printf( __( 'Your Pro subscription allows the use of Akismet on only one site. Please <a href="%s" target="_blank">purchase additional Pro subscriptions</a> or upgrade to an Enterprise subscription that allows the use of Akismet on unlimited sites.', 'akismet' ), 'https://docs.akismet.com/billing/add-more-sites/' ); ?>
129
  <br /><br />
130
  <?php printf( __( 'Please <a href="%s" target="_blank">contact our support team</a> with any questions.', 'akismet' ), 'https://akismet.com/contact/'); ?>
131
  </p>
132
  <?php elseif ( $level == 'red' ): ?>
133
- <h3 class="akismet-key-status failed"><?php esc_html_e( 'You&#8217;re using Akismet on far too many sites for your Pro subscription.', 'akismet' ); ?></h3>
134
  <p class="akismet-description">
135
  <?php printf( __( 'To continue your service, <a href="%s" target="_blank">upgrade to an Enterprise subscription</a>, which covers an unlimited number of sites.', 'akismet'), 'https://akismet.com/account/upgrade/' ); ?>
136
  <br /><br />
@@ -138,4 +139,4 @@
138
  </p>
139
  <?php endif; ?>
140
  </div>
141
- <?php endif;?>
113
  </div>
114
  <?php elseif ( $type == 'existing-key-invalid' ) :?>
115
  <div class="akismet-alert akismet-critical">
116
+ <h3 class="akismet-key-status"><?php echo esc_html( __( 'Your API key is no longer valid.' , 'akismet' ) ); ?></h3>
117
+ <p class="akismet-description"><?php printf( __( 'Please enter a new key or <a href="%s" target="_blank">contact Akismet support</a>.' , 'akismet' ), 'https://akismet.com/contact/' ); ?></p>
118
  </div>
119
  <?php elseif ( $type == 'new-key-failed' ) :?>
120
  <div class="akismet-alert akismet-critical">
124
  <?php elseif ( $type == 'limit-reached' && in_array( $level, array( 'yellow', 'red' ) ) ) :?>
125
  <div class="akismet-alert akismet-critical">
126
  <?php if ( $level == 'yellow' ): ?>
127
+ <h3 class="akismet-key-status failed"><?php esc_html_e( 'You&#8217;re using your Akismet key on more sites than your Plus subscription allows.', 'akismet' ); ?></h3>
128
  <p class="akismet-description">
129
+ <?php printf( __( 'Your Plus subscription allows the use of Akismet on only one site. Please <a href="%s" target="_blank">purchase additional Plus subscriptions</a> or upgrade to an Enterprise subscription that allows the use of Akismet on unlimited sites.', 'akismet' ), 'https://docs.akismet.com/billing/add-more-sites/' ); ?>
130
  <br /><br />
131
  <?php printf( __( 'Please <a href="%s" target="_blank">contact our support team</a> with any questions.', 'akismet' ), 'https://akismet.com/contact/'); ?>
132
  </p>
133
  <?php elseif ( $level == 'red' ): ?>
134
+ <h3 class="akismet-key-status failed"><?php esc_html_e( 'You&#8217;re using Akismet on far too many sites for your Plus subscription.', 'akismet' ); ?></h3>
135
  <p class="akismet-description">
136
  <?php printf( __( 'To continue your service, <a href="%s" target="_blank">upgrade to an Enterprise subscription</a>, which covers an unlimited number of sites.', 'akismet'), 'https://akismet.com/account/upgrade/' ); ?>
137
  <br /><br />
139
  </p>
140
  <?php endif; ?>
141
  </div>
142
+ <?php endif;?>