summaryrefslogtreecommitdiffstats
path: root/wp-content/plugins/akismet
diff options
context:
space:
mode:
Diffstat (limited to 'wp-content/plugins/akismet')
-rw-r--r--wp-content/plugins/akismet/akismet.php4
-rw-r--r--wp-content/plugins/akismet/class.akismet-admin.php53
-rw-r--r--wp-content/plugins/akismet/class.akismet-rest-api.php179
-rw-r--r--wp-content/plugins/akismet/class.akismet.php65
-rw-r--r--wp-content/plugins/akismet/readme.txt9
-rw-r--r--wp-content/plugins/akismet/views/config.php68
-rw-r--r--wp-content/plugins/akismet/views/notice.php10
7 files changed, 319 insertions, 69 deletions
diff --git a/wp-content/plugins/akismet/akismet.php b/wp-content/plugins/akismet/akismet.php
index 9ec3315..b62fddd 100644
--- a/wp-content/plugins/akismet/akismet.php
+++ b/wp-content/plugins/akismet/akismet.php
@@ -6,7 +6,7 @@
Plugin Name: Akismet Anti-spam: Spam Protection
Plugin URI: https://akismet.com/
Description: Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Akismet Anti-spam 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.
-Version: 5.3.1
+Version: 5.3.2
Requires at least: 5.8
Requires PHP: 5.6.20
Author: Automattic - Anti-spam Team
@@ -39,7 +39,7 @@ if ( !function_exists( 'add_action' ) ) {
exit;
}
-define( 'AKISMET_VERSION', '5.3.1' );
+define( 'AKISMET_VERSION', '5.3.2' );
define( 'AKISMET__MINIMUM_WP_VERSION', '5.8' );
define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'AKISMET_DELETE_LIMIT', 10000 );
diff --git a/wp-content/plugins/akismet/class.akismet-admin.php b/wp-content/plugins/akismet/class.akismet-admin.php
index b30813a..dd39104 100644
--- a/wp-content/plugins/akismet/class.akismet-admin.php
+++ b/wp-content/plugins/akismet/class.akismet-admin.php
@@ -667,6 +667,18 @@ class Akismet_Admin {
$message = esc_html( __( 'Akismet was unable to recheck this comment.', 'akismet' ) );
}
break;
+ case 'webhook-spam':
+ $message = esc_html( __( 'Akismet caught this comment as spam and updated its status via webhook.', 'akismet' ) );
+ break;
+ case 'webhook-ham':
+ $message = esc_html( __( 'Akismet cleared this comment and updated its status via webhook.', 'akismet' ) );
+ break;
+ case 'webhook-spam-noaction':
+ $message = esc_html( __( 'Akismet determined this comment was spam during a recheck. It did not update the comment status because it had already been modified by another user or plugin.', 'akismet' ) );
+ break;
+ case 'webhook-ham-noaction':
+ $message = esc_html( __( 'Akismet cleared this comment during a recheck. It did not update the comment status because it had already been modified by another user or plugin.', 'akismet' ) );
+ break;
default:
if ( preg_match( '/^status-changed/', $row['event'] ) ) {
// Half of these used to be saved without the dash after 'status-changed'.
@@ -1098,26 +1110,29 @@ class Akismet_Admin {
}
/*
- // To see all variants when testing.
- $notices[] = array( 'type' => 'active-notice', 'time_saved' => 'Cleaning up spam takes time. Akismet has saved you 1 minute!' );
- $notices[] = array( 'type' => 'plugin' );
- $notices[] = array( 'type' => 'spam-check', 'link_text' => 'Link text.' );
- $notices[] = array( 'type' => 'notice', 'notice_header' => 'This is the notice header.', 'notice_text' => 'This is the notice text.' );
- $notices[] = array( 'type' => 'missing-functions' );
- $notices[] = array( 'type' => 'servers-be-down' );
- $notices[] = array( 'type' => 'active-dunning' );
- $notices[] = array( 'type' => 'cancelled' );
- $notices[] = array( 'type' => 'suspended' );
- $notices[] = array( 'type' => 'missing' );
- $notices[] = array( 'type' => 'no-sub' );
- $notices[] = array( 'type' => 'new-key-valid' );
- $notices[] = array( 'type' => 'new-key-invalid' );
- $notices[] = array( 'type' => 'existing-key-invalid' );
- $notices[] = array( 'type' => 'new-key-failed' );
- $notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_plan' => 'Enterprise', 'upgrade_url' => 'https://akismet.com/account/', 'code' => 10502 );
- $notices[] = array( 'type' => 'spam-check-cron-disabled' );
- $notices[] = array( 'type' => 'alert', 'code' => 123 );
+ * To see all variants when testing.
+ *
+ * You may also want to comment out the akismet_view_arguments filter in Akismet::view()
+ * to ensure that you can see all of the notices (e.g. suspended, active-notice).
*/
+ // $notices[] = array( 'type' => 'active-notice', 'time_saved' => 'Cleaning up spam takes time. Akismet has saved you 1 minute!' );
+ // $notices[] = array( 'type' => 'plugin' );
+ // $notices[] = array( 'type' => 'spam-check', 'link_text' => 'Link text.' );
+ // $notices[] = array( 'type' => 'notice', 'notice_header' => 'This is the notice header.', 'notice_text' => 'This is the notice text.' );
+ // $notices[] = array( 'type' => 'missing-functions' );
+ // $notices[] = array( 'type' => 'servers-be-down' );
+ // $notices[] = array( 'type' => 'active-dunning' );
+ // $notices[] = array( 'type' => 'cancelled' );
+ // $notices[] = array( 'type' => 'suspended' );
+ // $notices[] = array( 'type' => 'missing' );
+ // $notices[] = array( 'type' => 'no-sub' );
+ // $notices[] = array( 'type' => 'new-key-valid' );
+ // $notices[] = array( 'type' => 'new-key-invalid' );
+ // $notices[] = array( 'type' => 'existing-key-invalid' );
+ // $notices[] = array( 'type' => 'new-key-failed' );
+ // $notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_plan' => 'Enterprise', 'upgrade_url' => 'https://akismet.com/account/', 'code' => 10502 );
+ // $notices[] = array( 'type' => 'spam-check-cron-disabled' );
+ // $notices[] = array( 'type' => 'alert', 'code' => 123 );
Akismet::log( compact( 'stat_totals', 'akismet_user' ) );
Akismet::view( 'config', compact( 'api_key', 'akismet_user', 'stat_totals', 'notices' ) );
diff --git a/wp-content/plugins/akismet/class.akismet-rest-api.php b/wp-content/plugins/akismet/class.akismet-rest-api.php
index ef09f70..12e86f0 100644
--- a/wp-content/plugins/akismet/class.akismet-rest-api.php
+++ b/wp-content/plugins/akismet/class.akismet-rest-api.php
@@ -129,6 +129,16 @@ class Akismet_REST_API {
),
)
) );
+
+ register_rest_route(
+ 'akismet/v1',
+ '/webhook',
+ array(
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => array( 'Akismet_REST_API', 'receive_webhook' ),
+ 'permission_callback' => array( 'Akismet_REST_API', 'remote_call_permission_callback' ),
+ )
+ );
}
/**
@@ -370,4 +380,173 @@ class Akismet_REST_API {
public static function sanitize_key( $key, $request, $param ) {
return trim( $key );
}
+
+ /**
+ * Process a webhook request from the Akismet servers.
+ *
+ * @param WP_REST_Request $request
+ * @return WP_Error|WP_REST_Response
+ */
+ public static function receive_webhook( $request ) {
+ Akismet::log( array( 'Webhook request received', $request->get_body() ) );
+
+ /**
+ * The request body should look like this:
+ * array(
+ * 'key' => '1234567890abcd',
+ * 'endpoint' => '[comment-check|submit-ham|submit-spam]',
+ * 'comments' => array(
+ * array(
+ * 'guid' => '[...]',
+ * 'result' => '[true|false]',
+ * 'comment_author' => '[...]',
+ * [...]
+ * ),
+ * array(
+ * 'guid' => '[...]',
+ * [...],
+ * ),
+ * [...]
+ * )
+ * )
+ *
+ * Multiple comments can be included in each request, and the only truly required
+ * field for each is the guid, although it would be friendly to include also
+ * comment_post_ID, comment_parent, and comment_author_email, if possible to make
+ * searching easier.
+ */
+
+ // The response will include statuses for the result of each comment that was supplied.
+ $response = array(
+ 'comments' => array(),
+ );
+
+ $endpoint = $request->get_param( 'endpoint' );
+
+ switch ( $endpoint ) {
+ case 'comment-check':
+ $webhook_comments = $request->get_param( 'comments' );
+
+ if ( ! is_array( $webhook_comments ) ) {
+ return rest_ensure_response( new WP_Error( 'malformed_request', __( 'The \'comments\' parameter must be an array.', 'akismet' ), array( 'status' => 400 ) ) );
+ }
+
+ foreach ( $webhook_comments as $webhook_comment ) {
+ $guid = $webhook_comment['guid'];
+
+ if ( ! $guid ) {
+ // Without the GUID, we can't be sure that we're matching the right comment.
+ // We'll make it a rule that any comment without a GUID is ignored intentionally.
+ continue;
+ }
+
+ // Search on the fields that are indexed in the comments table, plus the GUID.
+ // The GUID is the only thing we really need to search on, but comment_meta
+ // is not indexed in a useful way if there are many many comments. This
+ // should help narrow it down first.
+ $queryable_fields = array(
+ 'comment_post_ID' => 'post_id',
+ 'comment_parent' => 'parent',
+ 'comment_author_email' => 'author_email',
+ );
+
+ $query_args = array();
+ $query_args['status'] = 'any';
+ $query_args['meta_key'] = 'akismet_guid';
+ $query_args['meta_value'] = $guid;
+
+ foreach ( $queryable_fields as $queryable_field => $wp_comment_query_field ) {
+ if ( isset( $webhook_comment[ $queryable_field ] ) ) {
+ $query_args[ $wp_comment_query_field ] = $webhook_comment[ $queryable_field ];
+ }
+ }
+
+ $comments_query = new WP_Comment_Query( $query_args );
+ $comments = $comments_query->comments;
+
+ if ( ! $comments ) {
+ // Unexpected, although the comment could have been deleted since being submitted.
+ Akismet::log( 'Webhook failed: no matching comment found.' );
+
+ $response['comments'][ $guid ] = array( 'status' => 'error', 'message' => __( 'Could not find matching comment.', 'akismet' ) );
+
+ continue;
+ } if ( count( $comments ) > 1 ) {
+ // Two comments shouldn't be able to match the same GUID.
+ Akismet::log( 'Webhook failed: multiple matching comments found.', $comments );
+
+ $response['comments'][ $guid ] = array( 'status' => 'error', 'message' => __( 'Multiple comments matched request.', 'akismet' ) );
+
+ continue;
+ } else {
+ // We have one single match, as hoped for.
+ Akismet::log( 'Found matching comment.', $comments );
+
+ $current_status = wp_get_comment_status( $comments[0] );
+
+ $result = $webhook_comment['result'];
+
+ if ( 'true' == $result ) {
+ Akismet::log( 'Comment should be spam' );
+
+ // The comment should be classified as spam.
+ if ( 'spam' != $current_status ) {
+ // The comment is not classified as spam. If Akismet was the one to act on it, move it to spam.
+ if ( Akismet::last_comment_status_change_came_from_akismet( $comments[0]->comment_ID ) ) {
+ Akismet::log( 'Comment is not spam; marking as spam.' );
+
+ wp_spam_comment( $comments[0] );
+ Akismet::update_comment_history( $comments[0]->comment_ID, '', 'webhook-spam' );
+ } else {
+ Akismet::log( 'Comment is not spam, but it has already been manually handled by some other process.' );
+ Akismet::update_comment_history( $comments[0]->comment_ID, '', 'webhook-spam-noaction' );
+ }
+ }
+ } else if ( 'false' == $result ) {
+ Akismet::log( 'Comment should be ham' );
+
+ // The comment should be classified as ham.
+ if ( 'spam' == $current_status ) {
+ Akismet::log( 'Comment is spam.' );
+
+ // The comment is classified as spam. If Akismet was the one to label it as spam, unspam it.
+ if ( Akismet::last_comment_status_change_came_from_akismet( $comments[0]->comment_ID ) ) {
+ Akismet::log( 'Akismet marked it as spam; unspamming.' );
+
+ wp_unspam_comment( $comments[0] );
+ akismet::update_comment_history( $comments[0]->comment_ID, '', 'webhook-ham' );
+ } else {
+ Akismet::log( 'Comment is not spam, but it has already been manually handled by some other process.' );
+ Akismet::update_comment_history( $comments[0]->comment_ID, '', 'webhook-ham-noaction' );
+ }
+ }
+ }
+
+ $response['comments'][ $guid ] = array( 'status' => 'success' );
+ }
+ }
+
+ break;
+ case 'submit-ham':
+ case 'submit-spam':
+ // Nothing to do for submit-ham or submit-spam.
+ break;
+ default:
+ // Unsupported endpoint.
+ break;
+ }
+
+ /**
+ * Allow plugins to do things with a successfully processed webhook request, like logging.
+ *
+ * @since 5.3.2
+ *
+ * @param WP_REST_Request $request The REST request object.
+ */
+ do_action( 'akismet_webhook_received', $request );
+
+ Akismet::log( 'Done processing webhook.' );
+
+ return rest_ensure_response( $response );
+ }
}
diff --git a/wp-content/plugins/akismet/class.akismet.php b/wp-content/plugins/akismet/class.akismet.php
index 951142e..7a89f61 100644
--- a/wp-content/plugins/akismet/class.akismet.php
+++ b/wp-content/plugins/akismet/class.akismet.php
@@ -642,7 +642,14 @@ class Akismet {
return 0;
}
- // get the full comment history for a given comment, as an array in reverse chronological order
+ /**
+ * Get the full comment history for a given comment, as an array in reverse chronological order.
+ * Each entry will have an 'event', a 'time', and possible a 'message' member (if the entry is old enough).
+ * Some entries will also have a 'user' or 'meta' member.
+ *
+ * @param int $comment_id The relevant comment ID.
+ * @return array|bool An array of history events, or false if there is no history.
+ */
public static function get_comment_history( $comment_id ) {
$history = get_comment_meta( $comment_id, 'akismet_history', false );
if ( empty( $history ) || empty( $history[ 0 ] ) ) {
@@ -681,6 +688,10 @@ class Akismet {
$history[] = array( 'time' => 445856425, 'event' => 'status-spam', 'user' => 'sam' );
$history[] = array( 'time' => 445856426, 'event' => 'status-hold', 'user' => 'sam' );
$history[] = array( 'time' => 445856427, 'event' => 'status-approve', 'user' => 'sam' );
+ $history[] = array( 'time' => 445856427, 'event' => 'webhook-spam' );
+ $history[] = array( 'time' => 445856427, 'event' => 'webhook-ham' );
+ $history[] = array( 'time' => 445856427, 'event' => 'webhook-spam-noaction' );
+ $history[] = array( 'time' => 445856427, 'event' => 'webhook-ham-noaction' );
*/
usort( $history, array( 'Akismet', '_cmp_time' ) );
@@ -819,6 +830,17 @@ class Akismet {
if ( get_comment_meta( $comment->comment_ID, 'akismet_rechecking' ) )
return;
+ if ( function_exists( 'getallheaders' ) ) {
+ $request_headers = getallheaders();
+
+ foreach ( $request_headers as $header => $value ) {
+ if ( strtolower( $header ) == 'x-akismet-webhook' ) {
+ // This change is due to a webhook request.
+ return;
+ }
+ }
+ }
+
// Assumption alert:
// We want to submit comments to Akismet only when a moderator explicitly spams or approves it - not if the status
// is changed automatically by another plugin. Unfortunately WordPress doesn't provide an unambiguous way to
@@ -1583,7 +1605,7 @@ p {
public static function view( $name, array $args = array() ) {
$args = apply_filters( 'akismet_view_arguments', $args, $name );
- foreach ( $args AS $key => $val ) {
+ foreach ( $args as $key => $val ) {
$$key = $val;
}
@@ -1871,4 +1893,43 @@ p {
return $return_value;
}
+
+ /**
+ * Was the last entry in the comment history created by Akismet?
+ *
+ * @param int $comment_id The ID of the comment.
+ * @return bool
+ */
+ public static function last_comment_status_change_came_from_akismet( $comment_id ) {
+ $history = self::get_comment_history( $comment_id );
+
+ if ( empty( $history ) ) {
+ return false;
+ }
+
+ $most_recent_history_event = $history[0];
+
+ if ( ! isset( $most_recent_history_event['event'] ) ) {
+ return false;
+ }
+
+ $akismet_history_events = array(
+ 'check-error',
+ 'cron-retry-ham',
+ 'cron-retry-spam',
+ 'check-ham',
+ 'check-spam',
+ 'recheck-error',
+ 'recheck-ham',
+ 'recheck-spam',
+ 'webhook-ham',
+ 'webhook-spam',
+ );
+
+ if ( in_array( $most_recent_history_event['event'], $akismet_history_events ) ) {
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/wp-content/plugins/akismet/readme.txt b/wp-content/plugins/akismet/readme.txt
index 2221acf..30d7748 100644
--- a/wp-content/plugins/akismet/readme.txt
+++ b/wp-content/plugins/akismet/readme.txt
@@ -3,7 +3,7 @@ Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eo
Tags: comments, spam, antispam, anti-spam, contact form, anti spam, comment moderation, comment spam, contact form spam, spam comments
Requires at least: 5.8
Tested up to: 6.4
-Stable tag: 5.3.1
+Stable tag: 5.3.2
License: GPLv2 or later
The best anti-spam protection to block spam comments and spam in a contact form. The most trusted antispam solution for WordPress and WooCommerce.
@@ -32,6 +32,13 @@ Upload the Akismet plugin to your blog, activate it, and then enter your Akismet
== Changelog ==
+= 5.3.2 =
+*Release Date - 21 March 2024*
+
+* Improve the empty state shown to new users when no spam has been caught yet.
+* Update the message shown to users without a current subscription.
+* Add foundations for future webhook support.
+
= 5.3.1 =
*Release Date - 17 January 2024*
diff --git a/wp-content/plugins/akismet/views/config.php b/wp-content/plugins/akismet/views/config.php
index 77e5914..b9e4457 100644
--- a/wp-content/plugins/akismet/views/config.php
+++ b/wp-content/plugins/akismet/views/config.php
@@ -30,7 +30,6 @@ $kses_allow_link_href = array(
<span><?php esc_html_e( 'Statistics', 'akismet' ); ?></span>
</h2>
- <?php if ( $stat_totals && isset( $stat_totals['all'] ) && (int) $stat_totals['all']->spam > 0 ) : ?>
<div class="akismet-section-header__actions">
<a href="<?php echo esc_url( Akismet_Admin::get_page_url( 'stats' ) ); ?>">
<?php esc_html_e( 'Detailed stats', 'akismet' ); ?>
@@ -38,43 +37,36 @@ $kses_allow_link_href = array(
</div>
</div> <!-- close akismet-section-header -->
- <div class="akismet-new-snapshot">
- <?php /* name attribute on iframe is used as a cache-buster here to force Firefox to load the new style charts: https://bugzilla.mozilla.org/show_bug.cgi?id=356558 */ ?>
- <div class="akismet-new-snapshot__chart">
- <iframe id="stats-iframe" allowtransparency="true" scrolling="no" frameborder="0" style="width: 100%; height: 220px; overflow: hidden;" src="<?php echo esc_url( sprintf( 'https://tools.akismet.com/1.0/snapshot.php?blog=%s&token=%s&height=200&locale=%s&is_redecorated=1', rawurlencode( get_option( 'home' ) ), rawurlencode( Akismet::get_access_token() ), get_locale() ) ); ?>" name="<?php echo esc_attr( 'snapshot-' . filemtime( __FILE__ ) ); ?>" title="<?php echo esc_attr__( 'Akismet stats' ); ?>"></iframe>
- </div>
- <ul class="akismet-new-snapshot__list">
- <li class="akismet-new-snapshot__item">
- <h3 class="akismet-new-snapshot__header"><?php esc_html_e( 'Past six months', 'akismet' ); ?></h3>
- <span class="akismet-new-snapshot__number"><?php echo number_format( $stat_totals['6-months']->spam ); ?></span>
- <span class="akismet-new-snapshot__text"><?php echo esc_html( _n( 'Spam blocked', 'Spam blocked', $stat_totals['6-months']->spam, 'akismet' ) ); ?></span>
- </li>
- <li class="akismet-new-snapshot__item">
- <h3 class="akismet-new-snapshot__header"><?php esc_html_e( 'All time', 'akismet' ); ?></h3>
- <span class="akismet-new-snapshot__number"><?php echo number_format( $stat_totals['all']->spam ); ?></span>
- <span class="akismet-new-snapshot__text"><?php echo esc_html( _n( 'Spam blocked', 'Spam blocked', $stat_totals['all']->spam, 'akismet' ) ); ?></span>
- </li>
- <li class="akismet-new-snapshot__item">
- <h3 class="akismet-new-snapshot__header"><?php esc_html_e( 'Accuracy', 'akismet' ); ?></h3>
- <span class="akismet-new-snapshot__number"><?php echo floatval( $stat_totals['all']->accuracy ); ?>%</span>
- <span class="akismet-new-snapshot__text">
- <?php
- /* translators: %s: number of spam missed by Akismet */
- echo esc_html( sprintf( _n( '%s missed spam', '%s missed spam', $stat_totals['all']->missed_spam, 'akismet' ), number_format( $stat_totals['all']->missed_spam ) ) ) . ', ';
- /* translators: %s: number of false positive spam flagged by Akismet */
- echo esc_html( sprintf( _n( '%s false positive', '%s false positives', $stat_totals['all']->false_positives, 'akismet' ), number_format( $stat_totals['all']->false_positives ) ) );
- ?>
- </span>
- </li>
- </ul>
- </div> <!-- close akismet-new-snapshot -->
-
- <?php else : ?>
- </div> <!-- close akismet-section-header -->
- <div class="inside">
- <p class="akismet-awaiting-stats"><?php esc_html_e( 'Akismet is active and ready to stop spam. Your site&#8217;s spam statistics will be displayed here.', 'akismet' ); ?></p>
- </div>
- <?php endif; ?>
+ <div class="akismet-new-snapshot">
+ <?php /* name attribute on iframe is used as a cache-buster here to force Firefox to load the new style charts: https://bugzilla.mozilla.org/show_bug.cgi?id=356558 */ ?>
+ <div class="akismet-new-snapshot__chart">
+ <iframe id="stats-iframe" allowtransparency="true" scrolling="no" frameborder="0" style="width: 100%; height: 220px; overflow: hidden;" src="<?php echo esc_url( sprintf( 'https://tools.akismet.com/1.0/snapshot.php?blog=%s&token=%s&height=200&locale=%s&is_redecorated=1', rawurlencode( get_option( 'home' ) ), rawurlencode( Akismet::get_access_token() ), get_locale() ) ); ?>" name="<?php echo esc_attr( 'snapshot-' . filemtime( __FILE__ ) ); ?>" title="<?php echo esc_attr__( 'Akismet stats' ); ?>"></iframe>
+ </div>
+ <ul class="akismet-new-snapshot__list">
+ <li class="akismet-new-snapshot__item">
+ <h3 class="akismet-new-snapshot__header"><?php esc_html_e( 'Past six months', 'akismet' ); ?></h3>
+ <span class="akismet-new-snapshot__number"><?php echo number_format( $stat_totals['6-months']->spam ); ?></span>
+ <span class="akismet-new-snapshot__text"><?php echo esc_html( _n( 'Spam blocked', 'Spam blocked', $stat_totals['6-months']->spam, 'akismet' ) ); ?></span>
+ </li>
+ <li class="akismet-new-snapshot__item">
+ <h3 class="akismet-new-snapshot__header"><?php esc_html_e( 'All time', 'akismet' ); ?></h3>
+ <span class="akismet-new-snapshot__number"><?php echo number_format( $stat_totals['all']->spam ); ?></span>
+ <span class="akismet-new-snapshot__text"><?php echo esc_html( _n( 'Spam blocked', 'Spam blocked', $stat_totals['all']->spam, 'akismet' ) ); ?></span>
+ </li>
+ <li class="akismet-new-snapshot__item">
+ <h3 class="akismet-new-snapshot__header"><?php esc_html_e( 'Accuracy', 'akismet' ); ?></h3>
+ <span class="akismet-new-snapshot__number"><?php echo floatval( $stat_totals['all']->accuracy ); ?>%</span>
+ <span class="akismet-new-snapshot__text">
+ <?php
+ /* translators: %s: number of spam missed by Akismet */
+ echo esc_html( sprintf( _n( '%s missed spam', '%s missed spam', $stat_totals['all']->missed_spam, 'akismet' ), number_format( $stat_totals['all']->missed_spam ) ) ) . ', ';
+ /* translators: %s: number of false positive spam flagged by Akismet */
+ echo esc_html( sprintf( _n( '%s false positive', '%s false positives', $stat_totals['all']->false_positives, 'akismet' ), number_format( $stat_totals['all']->false_positives ) ) );
+ ?>
+ </span>
+ </li>
+ </ul>
+ </div> <!-- close akismet-new-snapshot -->
</div> <!-- close akismet-card -->
diff --git a/wp-content/plugins/akismet/views/notice.php b/wp-content/plugins/akismet/views/notice.php
index 8bacc54..466a322 100644
--- a/wp-content/plugins/akismet/views/notice.php
+++ b/wp-content/plugins/akismet/views/notice.php
@@ -152,15 +152,11 @@ $kses_allow_strong = array( 'strong' => true );
<?php elseif ( $type === 'no-sub' ) : ?>
<div class="akismet-alert is-bad">
<h3 class="akismet-alert__heading"><?php esc_html_e( 'You don&#8217;t have an Akismet plan.', 'akismet' ); ?></h3>
+ <p><?php echo esc_html__( 'Your API key must have an Akismet plan before it can protect your site from spam.', 'akismet' ); ?></p>
<p>
<?php
- /* translators: the placeholder is a clickable URL to the Akismet account upgrade page. */
- echo wp_kses( sprintf( __( 'In 2012, Akismet began using subscription plans for all accounts (even free ones). A plan has not been assigned to your account, and we&#8217;d appreciate it if you&#8217;d <a href="%s" target="_blank">sign into your account</a> and choose one.', 'akismet' ), esc_url( 'https://akismet.com/pricing' ) ), $kses_allow_link );
- ?>
- <br /><br />
- <?php
- /* translators: The placeholder is a URL to the Akismet contact form. */
- echo wp_kses( sprintf( __( 'Please <a href="%s" target="_blank">contact our support team</a> with any questions.', 'akismet' ), esc_url( 'https://akismet.com/contact/' ) ), $kses_allow_link );
+ /* translators: the placeholder is the URL to the Akismet pricing page. */
+ echo wp_kses( sprintf( __( 'Please <a href="%s" target="_blank">choose a plan</a> to get started with Akismet.', 'akismet' ), esc_url( 'https://akismet.com/pricing' ) ), $kses_allow_link );
?>
</p>
</div>