From 0949f1a8dfdad78ba67fb1dcd58537b83a26e8a3 Mon Sep 17 00:00:00 2001 From: Yury German Date: Sun, 23 Jan 2022 19:08:06 -0500 Subject: Plugin - akismet update from 4.1.3 to 4.2.1 Signed-off-by: Yury German --- plugins/akismet/.htaccess | 2 +- plugins/akismet/_inc/akismet.css | 83 +++++- plugins/akismet/_inc/akismet.js | 207 +++++++++----- plugins/akismet/_inc/form.js | 30 -- plugins/akismet/_inc/img/logo-a-2x.png | Bin 0 -> 904 bytes plugins/akismet/akismet.php | 6 +- plugins/akismet/changelog.txt | 465 ++++++++++++++++++++++++++++++ plugins/akismet/class.akismet-admin.php | 167 ++++++----- plugins/akismet/class.akismet-widget.php | 24 +- plugins/akismet/class.akismet.php | 477 ++++++++++++++++++++++++------- plugins/akismet/readme.txt | 476 +++--------------------------- plugins/akismet/views/config.php | 8 +- plugins/akismet/views/enter.php | 2 +- plugins/akismet/views/notice.php | 193 +++++++++++-- plugins/akismet/views/setup.php | 5 +- plugins/akismet/views/stats.php | 2 +- 16 files changed, 1389 insertions(+), 758 deletions(-) delete mode 100644 plugins/akismet/_inc/form.js create mode 100644 plugins/akismet/_inc/img/logo-a-2x.png create mode 100644 plugins/akismet/changelog.txt diff --git a/plugins/akismet/.htaccess b/plugins/akismet/.htaccess index 49d72d71..d2675517 100644 --- a/plugins/akismet/.htaccess +++ b/plugins/akismet/.htaccess @@ -23,7 +23,7 @@ # Akismet images - + Allow from all diff --git a/plugins/akismet/_inc/akismet.css b/plugins/akismet/_inc/akismet.css index fea4eb7b..a62f7a84 100644 --- a/plugins/akismet/_inc/akismet.css +++ b/plugins/akismet/_inc/akismet.css @@ -79,15 +79,6 @@ table.comments td.comment p a:after { .checkforspam { display: inline-block !important; } -.checkforspam-progress { - display: none; -} -.checkforspam.checking .checkforspam-progress { - padding-left: 1ex; -} -.checkforspam.button-disabled .checkforspam-progress { - display: inline; -} .checkforspam-spinner { display: inline-block; @@ -109,12 +100,9 @@ table.comments td.comment p a:after { margin-top: .5rem; } .akismet-alert { - border: 1px solid #e5e5e5; padding: 0.4em 1em 1.4em 1em; - border-radius: 3px; - -webkit-border-radius: 3px; - border-width: 1px; - border-style: solid; + box-sizing: border-box; + box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3; } .akismet-alert h3.akismet-key-status { @@ -661,3 +649,70 @@ table.comments td.comment p a:after { .akismet-section-header__actions { line-height: 1.75rem; } + +.akismet-setup-instructions { + text-align: center; +} + +.akismet-setup-instructions form { + padding-bottom: 1.5rem; +} + +div.error.akismet-usage-limit-alert { + padding: 25px 45px 25px 15px; + display: flex; + align-items: center; +} + +#akismet-plugin-container .akismet-usage-limit-alert { + margin: 0 auto 0.625rem auto; + box-sizing: border-box; + box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3; + border: none; + border-left: 4px solid #d63638; +} + +.akismet-usage-limit-alert .akismet-usage-limit-logo { + width: 38px; + min-width: 38px; + height: 38px; + border-radius: 20px; + margin-right: 18px; + background: black; + position: relative; +} + +.akismet-usage-limit-alert .akismet-usage-limit-logo img { + position: absolute; + width: 22px; + left: 8px; + top: 10px; +} + +.akismet-usage-limit-alert .akismet-usage-limit-text { + flex-grow: 1; + margin-right: 18px; +} + +.akismet-usage-limit-alert h3 { + margin: 0; +} + +.akismet-usage-limit-alert .akismet-usage-limit-cta { + text-align: right; +} + +@media (max-width: 550px) { + div.error.akismet-usage-limit-alert { + display: block; + } + + .akismet-usage-limit-alert .akismet-usage-limit-logo, + .akismet-usage-limit-alert .akismet-usage-limit-text { + margin-bottom: 15px; + } + + .akismet-usage-limit-alert .akismet-usage-limit-cta { + text-align: left; + } +} \ No newline at end of file diff --git a/plugins/akismet/_inc/akismet.js b/plugins/akismet/_inc/akismet.js index 3445a094..7ebac1a5 100644 --- a/plugins/akismet/_inc/akismet.js +++ b/plugins/akismet/_inc/akismet.js @@ -1,10 +1,12 @@ jQuery( function ( $ ) { var mshotRemovalTimer = null; - var mshotSecondTryTimer = null - var mshotThirdTryTimer = null - + var mshotRetryTimer = null; + var mshotTries = 0; + var mshotRetryInterval = 1000; var mshotEnabledLinkSelector = 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a'; - + + var preloadedMshotURLs = []; + $('.akismet-status').each(function () { var thisId = $(this).attr('commentid'); $(this).prependTo('#comment-' + thisId + ' .column-comment'); @@ -84,69 +86,142 @@ jQuery( function ( $ ) { }); // Show a preview image of the hovered URL. Applies to author URLs and URLs inside the comments. - $( '#the-comment-list' ).on( 'mouseover', mshotEnabledLinkSelector, function () { - clearTimeout( mshotRemovalTimer ); + if ( "enable_mshots" in WPAkismet && WPAkismet.enable_mshots ) { + $( '#the-comment-list' ).on( 'mouseover', mshotEnabledLinkSelector, function () { + clearTimeout( mshotRemovalTimer ); - if ( $( '.akismet-mshot' ).length > 0 ) { - if ( $( '.akismet-mshot:first' ).data( 'link' ) == this ) { - // The preview is already showing for this link. - return; + if ( $( '.akismet-mshot' ).length > 0 ) { + if ( $( '.akismet-mshot:first' ).data( 'link' ) == this ) { + // The preview is already showing for this link. + return; + } + else { + // A new link is being hovered, so remove the old preview. + $( '.akismet-mshot' ).remove(); + } + } + + clearTimeout( mshotRetryTimer ); + + var linkUrl = $( this ).attr( 'href' ); + + if ( preloadedMshotURLs.indexOf( linkUrl ) !== -1 ) { + // 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. + mshotTries = 2; } else { - // A new link is being hovered, so remove the old preview. - $( '.akismet-mshot' ).remove(); + mshotTries = 1; } - } - clearTimeout( mshotSecondTryTimer ); - clearTimeout( mshotThirdTryTimer ); + var mShot = $( '
' ); + mShot.data( 'link', this ); + mShot.data( 'url', linkUrl ); + + mShot.find( 'img' ).on( 'load', function () { + $( '.akismet-mshot' ).data( 'pending-request', false ); + } ); - var thisHref = $( this ).attr( 'href' ); + var offset = $( this ).offset(); - var mShot = $( '
' ); - mShot.data( 'link', this ); + mShot.offset( { + left : Math.min( $( window ).width() - 475, offset.left + $( this ).width() + 10 ), // Keep it on the screen if the link is near the edge of the window. + top: offset.top + ( $( this ).height() / 2 ) - 101 // 101 = top offset of the arrow plus the top border thickness + } ); - var offset = $( this ).offset(); + $( 'body' ).append( mShot ); - mShot.offset( { - left : Math.min( $( window ).width() - 475, offset.left + $( this ).width() + 10 ), // Keep it on the screen if the link is near the edge of the window. - top: offset.top + ( $( this ).height() / 2 ) - 101 // 101 = top offset of the arrow plus the top border thickness + mshotRetryTimer = setTimeout( retryMshotUntilLoaded, mshotRetryInterval ); + } ).on( 'mouseout', 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a', function () { + mshotRemovalTimer = setTimeout( function () { + clearTimeout( mshotRetryTimer ); + + $( '.akismet-mshot' ).remove(); + }, 200 ); } ); - // These retries appear to be superfluous if .mshot-image has already loaded, but it's because mShots - // can return a "Generating thumbnail..." image if it doesn't have a thumbnail ready, so we need - // to retry to see if we can get the newly generated thumbnail. - mshotSecondTryTimer = setTimeout( function () { - mShot.find( '.mshot-image' ).attr( 'src', akismet_mshot_url( thisHref, 2 ) ); - }, 6000 ); - - mshotThirdTryTimer = setTimeout( function () { - mShot.find( '.mshot-image' ).attr( 'src', akismet_mshot_url( thisHref, 3 ) ); - }, 12000 ); - - $( 'body' ).append( mShot ); - } ).on( 'mouseout', 'a[id^="author_comment_url"], tr.pingback td.column-author a:first-of-type, td.comment p a', function () { - mshotRemovalTimer = setTimeout( function () { - clearTimeout( mshotSecondTryTimer ); - clearTimeout( mshotThirdTryTimer ); - - $( '.akismet-mshot' ).remove(); - }, 200 ); - } ).on( 'mouseover', 'tr', function () { - // When the mouse hovers over a comment row, begin preloading mshots for any links in the comment or the comment author. - var linksToPreloadMshotsFor = $( this ).find( mshotEnabledLinkSelector ); - - linksToPreloadMshotsFor.each( function () { - // Don't attempt to preload an mshot for a single link twice. Browser caching should cover this, but in case of - // race conditions, save a flag locally when we've begun trying to preload one. - if ( ! $( this ).data( 'akismet-mshot-preloaded' ) ) { - akismet_preload_mshot( $( this ).attr( 'href' ) ); + var preloadDelayTimer = null; + + $( window ).on( 'scroll resize', function () { + clearTimeout( preloadDelayTimer ); + + preloadDelayTimer = setTimeout( preloadMshotsInViewport, 500 ); + } ); + + preloadMshotsInViewport(); + } + + /** + * The way mShots works is if there was no screenshot already recently generated for the URL, + * it returns a "loading..." image for the first request. Then, some subsequent request will + * receive the actual screenshot, but it's unknown how long it will take. So, what we do here + * is continually re-request the mShot, waiting a second after every response until we get the + * actual screenshot. + */ + function retryMshotUntilLoaded() { + clearTimeout( mshotRetryTimer ); + + var imageWidth = $( '.akismet-mshot img' ).get(0).naturalWidth; + + if ( imageWidth == 0 ) { + // It hasn't finished loading yet the first time. Check again shortly. + setTimeout( retryMshotUntilLoaded, mshotRetryInterval ); + } + else if ( imageWidth == 400 ) { + // It loaded the preview image. + + if ( mshotTries == 20 ) { + // Give up if we've requested the mShot 20 times already. + return; + } + + if ( ! $( '.akismet-mshot' ).data( 'pending-request' ) ) { + $( '.akismet-mshot' ).data( 'pending-request', true ); + + mshotTries++; + + $( '.akismet-mshot .mshot-image' ).attr( 'src', akismet_mshot_url( $( '.akismet-mshot' ).data( 'url' ), mshotTries ) ); + } + + mshotRetryTimer = setTimeout( retryMshotUntilLoaded, mshotRetryInterval ); + } + else { + // All done. + } + } + + function preloadMshotsInViewport() { + var windowWidth = $( window ).width(); + var windowHeight = $( window ).height(); + + $( '#the-comment-list' ).find( mshotEnabledLinkSelector ).each( function ( index, element ) { + var linkUrl = $( this ).attr( 'href' ); + + // Don't attempt to preload an mshot for a single link twice. + if ( preloadedMshotURLs.indexOf( linkUrl ) !== -1 ) { + // The URL is already preloaded. + return true; + } + + if ( typeof element.getBoundingClientRect !== 'function' ) { + // The browser is too old. Return false to stop this preloading entirely. + return false; + } + + var rect = element.getBoundingClientRect(); + + if ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= windowHeight && rect.right <= windowWidth ) { + akismet_preload_mshot( linkUrl ); $( this ).data( 'akismet-mshot-preloaded', true ); } } ); - } ); + } + + $( '.checkforspam.enable-on-load' ).on( 'click', function( e ) { + if ( $( this ).hasClass( 'ajax-disabled' ) ) { + // Akismet hasn't been configured yet. Allow the user to proceed to the button's link. + return; + } - $( '.checkforspam' ).click( function( e ) { e.preventDefault(); if ( $( this ).hasClass( 'button-disabled' ) ) { @@ -157,11 +232,8 @@ jQuery( function ( $ ) { $('.checkforspam').addClass('button-disabled').addClass( 'checking' ); $('.checkforspam-spinner').addClass( 'spinner' ).addClass( 'is-active' ); - // Update the label on the "Check for Spam" button to use the active "Checking for Spam" language. - $( '.checkforspam .akismet-label' ).text( $( '.checkforspam' ).data( 'active-label' ) ); - akismet_check_for_spam(0, 100); - }); + }).removeClass( 'button-disabled' ); var spam_count = 0; var recheck_count = 0; @@ -176,7 +248,7 @@ jQuery( function ( $ ) { var percentage_complete = Math.round( ( recheck_count / check_for_spam_buttons.data( 'pending-comment-count' ) ) * 1000 ) / 10; // Update the progress counter on the "Check for Spam" button. - $( '.checkforspam-progress' ).text( check_for_spam_buttons.data( 'progress-label-format' ).replace( '%1$s', percentage_complete ) ); + $( '.checkforspam' ).text( check_for_spam_buttons.data( 'progress-label' ).replace( '%1$s', percentage_complete ) ); $.post( ajaxurl, @@ -269,12 +341,14 @@ jQuery( function ( $ ) { * @return string The mShot URL; */ function akismet_mshot_url( linkUrl, retry ) { - var mshotUrl = '//s0.wordpress.com/mshots/v1/' + encodeURIComponent( linkUrl ) + '?w=900'; - - if ( retry ) { + var mshotUrl = '//s0.wp.com/mshots/v1/' + encodeURIComponent( linkUrl ) + '?w=900'; + + if ( retry > 1 ) { mshotUrl += '&r=' + encodeURIComponent( retry ); } - + + mshotUrl += '&source=akismet'; + return mshotUrl; } @@ -286,17 +360,10 @@ jQuery( function ( $ ) { function akismet_preload_mshot( linkUrl ) { var img = new Image(); img.src = akismet_mshot_url( linkUrl ); + + preloadedMshotURLs.push( linkUrl ); } - /** - * Sets the comment form privacy notice display to hide when one clicks Core's dismiss button on the related admin notice. - */ - $( '#akismet-privacy-notice-admin-notice' ).on( 'click', '.notice-dismiss', function () { - $.ajax( { - url: './options-general.php?page=akismet-key-config&akismet_comment_form_privacy_notice=hide', - } ); - }); - $( '.akismet-could-be-primary' ).each( function () { var form = $( this ).closest( 'form' ); diff --git a/plugins/akismet/_inc/form.js b/plugins/akismet/_inc/form.js deleted file mode 100644 index 3a5be8af..00000000 --- a/plugins/akismet/_inc/form.js +++ /dev/null @@ -1,30 +0,0 @@ -var ak_js = document.getElementById( "ak_js" ); - -if ( ! ak_js ) { - ak_js = document.createElement( 'input' ); - ak_js.setAttribute( 'id', 'ak_js' ); - ak_js.setAttribute( 'name', 'ak_js' ); - ak_js.setAttribute( 'type', 'hidden' ); -} -else { - ak_js.parentNode.removeChild( ak_js ); -} - -ak_js.setAttribute( 'value', ( new Date() ).getTime() ); - -var commentForm = document.getElementById( 'commentform' ); - -if ( commentForm ) { - commentForm.appendChild( ak_js ); -} -else { - var replyRowContainer = document.getElementById( 'replyrow' ); - - if ( replyRowContainer ) { - var children = replyRowContainer.getElementsByTagName( 'td' ); - - if ( children.length > 0 ) { - children[0].appendChild( ak_js ); - } - } -} \ No newline at end of file diff --git a/plugins/akismet/_inc/img/logo-a-2x.png b/plugins/akismet/_inc/img/logo-a-2x.png new file mode 100644 index 00000000..087144ae Binary files /dev/null and b/plugins/akismet/_inc/img/logo-a-2x.png differ diff --git a/plugins/akismet/akismet.php b/plugins/akismet/akismet.php index 538a7dbc..2175a913 100644 --- a/plugins/akismet/akismet.php +++ b/plugins/akismet/akismet.php @@ -6,7 +6,7 @@ Plugin Name: Akismet Anti-Spam Plugin URI: https://akismet.com/ Description: Used by millions, Akismet is quite possibly the best way in the world to protect your blog from spam. 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. -Version: 4.1.3 +Version: 4.2.1 Author: Automattic Author URI: https://automattic.com/wordpress-plugins/ License: GPLv2 or later @@ -37,8 +37,8 @@ if ( !function_exists( 'add_action' ) ) { exit; } -define( 'AKISMET_VERSION', '4.1.3' ); -define( 'AKISMET__MINIMUM_WP_VERSION', '4.0' ); +define( 'AKISMET_VERSION', '4.2.1' ); +define( 'AKISMET__MINIMUM_WP_VERSION', '5.0' ); define( 'AKISMET__PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'AKISMET_DELETE_LIMIT', 100000 ); diff --git a/plugins/akismet/changelog.txt b/plugins/akismet/changelog.txt new file mode 100644 index 00000000..680bc2e6 --- /dev/null +++ b/plugins/akismet/changelog.txt @@ -0,0 +1,465 @@ +=== Akismet Anti-Spam === + +== Archived Changelog Entries == + +This file contains older changelog entries, so we can keep the size of the standard WordPress readme.txt file reasonable. +For the latest changes, please see the "Changelog" section of the [readme.txt file](https://plugins.svn.wordpress.org/akismet/trunk/readme.txt). + += 4.1.5 = +*Release Date - 29 April 2020* + +* Based on user feedback, we have dropped the in-admin notice explaining the availability of the "privacy notice" option in the AKismet settings screen. The option itself is available, but after displaying the notice for the last 2 years, it is now considered a known fact. +* Updated the "Requires at least" to WP 4.6, based on recommendations from https://wp-info.org/tools/checkplugini18n.php?slug=akismet +* Moved older changelog entries to a separate file to keep the size of this readme reasonable, also based on recommendations from https://wp-info.org/tools/checkplugini18n.php?slug=akismet + += 4.1.4 = +*Release Date - 17 March 2020* + +* Only redirect to the Akismet setup screen upon plugin activation if the plugin was activated manually from within the plugin-related screens, to help users with non-standard install workflows, like WP-CLI. +* Update the layout of the initial setup screen to be more readable on small screens. +* If no API key has been entered, don't run code that expects an API key. +* Improve the readability of the comment history entries. +* Don't modify the comment form HTML if no API key has been set. + += 4.1.3 = +*Release Date - 31 October 2019* + +* Prevented an attacker from being able to cause a user to unknowingly recheck their Pending comments for spam. +* Improved compatibility with Jetpack 7.7+. +* Updated the plugin activation page to use consistent language and markup. +* Redirecting users to the Akismet connnection/settings screen upon plugin activation, in an effort to make it easier for people to get setup. + += 4.1.2 = +*Release Date - 14 May 2019* + +* Fixed a conflict between the Akismet setup banner and other plugin notices. +* Reduced the number of API requests made by the plugin when attempting to verify the API key. +* Include additional data in the pingback pre-check API request to help make the stats more accurate. +* Fixed a bug that was enabling the "Check for Spam" button when no comments were eligible to be checked. +* Improved Akismet's AMP compatibility. + += 4.1.1 = +*Release Date - 31 January 2019* + +* Fixed the "Setup Akismet" notice so it resizes responsively. +* Only highlight the "Save Changes" button in the Akismet config when changes have been made. +* The count of comments in your spam queue shown on the dashboard show now always be up-to-date. + += 4.1 = +*Release Date - 12 November 2018* + +* Added a WP-CLI method for retrieving stats. +* Hooked into the new "Personal Data Eraser" functionality from WordPress 4.9.6. +* Added functionality to clear outdated alerts from Akismet.com. + += 4.0.8 = +*Release Date - 19 June 2018* + +* Improved the grammar and consistency of the in-admin privacy related notes (notice and config). +* Revised in-admin explanation of the comment form privacy notice to make its usage clearer. +* Added `rel="nofollow noopener"` to the comment form privacy notice to improve SEO and security. + += 4.0.7 = +*Release Date - 28 May 2018* + +* Based on user feedback, the link on "Learn how your comment data is processed." in the optional privacy notice now has a `target` of `_blank` and opens in a new tab/window. +* Updated the in-admin privacy notice to use the term "comment" instead of "contact" in "Akismet can display a notice to your users under your comment forms." +* Only show in-admin privacy notice if Akismet has an API Key configured + += 4.0.6 = +*Release Date - 26 May 2018* + +* Moved away from using `empty( get_option() )` to instantiating a variable to be compatible with older versions of PHP (5.3, 5.4, etc). + += 4.0.5 = +*Release Date - 26 May 2018* + +* Corrected version number after tagging. Sorry... + += 4.0.4 = +*Release Date - 26 May 2018* + +* Added a hook to provide Akismet-specific privacy information for a site's privacy policy. +* Added tools to control the display of a privacy related notice under comment forms. +* Fixed HTML in activation failure message to close META and HEAD tag properly. +* Fixed a bug that would sometimes prevent Akismet from being correctly auto-configured. + += 4.0.3 = +*Release Date - 19 February 2018* + +* Added a scheduled task to remove entries in wp_commentmeta that no longer have corresponding comments in wp_comments. +* Added a new `akismet_batch_delete_count` action to the batch delete methods for people who'd like to keep track of the numbers of records being processed by those methods. + += 4.0.2 = +*Release Date - 18 December 2017* + +* Fixed a bug that could cause Akismet to recheck a comment that has already been manually approved or marked as spam. +* Fixed a bug that could cause Akismet to claim that some comments are still waiting to be checked when no comments are waiting to be checked. + += 4.0.1 = +*Release Date - 6 November 2017* + +* Fixed a bug that could prevent some users from connecting Akismet via their Jetpack connection. +* Ensured that any pending Akismet-related events are unscheduled if the plugin is deactivated. +* Allow some JavaScript to be run asynchronously to avoid affecting page render speeds. + += 4.0 = +*Release Date - 19 September 2017* + +* Added REST API endpoints for configuring Akismet and retrieving stats. +* Increased the minimum supported WordPress version to 4.0. +* Added compatibility with comments submitted via the REST API. +* Improved the progress indicator on the "Check for Spam" button. + += 3.3.4 = +*Release Date - 3 August 2017* + +* Disabled Akismet's debug log output by default unless AKISMET_DEBUG is defined. +* URL previews now begin preloading when the mouse moves near them in the comments section of wp-admin. +* When a comment is caught by the Comment Blacklist, Akismet will always allow it to stay in the trash even if it is spam as well. +* Fixed a bug that was preventing an error from being shown when a site can't reach Akismet's servers. + += 3.3.3 = +*Release Date - 13 July 2017* + +* Reduced amount of bandwidth used by the URL Preview feature. +* Improved the admin UI when the API key is manually pre-defined for the site. +* Removed a workaround for WordPress installations older than 3.3 that will improve Akismet's compatibility with other plugins. +* The number of spam blocked that is displayed on the WordPress dashboard will now be more accurate and updated more frequently. +* Fixed a bug in the Akismet widget that could cause PHP warnings. + += 3.3.2 = +*Release Date - 10 May 2017* + +* Fixed a bug causing JavaScript errors in some browsers. + += 3.3.1 = +*Release Date - 2 May 2017* + +* Improve performance by only requesting the akismet_comment_nonce option when absolutely necessary. +* Fixed two bugs that could cause PHP warnings. +* Fixed a bug that was preventing the "Remove author URL" feature from working after a comment was edited using "Quick Edit." +* Fixed a bug that was preventing the URL preview feature from working after a comment was edited using "Quick Edit." + += 3.3 = +*Release Date - 23 February 2017* + +* Updated the Akismet admin pages with a new clean design. +* Fixed bugs preventing the `akismet_add_comment_nonce` and `akismet_update_alert` wrapper functions from working properly. +* Fixed bug preventing the loading indicator from appearing when re-checking all comments for spam. +* Added a progress indicator to the "Check for Spam" button. +* Added a success message after manually rechecking the Pending queue for spam. + += 3.2 = +*Release Date - 6 September 2016* + +* Added a WP-CLI module. You can now check comments and recheck the moderation queue from the command line. +* Stopped using the deprecated jQuery function `.live()`. +* Fixed a bug in `remove_comment_author_url()` and `add_comment_author_url()` that could generate PHP notices. +* Fixed a bug that could cause an infinite loop for sites with very very very large comment IDs. +* Fixed a bug that could cause the Akismet widget title to be blank. + += 3.1.11 = +*Release Date - 12 May 2016* + +* Fixed a bug that could cause the "Check for Spam" button to skip some comments. +* Fixed a bug that could prevent some spam submissions from being sent to Akismet. +* Updated all links to use https:// when possible. +* Disabled Akismet debug logging unless WP_DEBUG and WP_DEBUG_LOG are both enabled. + += 3.1.10 = +*Release Date - 1 April 2016* + +* Fixed a bug that could cause comments caught as spam to be placed in the Pending queue. +* Fixed a bug that could have resulted in comments that were caught by the core WordPress comment blacklist not to have a corresponding History entry. +* Fixed a bug that could have caused avoidable PHP warnings in the error log. + += 3.1.9 = +*Release Date - 28 March 2016* + +* Add compatibility with Jetpack so that Jetpack can automatically configure Akismet settings when appropriate. +* Fixed a bug preventing some comment data from being sent to Akismet. + += 3.1.8 = +*Release Date - 4 March 2016* + +* Fixed a bug preventing Akismet from being used with some plugins that rewrite admin URLs. +* Reduced the amount of bandwidth used on Akismet API calls +* Reduced the amount of space Akismet uses in the database +* Fixed a bug that could cause comments caught as spam to be placed in the Pending queue. + += 3.1.7 = +*Release Date - 4 January 2016* + +* Added documentation for the 'akismet_comment_nonce' filter. +* The post-install activation button is now accessible to screen readers and keyboard-only users. +* Fixed a bug that was preventing the "Remove author URL" feature from working in WordPress 4.4 + += 3.1.6 = +*Release Date - 14 December 2015* + +* Improve the notices shown after activating Akismet. +* Update some strings to allow for the proper plural forms in all languages. + += 3.1.5 = +*Release Date - 13 October 2015* + +* Closes a potential XSS vulnerability. + += 3.1.4 = +*Release Date - 24 September 2015* + +* Fixed a bug that was preventing some users from automatically connecting using Jetpack if they didn't have a current Akismet subscription. +* Fixed a bug that could cause comments caught as spam to be placed in the Pending queue. +* Error messages and instructions have been simplified to be more understandable. +* Link previews are enabled for all links inside comments, not just the author's website link. + += 3.1.3 = +*Release Date - 6 July 2015* + +* Notify users when their account status changes after previously being successfully set up. This should help any users who are seeing blank Akismet settings screens. + += 3.1.2 = +*Release Date - 7 June 2015* + +* Reduced the amount of space Akismet uses in the commentmeta table. +* Fixed a bug where some comments with quotes in the author name weren't getting history entries +* Pre-emptive security improvements to ensure that the Akismet plugin can't be used by attackers to compromise a WordPress installation. +* Better UI for the key entry field: allow whitespace to be included at the beginning or end of the key and strip it out automatically when the form is submitted. +* When deactivating the plugin, notify the Akismet API so the site can be marked as inactive. +* Clearer error messages. + += 3.1.1 = +*Release Date - 17th March, 2015* + +* Improvements to the "Remove comment author URL" JavaScript +* Include the pingback pre-check from the 2.6 branch. + += 3.1 = +*Release Date - 11th March, 2015* + +* Use HTTPS by default for all requests to Akismet. +* Fix for a situation where Akismet might strip HTML from a comment. + += 3.0.4 = +*Release Date - 11th December, 2014* + +* Fix to make .htaccess compatible with Apache 2.4. +* Fix to allow removal of https author URLs. +* Fix to avoid stripping part of the author URL when removing and re-adding. +* Removed the "Check for Spam" button from the "Trash" and "Approved" queues, where it would have no effect. +* Allow automatic API key configuration when Jetpack is installed and connected to a WordPress.com account + += 3.0.3 = +*Release Date - 3rd November, 2014* + +* Fix for sending the wrong data to delete_comment action that could have prevented old spam comments from being deleted. +* Added a filter to disable logging of Akismet debugging information. +* Added a filter for the maximum comment age when deleting old spam comments. +* Added a filter for the number per batch when deleting old spam comments. +* Removed the "Check for Spam" button from the Spam folder. + += 3.0.2 = +*Release Date - 18th August, 2014* + +* Performance improvements. +* Fixed a bug that could truncate the comment data being sent to Akismet for checking. + += 3.0.1 = +*Release Date - 9th July, 2014* + +* Removed dependency on PHP's fsockopen function +* Fix spam/ham reports to work when reported outside of the WP dashboard, e.g., from Notifications or the WP app +* Remove jQuery dependency for comment form JavaScript +* Remove unnecessary data from some Akismet comment meta +* Suspended keys will now result in all comments being put in moderation, not spam. + += 3.0.0 = +*Release Date - 15th April, 2014* + +* Move Akismet to Settings menu +* Drop Akismet Stats menu +* Add stats snapshot to Akismet settings +* Add Akismet subscription details and status to Akismet settings +* Add contextual help for each page +* Improve Akismet setup to use Jetpack to automate plugin setup +* Fix "Check for Spam" to use AJAX to avoid page timing out +* Fix Akismet settings page to be responsive +* Drop legacy code +* Tidy up CSS and Javascript +* Replace the old discard setting with a new "discard pervasive spam" feature. + += 2.6.0 = +*Release Date - 18th March, 2014* + +* Add ajax paging to the check for spam button to handle large volumes of comments +* Optimize javascript and add localization support +* Fix bug in link to spam comments from right now dashboard widget +* Fix bug with deleting old comments to avoid timeouts dealing with large volumes of comments +* Include X-Pingback-Forwarded-For header in outbound WordPress pingback verifications +* Add pre-check for pingbacks, to stop spam before an outbound verification request is made + += 2.5.9 = +*Release Date - 1st August, 2013* + +* Update 'Already have a key' link to redirect page rather than depend on javascript +* Fix some non-translatable strings to be translatable +* Update Activation banner in plugins page to redirect user to Akismet config page + += 2.5.8 = +*Release Date - 20th January, 2013* + +* Simplify the activation process for new users +* Remove the reporter_ip parameter +* Minor preventative security improvements + += 2.5.7 = +*Release Date - 13th December, 2012* + +* FireFox Stats iframe preview bug +* Fix mshots preview when using https +* Add .htaccess to block direct access to files +* Prevent some PHP notices +* Fix Check For Spam return location when referrer is empty +* Fix Settings links for network admins +* Fix prepare() warnings in WP 3.5 + += 2.5.6 = +*Release Date - 26th April, 2012* + +* Prevent retry scheduling problems on sites where wp_cron is misbehaving +* Preload mshot previews +* Modernize the widget code +* Fix a bug where comments were not held for moderation during an error condition +* Improve the UX and display when comments are temporarily held due to an error +* Make the Check For Spam button force a retry when comments are held due to an error +* Handle errors caused by an invalid key +* Don't retry comments that are too old +* Improve error messages when verifying an API key + += 2.5.5 = +*Release Date - 11th January, 2012* + +* Add nonce check for comment author URL remove action +* Fix the settings link + += 2.5.4 = +*Release Date - 5th January, 2012* + +* Limit Akismet CSS and Javascript loading in wp-admin to just the pages that need it +* Added author URL quick removal functionality +* Added mShot preview on Author URL hover +* Added empty index.php to prevent directory listing +* Move wp-admin menu items under Jetpack, if it is installed +* Purge old Akismet comment meta data, default of 15 days + += 2.5.3 = +*Release Date - 8th Febuary, 2011* + +* Specify the license is GPL v2 or later +* Fix a bug that could result in orphaned commentmeta entries +* Include hotfix for WordPress 3.0.5 filter issue + += 2.5.2 = +*Release Date - 14th January, 2011* + +* Properly format the comment count for author counts +* Look for super admins on multisite installs when looking up user roles +* Increase the HTTP request timeout +* Removed padding for author approved count +* Fix typo in function name +* Set Akismet stats iframe height to fixed 2500px. Better to have one tall scroll bar than two side by side. + += 2.5.1 = +*Release Date - 17th December, 2010* + +* Fix a bug that caused the "Auto delete" option to fail to discard comments correctly +* Remove the comment nonce form field from the 'Akismet Configuration' page in favor of using a filter, akismet_comment_nonce +* Fixed padding bug in "author" column of posts screen +* Added margin-top to "cleared by ..." badges on dashboard +* Fix possible error when calling akismet_cron_recheck() +* Fix more PHP warnings +* Clean up XHTML warnings for comment nonce +* Fix for possible condition where scheduled comment re-checks could get stuck +* Clean up the comment meta details after deleting a comment +* Only show the status badge if the comment status has been changed by someone/something other than Akismet +* Show a 'History' link in the row-actions +* Translation fixes +* Reduced font-size on author name +* Moved "flagged by..." notification to top right corner of comment container and removed heavy styling +* Hid "flagged by..." notification while on dashboard + += 2.5.0 = +*Release Date - 7th December, 2010* + +* Track comment actions under 'Akismet Status' on the edit comment screen +* Fix a few remaining deprecated function calls ( props Mike Glendinning ) +* Use HTTPS for the stats IFRAME when wp-admin is using HTTPS +* Use the WordPress HTTP class if available +* Move the admin UI code to a separate file, only loaded when needed +* Add cron retry feature, to replace the old connectivity check +* Display Akismet status badge beside each comment +* Record history for each comment, and display it on the edit page +* Record the complete comment as originally submitted in comment_meta, to use when reporting spam and ham +* Highlight links in comment content +* New option, "Show the number of comments you've approved beside each comment author." +* New option, "Use a nonce on the comment form." + += 2.4.0 = +*Release Date - 23rd August, 2010* + +* Spell out that the license is GPLv2 +* Fix PHP warnings +* Fix WordPress deprecated function calls +* Fire the delete_comment action when deleting comments +* Move code specific for older WP versions to legacy.php +* General code clean up + += 2.3.0 = +*Release Date - 5th June, 2010* + +* Fix "Are you sure" nonce message on config screen in WPMU +* Fix XHTML compliance issue in sidebar widget +* Change author link; remove some old references to WordPress.com accounts +* Localize the widget title (core ticket #13879) + += 2.2.9 = +*Release Date - 2nd June, 2010* + +* Eliminate a potential conflict with some plugins that may cause spurious reports + += 2.2.8 = +*Release Date - 27th May, 2010* + +* Fix bug in initial comment check for ipv6 addresses +* Report comments as ham when they are moved from spam to moderation +* Report comments as ham when clicking undo after spam +* Use transition_comment_status action when available instead of older actions for spam/ham submissions +* Better diagnostic messages when PHP network functions are unavailable +* Better handling of comments by logged-in users + += 2.2.7 = +*Release Date - 17th December, 2009* + +* Add a new AKISMET_VERSION constant +* Reduce the possibility of over-counting spam when another spam filter plugin is in use +* Disable the connectivity check when the API key is hard-coded for WPMU + += 2.2.6 = +*Release Date - 20th July, 2009* + +* Fix a global warning introduced in 2.2.5 +* Add changelog and additional readme.txt tags +* Fix an array conversion warning in some versions of PHP +* Support a new WPCOM_API_KEY constant for easier use with WordPress MU + += 2.2.5 = +*Release Date - 13th July, 2009* + +* Include a new Server Connectivity diagnostic check, to detect problems caused by firewalls + += 2.2.4 = +*Release Date - 3rd June, 2009* + +* Fixed a key problem affecting the stats feature in WordPress MU +* Provide additional blog information in Akismet API calls diff --git a/plugins/akismet/class.akismet-admin.php b/plugins/akismet/class.akismet-admin.php index b5e2ef8e..c6cb1355 100644 --- a/plugins/akismet/class.akismet-admin.php +++ b/plugins/akismet/class.akismet-admin.php @@ -32,10 +32,6 @@ class Akismet_Admin { if ( isset( $_POST['action'] ) && $_POST['action'] == 'enter-key' ) { self::enter_api_key(); } - - if ( ! empty( $_GET['akismet_comment_form_privacy_notice'] ) && empty( $_GET['settings-updated']) ) { - self::set_form_privacy_notice_option( $_GET['akismet_comment_form_privacy_notice'] ); - } } public static function init_hooks() { @@ -70,11 +66,6 @@ class Akismet_Admin { add_filter( 'all_plugins', array( 'Akismet_Admin', 'modify_plugin_description' ) ); - if ( class_exists( 'Jetpack' ) ) { - add_filter( 'akismet_comment_form_privacy_notice_url_display', array( 'Akismet_Admin', 'jetpack_comment_form_privacy_notice_url' ) ); - add_filter( 'akismet_comment_form_privacy_notice_url_hide', array( 'Akismet_Admin', 'jetpack_comment_form_privacy_notice_url' ) ); - } - // priority=1 because we need ours to run before core's comment anonymizer runs, and that's registered at priority=10 add_filter( 'wp_privacy_personal_data_erasers', array( 'Akismet_Admin', 'register_personal_data_eraser' ), 1 ); } @@ -146,7 +137,7 @@ class Akismet_Admin { wp_register_script( 'akismet.js', plugin_dir_url( __FILE__ ) . '_inc/akismet.js', array('jquery'), AKISMET_VERSION ); wp_enqueue_script( 'akismet.js' ); - + $inline_js = array( 'comment_author_url_nonce' => wp_create_nonce( 'comment_author_url_nonce' ), 'strings' => array( @@ -162,6 +153,10 @@ class Akismet_Admin { $inline_js['start_recheck'] = true; } + if ( apply_filters( 'akismet_enable_mshots', true ) ) { + $inline_js['enable_mshots'] = true; + } + wp_localize_script( 'akismet.js', 'WPAkismet', $inline_js ); } } @@ -392,27 +387,40 @@ class Akismet_Admin { return; } - $link = add_query_arg( array( 'action' => 'akismet_recheck_queue' ), admin_url( 'admin.php' ) ); + $link = ''; $comments_count = wp_count_comments(); echo ''; echo '
'; + + $classes = array( + 'button-secondary', + 'checkforspam', + 'button-disabled' // Disable button until the page is loaded + ); + + if ( $comments_count->moderated > 0 ) { + $classes[] = 'enable-on-load'; + + if ( ! Akismet::get_api_key() ) { + $link = add_query_arg( array( 'page' => 'akismet-key-config' ), class_exists( 'Jetpack' ) ? admin_url( 'admin.php' ) : admin_url( 'options-general.php' ) ); + $classes[] = 'ajax-disabled'; + } + } + echo ''; - echo '' . esc_html__('Check for Spam', 'akismet') . ''; - echo ''; - echo ''; + ' . ( ! in_array( 'ajax-disabled', $classes ) ? 'onclick="return false;"' : '' ) . ' + >' . esc_html__('Check for Spam', 'akismet') . ''; echo ''; - } public static function recheck_queue() { @@ -575,10 +583,8 @@ class Akismet_Admin { $history = Akismet::get_comment_history( $comment->comment_ID ); if ( $history ) { - echo '
'; - foreach ( $history as $row ) { - $time = date( 'D d M Y @ h:i:m a', $row['time'] ) . ' GMT'; + $time = date( 'D d M Y @ h:i:s a', $row['time'] ) . ' GMT'; $message = ''; @@ -588,56 +594,67 @@ class Akismet_Admin { // 1) Save space. // 2) The message can be translated into the current language of the blog, not stuck // in the language of the blog when the comment was made. - $message = $row['message']; + $message = esc_html( $row['message'] ); } // If possible, use a current translation. switch ( $row['event'] ) { case 'recheck-spam'; - $message = __( 'Akismet re-checked and caught this comment as spam.', 'akismet' ); + $message = esc_html( __( 'Akismet re-checked and caught this comment as spam.', 'akismet' ) ); break; case 'check-spam': - $message = __( 'Akismet caught this comment as spam.', 'akismet' ); + $message = esc_html( __( 'Akismet caught this comment as spam.', 'akismet' ) ); break; case 'recheck-ham': - $message = __( 'Akismet re-checked and cleared this comment.', 'akismet' ); + $message = esc_html( __( 'Akismet re-checked and cleared this comment.', 'akismet' ) ); break; case 'check-ham': - $message = __( 'Akismet cleared this comment.', 'akismet' ); + $message = esc_html( __( 'Akismet cleared this comment.', 'akismet' ) ); break; case 'wp-blacklisted': - $message = __( 'Comment was caught by wp_blacklist_check.', 'akismet' ); + case 'wp-disallowed': + $message = sprintf( + /* translators: The placeholder is a WordPress PHP function name. */ + esc_html( __( 'Comment was caught by %s.', 'akismet' ) ), + function_exists( 'wp_check_comment_disallowed_list' ) ? 'wp_check_comment_disallowed_list' : 'wp_blacklist_check' + ); break; case 'report-spam': if ( isset( $row['user'] ) ) { - $message = sprintf( __( '%s reported this comment as spam.', 'akismet' ), $row['user'] ); + $message = esc_html( sprintf( __( '%s reported this comment as spam.', 'akismet' ), $row['user'] ) ); } else if ( ! $message ) { - $message = __( 'This comment was reported as spam.', 'akismet' ); + $message = esc_html( __( 'This comment was reported as spam.', 'akismet' ) ); } break; case 'report-ham': if ( isset( $row['user'] ) ) { - $message = sprintf( __( '%s reported this comment as not spam.', 'akismet' ), $row['user'] ); + $message = esc_html( sprintf( __( '%s reported this comment as not spam.', 'akismet' ), $row['user'] ) ); } else if ( ! $message ) { - $message = __( 'This comment was reported as not spam.', 'akismet' ); + $message = esc_html( __( 'This comment was reported as not spam.', 'akismet' ) ); } break; case 'cron-retry-spam': - $message = __( 'Akismet caught this comment as spam during an automatic retry.' , 'akismet'); + $message = esc_html( __( 'Akismet caught this comment as spam during an automatic retry.' , 'akismet') ); break; case 'cron-retry-ham': - $message = __( 'Akismet cleared this comment during an automatic retry.', 'akismet'); + $message = esc_html( __( 'Akismet cleared this comment during an automatic retry.', 'akismet') ); break; case 'check-error': if ( isset( $row['meta'], $row['meta']['response'] ) ) { - $message = sprintf( __( 'Akismet was unable to check this comment (response: %s) but will automatically retry later.', 'akismet'), $row['meta']['response'] ); + $message = sprintf( esc_html( __( 'Akismet was unable to check this comment (response: %s) but will automatically retry later.', 'akismet') ), '' . esc_html( $row['meta']['response'] ) . '' ); + } + else { + $message = esc_html( __( 'Akismet was unable to check this comment but will automatically retry later.', 'akismet' ) ); } break; case 'recheck-error': if ( isset( $row['meta'], $row['meta']['response'] ) ) { - $message = sprintf( __( 'Akismet was unable to recheck this comment (response: %s).', 'akismet'), $row['meta']['response'] ); + $message = sprintf( esc_html( __( 'Akismet was unable to recheck this comment (response: %s).', 'akismet') ), '' . esc_html( $row['meta']['response'] ) . '' ); + } + else { + $message = esc_html( __( 'Akismet was unable to recheck this comment.', 'akismet' ) ); } break; default: @@ -645,27 +662,32 @@ class Akismet_Admin { // Half of these used to be saved without the dash after 'status-changed'. // See https://plugins.trac.wordpress.org/changeset/1150658/akismet/trunk $new_status = preg_replace( '/^status-changed-?/', '', $row['event'] ); - $message = sprintf( __( 'Comment status was changed to %s', 'akismet' ), $new_status ); + $message = sprintf( esc_html( __( 'Comment status was changed to %s', 'akismet' ) ), '' . esc_html( $new_status ) . '' ); } else if ( preg_match( '/^status-/', $row['event'] ) ) { $new_status = preg_replace( '/^status-/', '', $row['event'] ); if ( isset( $row['user'] ) ) { - $message = sprintf( __( '%1$s changed the comment status to %2$s.', 'akismet' ), $row['user'], $new_status ); + $message = sprintf( esc_html( __( '%1$s changed the comment status to %2$s.', 'akismet' ) ), $row['user'], '' . esc_html( $new_status ) . '' ); } } break; } - echo '
'; + if ( ! empty( $message ) ) { + echo '

'; echo '' . sprintf( esc_html__('%s ago', 'akismet'), human_time_diff( $row['time'] ) ) . ''; echo ' - '; - echo esc_html( $message ); - echo '

'; + echo $message; // esc_html() is done above so that we can use HTML in some messages. + echo '

'; + } } - - echo '
'; + } + else { + echo '

'; + echo esc_html( __( 'No comment history.', 'akismet' ) ); + echo '

'; } } @@ -866,12 +888,21 @@ class Akismet_Admin { ) ); } - public static function display_privacy_notice_control_warning() { - if ( !current_user_can( 'manage_options' ) ) - return; - Akismet::view( 'notice', array( - 'type' => 'privacy', - ) ); + public static function get_usage_limit_alert_data() { + return array( + 'type' => 'usage-limit', + 'code' => (int) get_option( 'akismet_alert_code' ), + 'msg' => get_option( 'akismet_alert_msg' ), + 'api_calls' => get_option( 'akismet_alert_api_calls' ), + 'usage_limit' => get_option( 'akismet_alert_usage_limit' ), + 'upgrade_plan' => get_option( 'akismet_alert_upgrade_plan' ), + 'upgrade_url' => get_option( 'akismet_alert_upgrade_url' ), + 'upgrade_type' => get_option( 'akismet_alert_upgrade_type' ), + ); + } + + public static function display_usage_limit_alert() { + Akismet::view( 'notice', self::get_usage_limit_alert_data() ); } public static function display_spam_check_warning() { @@ -1007,8 +1038,9 @@ class Akismet_Admin { $notices[] = array( 'type' => $akismet_user->status ); } - if ( false === get_option( 'akismet_comment_form_privacy_notice' ) ) { - $notices[] = array( 'type' => 'privacy' ); + $alert_code = get_option( 'akismet_alert_code' ); + if ( isset( Akismet::$limit_notices[ $alert_code ] ) ) { + $notices[] = self::get_usage_limit_alert_data(); } /* @@ -1030,6 +1062,7 @@ class Akismet_Admin { $notices[] = array( 'type' => 'new-key-failed' ); $notices[] = array( 'type' => 'limit-reached', 'level' => 'yellow' ); $notices[] = array( 'type' => 'limit-reached', 'level' => 'red' ); + $notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_plan' => 'Enterprise', 'upgrade_url' => 'https://akismet.com/account/' ); */ Akismet::log( compact( 'stat_totals', 'akismet_user' ) ); @@ -1046,22 +1079,28 @@ class Akismet_Admin { if ( in_array( $hook_suffix, array( 'edit-comments.php' ) ) && (int) get_option( 'akismet_alert_code' ) > 0 ) { Akismet::verify_key( Akismet::get_api_key() ); //verify that the key is still in alert state - - if ( get_option( 'akismet_alert_code' ) > 0 ) + + $alert_code = get_option( 'akismet_alert_code' ); + if ( isset( Akismet::$limit_notices[ $alert_code ] ) ) { + self::display_usage_limit_alert(); + } elseif ( $alert_code > 0 ) { self::display_alert(); + } } - elseif ( $hook_suffix == 'plugins.php' && !Akismet::get_api_key() ) { + elseif ( ( 'plugins.php' === $hook_suffix || 'edit-comments.php' === $hook_suffix ) && ! Akismet::get_api_key() ) { + // Show the "Set Up Akismet" banner on the comments and plugin pages if no API key has been set. self::display_api_key_warning(); } elseif ( $hook_suffix == 'edit-comments.php' && wp_next_scheduled( 'akismet_schedule_cron_recheck' ) ) { self::display_spam_check_warning(); } - else if ( isset( $_GET['akismet_recheck_complete'] ) ) { + + if ( isset( $_GET['akismet_recheck_complete'] ) ) { $recheck_count = (int) $_GET['recheck_count']; $spam_count = (int) $_GET['spam_count']; if ( $recheck_count === 0 ) { - $message = __( 'There were no comments to check. Akismet will only check comments in the Pending queue.', 'akismet' ); + $message = __( 'There were no comments to check. Akismet will only check comments awaiting moderation.', 'akismet' ); } else { $message = sprintf( _n( 'Akismet checked %s comment.', 'Akismet checked %s comments.', $recheck_count, 'akismet' ), number_format( $recheck_count ) ); @@ -1080,14 +1119,6 @@ class Akismet_Admin { else if ( isset( $_GET['akismet_recheck_error'] ) ) { echo '

' . esc_html( __( 'Akismet could not recheck your comments for spam.', 'akismet' ) ) . '

'; } - - $akismet_comment_form_privacy_notice_option = get_option( 'akismet_comment_form_privacy_notice' ); - if ( ! in_array( $akismet_comment_form_privacy_notice_option, array( 'hide', 'display' ) ) ) { - $api_key = Akismet::get_api_key(); - if ( ! empty( $api_key ) ) { - self::display_privacy_notice_control_warning(); - } - } } public static function display_status() { @@ -1127,7 +1158,7 @@ class Akismet_Admin { if ( !class_exists('Jetpack') ) return false; - if ( defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '7.7', '<' ) ) { + if ( defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '7.7', '<' ) ) { // For version of Jetpack prior to 7.7. Jetpack::load_xml_rpc_client(); } @@ -1202,10 +1233,6 @@ class Akismet_Admin { update_option( 'akismet_comment_form_privacy_notice', $state ); } } - - public static function jetpack_comment_form_privacy_notice_url( $url ) { - return str_replace( 'options-general.php', 'admin.php', $url ); - } public static function register_personal_data_eraser( $erasers ) { $erasers['akismet'] = array( diff --git a/plugins/akismet/class.akismet-widget.php b/plugins/akismet/class.akismet-widget.php index 55b0f35c..9f0458b0 100644 --- a/plugins/akismet/class.akismet-widget.php +++ b/plugins/akismet/class.akismet-widget.php @@ -99,7 +99,29 @@ class Akismet_Widget extends WP_Widget { ?> 'FIRST_MONTH_OVER_LIMIT', + 10502 => 'SECOND_MONTH_OVER_LIMIT', + 10504 => 'THIRD_MONTH_APPROACHING_LIMIT', + 10508 => 'THIRD_MONTH_OVER_LIMIT', + 10516 => 'FOUR_PLUS_MONTHS_OVER_LIMIT', + ); + private static $last_comment = ''; private static $initiated = false; private static $prevent_moderation_email_for_these_comments = array(); private static $last_comment_result = null; 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' => '' ); - private static $is_rest_api_call = false; public static function init() { if ( ! self::$initiated ) { @@ -34,11 +41,7 @@ class Akismet { add_action( 'akismet_schedule_cron_recheck', array( 'Akismet', 'cron_recheck' ) ); add_action( 'comment_form', array( 'Akismet', 'add_comment_nonce' ), 1 ); - - add_action( 'admin_head-edit-comments.php', array( 'Akismet', 'load_form_js' ) ); - add_action( 'comment_form', array( 'Akismet', 'load_form_js' ) ); - add_action( 'comment_form', array( 'Akismet', 'inject_ak_js' ) ); - add_filter( 'script_loader_tag', array( 'Akismet', 'set_form_js_async' ), 10, 3 ); + add_action( 'comment_form', array( 'Akismet', 'output_custom_form_fields' ) ); add_filter( 'comment_moderation_recipients', array( 'Akismet', 'disable_moderation_emails_if_unreachable' ), 1000, 2 ); add_filter( 'pre_comment_approved', array( 'Akismet', 'last_comment_status' ), 10, 2 ); @@ -47,9 +50,20 @@ class Akismet { // Run this early in the pingback call, before doing a remote fetch of the source uri add_action( 'xmlrpc_call', array( 'Akismet', 'pre_check_pingback' ) ); - + // Jetpack compatibility add_filter( 'jetpack_options_whitelist', array( 'Akismet', 'add_to_jetpack_options_whitelist' ) ); + add_filter( 'jetpack_contact_form_html', array( 'Akismet', 'inject_custom_form_fields' ) ); + add_filter( 'jetpack_contact_form_akismet_values', array( 'Akismet', 'prepare_custom_form_values' ) ); + + // Gravity Forms + add_filter( 'gform_get_form_filter', array( 'Akismet', 'inject_custom_form_fields' ) ); + add_filter( 'gform_akismet_fields', array( 'Akismet', 'prepare_custom_form_values' ) ); + + // Contact Form 7 + add_filter( 'wpcf7_form_elements', array( 'Akismet', 'append_custom_form_fields' ) ); + add_filter( 'wpcf7_akismet_parameters', array( 'Akismet', 'prepare_custom_form_values' ) ); + add_action( 'update_option_wordpress_api_key', array( 'Akismet', 'updated_option' ), 10, 2 ); add_action( 'add_option_wordpress_api_key', array( 'Akismet', 'added_option' ), 10, 2 ); @@ -131,12 +145,23 @@ class Akismet { } public static function rest_auto_check_comment( $commentdata ) { - self::$is_rest_api_call = true; - - return self::auto_check_comment( $commentdata ); + return self::auto_check_comment( $commentdata, 'rest_api' ); } - public static function auto_check_comment( $commentdata ) { + /** + * Check a comment for spam. + * + * @param array $commentdata + * @param string $context What kind of request triggered this comment check? Possible values are 'default', 'rest_api', and 'xml-rpc'. + * @return array|WP_Error Either the $commentdata array with additional entries related to its spam status + * or a WP_Error, if it's a REST API request and the comment should be discarded. + */ + public static function auto_check_comment( $commentdata, $context = 'default' ) { + // If no key is configured, then there's no point in doing any of this. + if ( ! self::get_api_key() ) { + return $commentdata; + } + self::$last_comment_result = null; $comment = $commentdata; @@ -202,7 +227,15 @@ class Akismet { do_action( 'akismet_comment_check_response', $response ); $commentdata['comment_as_submitted'] = array_intersect_key( $comment, self::$comment_as_submitted_allowed_keys ); - $commentdata['akismet_result'] = $response[1]; + + // Also include any form fields we inject into the comment form, like ak_js + foreach ( $_POST as $key => $value ) { + if ( is_string( $value ) && strpos( $key, 'ak_' ) === 0 ) { + $commentdata['comment_as_submitted'][ 'POST_' . $key ] = $value; + } + } + + $commentdata['akismet_result'] = $response[1]; if ( isset( $response[0]['x-akismet-pro-tip'] ) ) $commentdata['akismet_pro_tip'] = $response[0]['x-akismet-pro-tip']; @@ -227,17 +260,19 @@ class Akismet { update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr ); } - if ( self::$is_rest_api_call ) { + if ( 'rest_api' === $context ) { return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) ); - } - else { + } else if ( 'xml-rpc' === $context ) { + // If this is a pingback that we're pre-checking, the discard behavior is the same as the normal spam response behavior. + return $commentdata; + } else { // Redirect back to the previous page, or failing that, the post permalink, or failing that, the homepage of the blog. $redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ( $post ? get_permalink( $post ) : home_url() ); wp_safe_redirect( esc_url_raw( $redirect_to ) ); die(); } } - else if ( self::$is_rest_api_call ) { + else if ( 'rest_api' === $context ) { // The way the REST API structures its calls, we can set the comment_approved value right away. $commentdata['comment_approved'] = 'spam'; } @@ -296,48 +331,56 @@ class Akismet { // as was checked by auto_check_comment if ( is_object( $comment ) && !empty( self::$last_comment ) && is_array( self::$last_comment ) ) { if ( self::matches_last_comment( $comment ) ) { - - load_plugin_textdomain( 'akismet' ); - - // normal result: true or false - if ( self::$last_comment['akismet_result'] == 'true' ) { - update_comment_meta( $comment->comment_ID, 'akismet_result', 'true' ); - self::update_comment_history( $comment->comment_ID, '', 'check-spam' ); - if ( $comment->comment_approved != 'spam' ) - self::update_comment_history( - $comment->comment_ID, - '', - 'status-changed-'.$comment->comment_approved - ); - } - elseif ( self::$last_comment['akismet_result'] == 'false' ) { - update_comment_meta( $comment->comment_ID, 'akismet_result', 'false' ); - self::update_comment_history( $comment->comment_ID, '', 'check-ham' ); - // Status could be spam or trash, depending on the WP version and whether this change applies: - // https://core.trac.wordpress.org/changeset/34726 - if ( $comment->comment_approved == 'spam' || $comment->comment_approved == 'trash' ) { - if ( wp_blacklist_check($comment->comment_author, $comment->comment_author_email, $comment->comment_author_url, $comment->comment_content, $comment->comment_author_IP, $comment->comment_agent) ) - self::update_comment_history( $comment->comment_ID, '', 'wp-blacklisted' ); - else - self::update_comment_history( $comment->comment_ID, '', 'status-changed-'.$comment->comment_approved ); - } - } // abnormal result: error - else { - update_comment_meta( $comment->comment_ID, 'akismet_error', time() ); + load_plugin_textdomain( 'akismet' ); + + // normal result: true or false + if ( self::$last_comment['akismet_result'] == 'true' ) { + update_comment_meta( $comment->comment_ID, 'akismet_result', 'true' ); + self::update_comment_history( $comment->comment_ID, '', 'check-spam' ); + if ( $comment->comment_approved != 'spam' ) { self::update_comment_history( $comment->comment_ID, '', - 'check-error', - array( 'response' => substr( self::$last_comment['akismet_result'], 0, 50 ) ) + 'status-changed-' . $comment->comment_approved ); } + } elseif ( self::$last_comment['akismet_result'] == 'false' ) { + update_comment_meta( $comment->comment_ID, 'akismet_result', 'false' ); + self::update_comment_history( $comment->comment_ID, '', 'check-ham' ); + // Status could be spam or trash, depending on the WP version and whether this change applies: + // https://core.trac.wordpress.org/changeset/34726 + if ( $comment->comment_approved == 'spam' || $comment->comment_approved == 'trash' ) { + if ( function_exists( 'wp_check_comment_disallowed_list' ) ) { + if ( wp_check_comment_disallowed_list( $comment->comment_author, $comment->comment_author_email, $comment->comment_author_url, $comment->comment_content, $comment->comment_author_IP, $comment->comment_agent ) ) { + self::update_comment_history( $comment->comment_ID, '', 'wp-disallowed' ); + } else { + self::update_comment_history( $comment->comment_ID, '', 'status-changed-' . $comment->comment_approved ); + } + } else if ( function_exists( 'wp_blacklist_check' ) && wp_blacklist_check( $comment->comment_author, $comment->comment_author_email, $comment->comment_author_url, $comment->comment_content, $comment->comment_author_IP, $comment->comment_agent ) ) { + self::update_comment_history( $comment->comment_ID, '', 'wp-blacklisted' ); + } else { + self::update_comment_history( $comment->comment_ID, '', 'status-changed-' . $comment->comment_approved ); + } + } + } else { + // abnormal result: error + update_comment_meta( $comment->comment_ID, 'akismet_error', time() ); + self::update_comment_history( + $comment->comment_ID, + '', + 'check-error', + array( 'response' => substr( self::$last_comment['akismet_result'], 0, 50 ) ) + ); + } - // record the complete original data as submitted for checking - if ( isset( self::$last_comment['comment_as_submitted'] ) ) - update_comment_meta( $comment->comment_ID, 'akismet_as_submitted', self::$last_comment['comment_as_submitted'] ); + // record the complete original data as submitted for checking + if ( isset( self::$last_comment['comment_as_submitted'] ) ) { + update_comment_meta( $comment->comment_ID, 'akismet_as_submitted', self::$last_comment['comment_as_submitted'] ); + } - if ( isset( self::$last_comment['akismet_pro_tip'] ) ) - update_comment_meta( $comment->comment_ID, 'akismet_pro_tip', self::$last_comment['akismet_pro_tip'] ); + if ( isset( self::$last_comment['akismet_pro_tip'] ) ) { + update_comment_meta( $comment->comment_ID, 'akismet_pro_tip', self::$last_comment['akismet_pro_tip'] ); + } } } } @@ -380,6 +423,10 @@ class Akismet { clean_comment_cache( $comment_ids ); do_action( 'akismet_delete_comment_batch', count( $comment_ids ) ); + + foreach ( $comment_ids as $comment_id ) { + do_action( 'deleted_comment', $comment_id ); + } } if ( apply_filters( 'akismet_optimize_table', ( mt_rand(1, 5000) == 11), $wpdb->comments ) ) // lucky number @@ -469,6 +516,44 @@ class Akismet { // get the full comment history for a given comment, as an array in reverse chronological order public static function get_comment_history( $comment_id ) { $history = get_comment_meta( $comment_id, 'akismet_history', false ); + if ( empty( $history ) || empty( $history[ 0 ] ) ) { + return false; + } + + /* + // To see all variants when testing. + $history[] = array( 'time' => 445856401, 'message' => 'Old versions of Akismet stored the message as a literal string in the commentmeta.', 'event' => null ); + $history[] = array( 'time' => 445856402, 'event' => 'recheck-spam' ); + $history[] = array( 'time' => 445856403, 'event' => 'check-spam' ); + $history[] = array( 'time' => 445856404, 'event' => 'recheck-ham' ); + $history[] = array( 'time' => 445856405, 'event' => 'check-ham' ); + $history[] = array( 'time' => 445856406, 'event' => 'wp-blacklisted' ); + $history[] = array( 'time' => 445856406, 'event' => 'wp-disallowed' ); + $history[] = array( 'time' => 445856407, 'event' => 'report-spam' ); + $history[] = array( 'time' => 445856408, 'event' => 'report-spam', 'user' => 'sam' ); + $history[] = array( 'message' => 'sam reported this comment as spam (hardcoded message).', 'time' => 445856400, 'event' => 'report-spam', 'user' => 'sam' ); + $history[] = array( 'time' => 445856409, 'event' => 'report-ham', 'user' => 'sam' ); + $history[] = array( 'message' => 'sam reported this comment as ham (hardcoded message).', 'time' => 445856400, 'event' => 'report-ham', 'user' => 'sam' ); // + $history[] = array( 'time' => 445856410, 'event' => 'cron-retry-spam' ); + $history[] = array( 'time' => 445856411, 'event' => 'cron-retry-ham' ); + $history[] = array( 'time' => 445856412, 'event' => 'check-error' ); // + $history[] = array( 'time' => 445856413, 'event' => 'check-error', 'meta' => array( 'response' => 'The server was taking a nap.' ) ); + $history[] = array( 'time' => 445856414, 'event' => 'recheck-error' ); // Should not generate a message. + $history[] = array( 'time' => 445856415, 'event' => 'recheck-error', 'meta' => array( 'response' => 'The server was taking a nap.' ) ); + $history[] = array( 'time' => 445856416, 'event' => 'status-changedtrash' ); + $history[] = array( 'time' => 445856417, 'event' => 'status-changedspam' ); + $history[] = array( 'time' => 445856418, 'event' => 'status-changedhold' ); + $history[] = array( 'time' => 445856419, 'event' => 'status-changedapprove' ); + $history[] = array( 'time' => 445856420, 'event' => 'status-changed-trash' ); + $history[] = array( 'time' => 445856421, 'event' => 'status-changed-spam' ); + $history[] = array( 'time' => 445856422, 'event' => 'status-changed-hold' ); + $history[] = array( 'time' => 445856423, 'event' => 'status-changed-approve' ); + $history[] = array( 'time' => 445856424, 'event' => 'status-trash', 'user' => 'sam' ); + $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' ); + */ + usort( $history, array( 'Akismet', '_cmp_time' ) ); return $history; } @@ -506,6 +591,10 @@ class Akismet { public static function check_db_comment( $id, $recheck_reason = 'recheck_queue' ) { global $wpdb; + if ( ! self::get_api_key() ) { + return new WP_Error( 'akismet-not-configured', __( 'Akismet is not configured. Please enter an API key.', 'akismet' ) ); + } + $c = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->comments} WHERE comment_ID = %d", $id ), ARRAY_A ); if ( ! $c ) { @@ -653,6 +742,13 @@ class Akismet { if ( 'spam' != $comment->comment_approved ) return; + self::update_comment_history( $comment_id, '', 'report-spam' ); + + // If the user hasn't configured Akismet, there's nothing else to do at this point. + if ( ! self::get_api_key() ) { + return; + } + // use the original version stored in comment_meta if available $as_submitted = self::sanitize_comment_as_submitted( get_comment_meta( $comment_id, 'akismet_as_submitted', true ) ); @@ -685,9 +781,10 @@ class Akismet { } $response = Akismet::http_post( Akismet::build_query( $comment ), 'submit-spam' ); + + update_comment_meta( $comment_id, 'akismet_user_result', 'true' ); + if ( $comment->reporter ) { - self::update_comment_history( $comment_id, '', 'report-spam' ); - update_comment_meta( $comment_id, 'akismet_user_result', 'true' ); update_comment_meta( $comment_id, 'akismet_user', $comment->reporter ); } @@ -703,6 +800,13 @@ class Akismet { if ( !$comment ) // it was deleted return; + self::update_comment_history( $comment_id, '', 'report-ham' ); + + // If the user hasn't configured Akismet, there's nothing else to do at this point. + if ( ! self::get_api_key() ) { + return; + } + // use the original version stored in comment_meta if available $as_submitted = self::sanitize_comment_as_submitted( get_comment_meta( $comment_id, 'akismet_as_submitted', true ) ); @@ -735,9 +839,10 @@ class Akismet { } $response = self::http_post( Akismet::build_query( $comment ), 'submit-ham' ); + + update_comment_meta( $comment_id, 'akismet_user_result', 'false' ); + if ( $comment->reporter ) { - self::update_comment_history( $comment_id, '', 'report-ham' ); - update_comment_meta( $comment_id, 'akismet_user_result', 'false' ); update_comment_meta( $comment_id, 'akismet_user', $comment->reporter ); } @@ -860,6 +965,11 @@ class Akismet { * has not been set and that Akismet should just choose the default behavior for that * situation. */ + + if ( ! self::get_api_key() ) { + return; + } + $akismet_comment_nonce_option = apply_filters( 'akismet_comment_nonce', get_option( 'akismet_comment_nonce' ) ); if ( $akismet_comment_nonce_option == 'true' || $akismet_comment_nonce_option == '' ) { @@ -879,7 +989,7 @@ class Akismet { if ( is_user_logged_in() ) return false; - return ( get_option( 'akismet_strictness' ) === '1' ); + return ( get_option( 'akismet_strictness' ) === '1' ); } public static function get_ip_address() { @@ -1029,10 +1139,12 @@ class Akismet { if ( ! empty( self::$prevent_moderation_email_for_these_comments ) && ! empty( $emails ) ) { $comment = get_comment( $comment_id ); - foreach ( self::$prevent_moderation_email_for_these_comments as $possible_match ) { - if ( self::comments_match( $possible_match, $comment ) ) { - update_comment_meta( $comment_id, 'akismet_delayed_moderation_email', true ); - return array(); + if ( $comment ) { + foreach ( self::$prevent_moderation_email_for_these_comments as $possible_match ) { + if ( self::comments_match( $possible_match, $comment ) ) { + update_comment_meta( $comment_id, 'akismet_delayed_moderation_email', true ); + return array(); + } } } } @@ -1163,51 +1275,106 @@ class Akismet { // given a response from an API call like check_key_status(), update the alert code options if an alert is present. public static function update_alert( $response ) { - $code = $msg = null; - if ( isset( $response[0]['x-akismet-alert-code'] ) ) { - $code = $response[0]['x-akismet-alert-code']; - $msg = $response[0]['x-akismet-alert-msg']; - } + $alert_option_prefix = 'akismet_alert_'; + $alert_header_prefix = 'x-akismet-alert-'; + $alert_header_names = array( + 'code', + 'msg', + 'api-calls', + 'usage-limit', + 'upgrade-plan', + 'upgrade-url', + 'upgrade-type', + ); - // only call update_option() if the value has changed - if ( $code != get_option( 'akismet_alert_code' ) ) { - if ( ! $code ) { - delete_option( 'akismet_alert_code' ); - delete_option( 'akismet_alert_msg' ); + foreach ( $alert_header_names as $alert_header_name ) { + $value = null; + if ( isset( $response[0][ $alert_header_prefix . $alert_header_name ] ) ) { + $value = $response[0][ $alert_header_prefix . $alert_header_name ]; } - else { - update_option( 'akismet_alert_code', $code ); - update_option( 'akismet_alert_msg', $msg ); + + $option_name = $alert_option_prefix . str_replace( '-', '_', $alert_header_name ); + if ( $value != get_option( $option_name ) ) { + if ( ! $value ) { + delete_option( $option_name ); + } else { + update_option( $option_name, $value ); + } } } } public static function load_form_js() { - if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) { - return; + /* deprecated */ + } + + public static function set_form_js_async( $tag, $handle, $src ) { + /* deprecated */ + return $tag; + } + + public static function get_akismet_form_fields() { + $fields = ''; + + $prefix = 'ak_'; + + // Contact Form 7 uses _wpcf7 as a prefix to know which fields to exclude from comment_content. + if ( 'wpcf7_form_elements' === current_filter() ) { + $prefix = '_wpcf7_ak_'; + } + + $fields .= '

'; + $fields .= ''; + + if ( ! function_exists( 'amp_is_request' ) || ! amp_is_request() ) { + $fields .= ''; + $fields .= ''; } - wp_register_script( 'akismet-form', plugin_dir_url( __FILE__ ) . '_inc/form.js', array(), AKISMET_VERSION, true ); - wp_enqueue_script( 'akismet-form' ); + $fields .= '

'; + + return $fields; } - + + public static function output_custom_form_fields( $post_id ) { + // phpcs:ignore WordPress.Security.EscapeOutput + echo self::get_akismet_form_fields(); + } + + public static function inject_custom_form_fields( $html ) { + $html = str_replace( '', self::get_akismet_form_fields() . '', $html ); + + return $html; + } + + public static function append_custom_form_fields( $html ) { + $html .= self::get_akismet_form_fields(); + + return $html; + } + /** - * Mark form.js as async. Because nothing depends on it, it can run at any time - * after it's loaded, and the browser won't have to wait for it to load to continue - * parsing the rest of the page. + * Ensure that any Akismet-added form fields are included in the comment-check call. + * + * @param array $form + * @return array $form */ - public static function set_form_js_async( $tag, $handle, $src ) { - if ( 'akismet-form' !== $handle ) { - return $tag; + public static function prepare_custom_form_values( $form ) { + $prefix = 'ak_'; + + // Contact Form 7 uses _wpcf7 as a prefix to know which fields to exclude from comment_content. + if ( 'wpcf7_akismet_parameters' === current_filter() ) { + $prefix = '_wpcf7_ak_'; } - - return preg_replace( '/^