���� JFIF �� �
"" $(4,$&1'-=-157:::#+?D?8C49:7
7%%77777777777777777777777777777777777777777777777777�� { �" �� �� 5 !1AQa"q�2��BR��#b������� �� �� ? ��D@DDD@DDD@DDkK��6 �UG�4V�1��
�����릟�@�#���RY�dqp�
����� �o�7�m�s�<��VPS�e~V�چ8���X�T��$��c�� 9��ᘆ�m6@ WU�f�Don��r��5}9��}��hc�fF��/r=hi�� �͇�*�� b�.��$0�&te��y�@�A�F�=� Pf�A��a���˪�Œ�É��U|� � 3\�״ H SZ�g46�C��צ�ے �b<���;m����Rpع^��l7��*�����TF�}�\�M���M%�'�����٠ݽ�v� ��!-�����?�N!La��A+[`#���M����'�~oR�?��v^)��=��h����A��X�.���˃����^Æ��ܯsO"B�c>;
�e�4��5�k��/CB��.
�J?��;�҈�������������������~�<�VZ�ê¼2/)Í”jC���ע�V�G�!���!�F������\�� Kj�R�oc�h���:Þ I��1"2�q×°8��Р@ז���_C0�ր��A��lQ��@纼�!7��F�� �]�sZ
B�62r�v�z~�K�7�c��5�.���ӄq&�Z�d�<�kk���T&8�|���I���� Ws}���ǽ�cqnΑ�_���3��|N�-y,��i���ȗ_�\60���@��6����D@DDD@DDD@DDD@DDD@DDc�KN66<�c��64=r�����
Ď0��h���t&(�hnb[� ?��^��\��â|�,�/h�\��R��5�?
�0�!צ܉-����G����٬��Q�zA���1�����V��� �:R���`�$��ik��H����D4�����#dk����� h�}����7���w%�������*o8wG�LycuT�.���ܯ7��I��u^���)��/c�,s�Nq�ۺ�;�ך�YH2���.5B���DDD@DDD@DDD@DDD@DDD@V|�a�j{7c��X�F\�3MuA׾hb� ��n��F������ ��8�(��e����Pp�\"G�`s��m��ާaW�K��O����|;ei����֋�[�q��";a��1����Y�G�W/�߇�&�<���Ќ�H'q�m���)�X+!���=�m�ۚ丷~6a^X�)���,�>#&6G���Y��{����"" """ """ """ """ ""��at\/�a�8 �yp%�lhl�n����)���i�t��B�������������?��modskinlienminh.com - WSOX ENC
PK ! <*
readme.txtnu [ === Akismet Anti-spam: Spam Protection ===
Contributors: matt, ryan, andy, mdawaffe, tellyworth, josephscott, lessbloat, eoigal, cfinke, automattic, jgs, procifer, stephdau, kbrownkd, bluefuton, akismetantispam
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.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.
== Description ==
The best anti-spam protection to block spam comments and spam in a contact form. The most trusted antispam solution for WordPress and WooCommerce.
Akismet checks your comments and contact form submissions against our global database of spam to prevent your site from publishing malicious content. You can review the comment spam it catches on your blog's "Comments" admin screen.
Major features in Akismet include:
* Automatically checks all comments and filters out the ones that look like spam.
* Each comment has a status history, so you can easily see which comments were caught or cleared by Akismet and which were spammed or unspammed by a moderator.
* URLs are shown in the comment body to reveal hidden or misleading links.
* Moderators can see the number of approved comments for each user.
* A discard feature that outright blocks the worst spam, saving you disk space and speeding up your site.
PS: You'll be prompted to get an Akismet.com API key to use it, once activated. Keys are free for personal blogs; paid subscriptions are available for businesses and commercial sites.
== Installation ==
Upload the Akismet plugin to your blog, activate it, and then enter your Akismet.com API key.
1, 2, 3: You're done!
== 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*
* Make the plugin more resilient when asset files are missing (as seen in WordPress Playground).
* Add a link to the 'Account overview' page on akismet.com.
* Fix a minor error that occurs when another plugin removes all comment actions from the dashboard.
* Add the akismet_request_args filter to allow request args in Akismet API requests to be filtered.
* Fix a bug that causes some contact forms to include unnecessary data in the comment_content parameter.
= 5.3 =
*Release Date - 14 September 2023*
* Improve display of user notices.
* Add stylesheets for RTL languages.
* Remove initial disabled state from 'Save changes' button.
* Improve accessibility of API key entry form.
* Add new filter hooks for Fluent Forms.
* Fix issue with PHP 8.1 compatibility.
= 5.2 =
*Release Date - 21 June 2023*
* Visual refresh of Akismet stats.
* Improve PHP 8.1 compatibility.
* Improve appearance of plugin to match updated stats.
* Change minimum supported PHP version to 5.6 to match WordPress.
* Drop IE11 support and update minimum WordPress version to 5.8 (where IE11 support was removed from WP Core).
= 5.1 =
*Release Date - 20 March 2023*
* Removed unnecessary limit notices from admin page.
* Improved spam detection by including post taxonomies in the comment-check call.
* Removed API keys from stats iframes to avoid possible inadvertent exposure.
= 5.0.2 =
*Release Date - 1 December 2022*
* Improved compatibility with themes that hide or show UI elements based on mouse movements.
* Increased security of API keys by sending them in request bodies instead of subdomains.
= 5.0.1 =
*Release Date - 28 September 2022*
* Added an empty state for the Statistics section on the admin page.
* Fixed a bug that broke some admin page links when Jetpack plugins are active.
* Marked some event listeners as passive to improve performance in newer browsers.
* Disabled interaction observation on forms that post to other domains.
= 5.0 =
*Release Date - 26 July 2022*
* Added a new feature to catch spammers by observing how they interact with the page.
= 4.2.5 =
*Release Date - 11 July 2022*
* Fixed a bug that added unnecessary comment history entries after comment rechecks.
* Added a notice that displays when WP-Cron is disabled and might be affecting comment rechecks.
= 4.2.4 =
*Release Date - 20 May 2022*
* Improved translator instructions for comment history.
* Bumped the "Tested up to" tag to WP 6.0.
= 4.2.3 =
*Release Date - 25 April 2022*
* Improved compatibility with Fluent Forms
* Fixed missing translation domains
* Updated stats URL.
* Improved accessibility of elements on the config page.
= 4.2.2 =
*Release Date - 24 January 2022*
* Improved compatibility with Formidable Forms
* Fixed a bug that could cause issues when multiple contact forms appear on one page.
* Updated delete_comment and deleted_comment actions to pass two arguments to match WordPress core since 4.9.0.
* Added a filter that allows comment types to be excluded when counting users' approved comments.
= 4.2.1 =
*Release Date - 1 October 2021*
* Fixed a bug causing AMP validation to fail on certain pages with forms.
= 4.2 =
*Release Date - 30 September 2021*
* Added links to additional information on API usage notifications.
* Reduced the number of network requests required for a comment page when running Akismet.
* Improved compatibility with the most popular contact form plugins.
* Improved API usage buttons for clarity on what upgrade is needed.
For older changelog entries, please see the [additional changelog.txt file](https://plugins.svn.wordpress.org/akismet/trunk/changelog.txt) delivered with the plugin.
PK ! p_ .htaccessnu [ # Only allow direct access to specific Web-available files.
# Apache 2.2
Order Deny,Allow
Deny from all
# Apache 2.4
Require all denied
# Akismet CSS and JS
Allow from all
Require all granted
# Akismet images
Allow from all
Require all granted
PK ! d! views/connect-jp.phpnu [
gethostbynamel function. Akismet cannot work correctly until this is fixed. Please contact your web host or firewall administrator and give them this information about Akismet’s system requirements.', 'akismet' ), esc_url( 'https://akismet.com/akismet-hosting-faq/' ) ), array_merge( $kses_allow_link, $kses_allow_strong, array( 'code' => true ) ) );
?>
our guide about firewalls and check your server configuration.', 'akismet' ),
'https://blog.akismet.com/akismet-hosting-faq/'
),
$kses_allow_link
);
?>
';
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
echo '';
break;
case 'SECOND_MONTH_OVER_LIMIT':
echo esc_html( __( 'Your Akismet usage has been over your plan’s limit for two consecutive months. Next month, we will restrict your account after you reach the limit. Please consider upgrading your plan.', 'akismet' ) );
echo ' ';
echo '';
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
echo '';
break;
case 'THIRD_MONTH_APPROACHING_LIMIT':
echo esc_html( __( 'Your Akismet usage is nearing your plan’s limit for the third consecutive month. We will restrict your account after you reach the limit. Upgrade your plan so Akismet can continue blocking spam.', 'akismet' ) );
echo ' ';
echo '';
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
echo '';
break;
case 'THIRD_MONTH_OVER_LIMIT':
case 'FOUR_PLUS_MONTHS_OVER_LIMIT':
echo esc_html( __( 'Your Akismet usage has been over your plan’s limit for three consecutive months. We have restricted your account for the rest of the month. Upgrade your plan so Akismet can continue blocking spam.', 'akismet' ) );
echo ' ';
echo '';
echo esc_html( __( 'Learn more about usage limits.', 'akismet' ) );
echo '';
break;
default:
}
?>
' . esc_html__( 'Comments' , 'akismet') . ' - ' . esc_html__( 'Show the number of approved comments beside each comment author in the comments list page.' , 'akismet') . '
' .
'
' . esc_html__( 'Strictness' , 'akismet') . ' - ' . esc_html__( 'Choose to either discard the worst spam automatically or to always put all spam in spam folder.' , 'akismet') . '
'.sprintf( _n(
'Akismet has protected your site from %3$s spam comment.',
'Akismet has protected your site from %3$s spam comments.',
$count
, 'akismet'), 'https://akismet.com/wordpress/', esc_url( add_query_arg( array( 'page' => 'akismet-admin' ), admin_url( isset( $submenu['edit-comments.php'] ) ? 'edit-comments.php' : 'edit.php' ) ) ), number_format_i18n($count) ).'
';
}
// WP 2.5+
public static function rightnow_stats() {
if ( $count = get_option('akismet_spam_count') ) {
$intro = sprintf( _n(
'Akismet has protected your site from %2$s spam comment already. ',
'Akismet has protected your site from %2$s spam comments already. ',
$count
, 'akismet'), 'https://akismet.com/wordpress/', number_format_i18n( $count ) );
} else {
$intro = sprintf( __('Akismet blocks spam from getting to your blog. ', 'akismet'), 'https://akismet.com/wordpress/' );
}
$link = add_query_arg( array( 'comment_status' => 'spam' ), admin_url( 'edit-comments.php' ) );
if ( $queue_count = self::get_spam_count() ) {
$queue_text = sprintf( _n(
'There’s %1$s comment in your spam queue right now.',
'There are %1$s comments in your spam queue right now.',
$queue_count
, 'akismet'), number_format_i18n( $queue_count ), esc_url( $link ) );
} else {
$queue_text = sprintf( __( "There’s nothing in your spam queue at the moment." , 'akismet'), esc_url( $link ) );
}
$text = $intro . ' ' . $queue_text;
echo "
$text
\n";
}
public static function check_for_spam_button( $comment_status ) {
// The "Check for Spam" button should only appear when the page might be showing
// a comment with comment_approved=0, which means an un-trashed, un-spammed,
// not-yet-moderated comment.
if ( 'all' != $comment_status && 'moderated' != $comment_status ) {
return;
}
$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 = self::get_page_url();
$classes[] = 'ajax-disabled';
}
}
echo '' . esc_html__('Check for Spam', 'akismet') . '';
echo '';
}
public static function recheck_queue() {
global $wpdb;
Akismet::fix_scheduled_recheck();
if ( ! ( isset( $_GET['recheckqueue'] ) || ( isset( $_REQUEST['action'] ) && 'akismet_recheck_queue' == $_REQUEST['action'] ) ) ) {
return;
}
if ( ! wp_verify_nonce( $_POST['nonce'], 'akismet_check_for_spam' ) ) {
wp_send_json( array(
'error' => __( 'You don’t have permission to do that.', 'akismet' ),
));
return;
}
$result_counts = self::recheck_queue_portion( empty( $_POST['offset'] ) ? 0 : $_POST['offset'], empty( $_POST['limit'] ) ? 100 : $_POST['limit'] );
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
wp_send_json( array(
'counts' => $result_counts,
));
}
else {
$redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : admin_url( 'edit-comments.php' );
wp_safe_redirect( $redirect_to );
exit;
}
}
public static function recheck_queue_portion( $start = 0, $limit = 100 ) {
global $wpdb;
$paginate = '';
if ( $limit <= 0 ) {
$limit = 100;
}
if ( $start < 0 ) {
$start = 0;
}
$moderation = $wpdb->get_col( $wpdb->prepare( "SELECT * FROM {$wpdb->comments} WHERE comment_approved = '0' LIMIT %d OFFSET %d", $limit, $start ) );
$result_counts = array(
'processed' => is_countable( $moderation ) ? count( $moderation ) : 0,
'spam' => 0,
'ham' => 0,
'error' => 0,
);
foreach ( $moderation as $comment_id ) {
$api_response = Akismet::recheck_comment( $comment_id, 'recheck_queue' );
if ( 'true' === $api_response ) {
++$result_counts['spam'];
}
elseif ( 'false' === $api_response ) {
++$result_counts['ham'];
}
else {
++$result_counts['error'];
}
}
return $result_counts;
}
// Adds an 'x' link next to author URLs, clicking will remove the author URL and show an undo link
public static function remove_comment_author_url() {
if ( !empty( $_POST['id'] ) && check_admin_referer( 'comment_author_url_nonce' ) ) {
$comment_id = intval( $_POST['id'] );
$comment = get_comment( $comment_id, ARRAY_A );
if ( $comment && current_user_can( 'edit_comment', $comment['comment_ID'] ) ) {
$comment['comment_author_url'] = '';
do_action( 'comment_remove_author_url' );
print( wp_update_comment( $comment ) );
die();
}
}
}
public static function add_comment_author_url() {
if ( !empty( $_POST['id'] ) && !empty( $_POST['url'] ) && check_admin_referer( 'comment_author_url_nonce' ) ) {
$comment_id = intval( $_POST['id'] );
$comment = get_comment( $comment_id, ARRAY_A );
if ( $comment && current_user_can( 'edit_comment', $comment['comment_ID'] ) ) {
$comment['comment_author_url'] = esc_url( $_POST['url'] );
do_action( 'comment_add_author_url' );
print( wp_update_comment( $comment ) );
die();
}
}
}
public static function comment_row_action( $a, $comment ) {
$akismet_result = get_comment_meta( $comment->comment_ID, 'akismet_result', true );
$akismet_error = get_comment_meta( $comment->comment_ID, 'akismet_error', true );
$user_result = get_comment_meta( $comment->comment_ID, 'akismet_user_result', true);
$comment_status = wp_get_comment_status( $comment->comment_ID );
$desc = null;
if ( $akismet_error ) {
$desc = __( 'Awaiting spam check' , 'akismet');
} elseif ( !$user_result || $user_result == $akismet_result ) {
// Show the original Akismet result if the user hasn't overridden it, or if their decision was the same
if ( $akismet_result == 'true' && $comment_status != 'spam' && $comment_status != 'trash' )
$desc = __( 'Flagged as spam by Akismet' , 'akismet');
elseif ( $akismet_result == 'false' && $comment_status == 'spam' )
$desc = __( 'Cleared by Akismet' , 'akismet');
} else {
$who = get_comment_meta( $comment->comment_ID, 'akismet_user', true );
if ( $user_result == 'true' )
$desc = sprintf( __('Flagged as spam by %s', 'akismet'), $who );
else
$desc = sprintf( __('Un-spammed by %s', 'akismet'), $who );
}
// add a History item to the hover links, just after Edit
if ( $akismet_result && is_array( $a ) ) {
$b = array();
foreach ( $a as $k => $item ) {
$b[ $k ] = $item;
if (
$k == 'edit'
|| $k == 'unspam'
) {
$b['history'] = ' '. esc_html__('History', 'akismet') . '';
}
}
$a = $b;
}
if ( $desc )
echo ''.esc_html( $desc ).'';
$show_user_comments_option = get_option( 'akismet_show_user_comments_approved' );
if ( $show_user_comments_option === false ) {
// Default to active if the user hasn't made a decision.
$show_user_comments_option = '1';
}
$show_user_comments = apply_filters( 'akismet_show_user_comments_approved', $show_user_comments_option );
$show_user_comments = $show_user_comments === 'false' ? false : $show_user_comments; //option used to be saved as 'false' / 'true'
if ( $show_user_comments ) {
$comment_count = Akismet::get_user_comments_approved( $comment->user_id, $comment->comment_author_email, $comment->comment_author, $comment->comment_author_url );
$comment_count = intval( $comment_count );
echo ' '. sprintf( esc_html( _n( '%s approved', '%s approved', $comment_count , 'akismet') ), number_format_i18n( $comment_count ) ) . '';
}
return $a;
}
public static function comment_status_meta_box( $comment ) {
$history = Akismet::get_comment_history( $comment->comment_ID );
if ( $history ) {
foreach ( $history as $row ) {
$message = '';
if ( ! empty( $row['message'] ) ) {
// Old versions of Akismet stored the message as a literal string in the commentmeta.
// New versions don't do that for two reasons:
// 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 = esc_html( $row['message'] );
} else if ( ! empty( $row['event'] ) ) {
// If possible, use a current translation.
switch ( $row['event'] ) {
case 'recheck-spam':
$message = esc_html( __( 'Akismet re-checked and caught this comment as spam.', 'akismet' ) );
break;
case 'check-spam':
$message = esc_html( __( 'Akismet caught this comment as spam.', 'akismet' ) );
break;
case 'recheck-ham':
$message = esc_html( __( 'Akismet re-checked and cleared this comment.', 'akismet' ) );
break;
case 'check-ham':
$message = esc_html( __( 'Akismet cleared this comment.', 'akismet' ) );
break;
case 'wp-blacklisted':
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'] ) ) {
/* translators: The placeholder is a username. */
$message = esc_html( sprintf( __( '%s reported this comment as spam.', 'akismet' ), $row['user'] ) );
} else if ( ! $message ) {
$message = esc_html( __( 'This comment was reported as spam.', 'akismet' ) );
}
break;
case 'report-ham':
if ( isset( $row['user'] ) ) {
/* translators: The placeholder is a username. */
$message = esc_html( sprintf( __( '%s reported this comment as not spam.', 'akismet' ), $row['user'] ) );
} else if ( ! $message ) {
$message = esc_html( __( 'This comment was reported as not spam.', 'akismet' ) );
}
break;
case 'cron-retry-spam':
$message = esc_html( __( 'Akismet caught this comment as spam during an automatic retry.', 'akismet' ) );
break;
case 'cron-retry-ham':
$message = esc_html( __( 'Akismet cleared this comment during an automatic retry.', 'akismet' ) );
break;
case 'check-error':
if ( isset( $row['meta'], $row['meta']['response'] ) ) {
/* translators: The placeholder is an error response returned by the API server. */
$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'] ) ) {
/* translators: The placeholder is an error response returned by the API server. */
$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;
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'.
// See https://plugins.trac.wordpress.org/changeset/1150658/akismet/trunk
$new_status = preg_replace( '/^status-changed-?/', '', $row['event'] );
/* translators: The placeholder is a short string (like 'spam' or 'approved') denoting the new comment 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'] ) ) {
/* translators: %1$s is a username; %2$s is a short string (like 'spam' or 'approved') denoting the new comment status. */
$message = sprintf( esc_html( __( '%1$s changed the comment status to %2$s.', 'akismet' ) ), $row['user'], '' . esc_html( $new_status ) . '' );
}
}
break;
}
}
if ( ! empty( $message ) ) {
echo '
';
if ( isset( $row['time'] ) ) {
$time = gmdate( 'D d M Y @ h:i:s a', (int) $row['time'] ) . ' GMT';
/* translators: The placeholder is an amount of time, like "7 seconds" or "3 days" returned by the function human_time_diff(). */
$time_html = '' . sprintf( esc_html__( '%s ago', 'akismet' ), human_time_diff( $row['time'] ) ) . '';
echo sprintf(
/* translators: %1$s is a human-readable time difference, like "3 hours ago", and %2$s is an already-translated phrase describing how a comment's status changed, like "This comment was reported as spam." */
esc_html( __( '%1$s - %2$s', 'akismet' ) ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$time_html,
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$message
); // esc_html() is done above so that we can use HTML in $message.
} else {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $message; // esc_html() is done above so that we can use HTML in $message.
}
echo '
';
}
}
public static function is_test_mode() {
return defined('AKISMET_TEST_MODE') && AKISMET_TEST_MODE;
}
public static function allow_discard() {
if ( defined( 'DOING_AJAX' ) && DOING_AJAX )
return false;
if ( is_user_logged_in() )
return false;
return ( get_option( 'akismet_strictness' ) === '1' );
}
public static function get_ip_address() {
return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null;
}
/**
* Do these two comments, without checking the comment_ID, "match"?
*
* @param mixed $comment1 A comment object or array.
* @param mixed $comment2 A comment object or array.
* @return bool Whether the two comments should be treated as the same comment.
*/
private static function comments_match( $comment1, $comment2 ) {
$comment1 = (array) $comment1;
$comment2 = (array) $comment2;
// Set default values for these strings that we check in order to simplify
// the checks and avoid PHP warnings.
if ( ! isset( $comment1['comment_author'] ) ) {
$comment1['comment_author'] = '';
}
if ( ! isset( $comment2['comment_author'] ) ) {
$comment2['comment_author'] = '';
}
if ( ! isset( $comment1['comment_author_email'] ) ) {
$comment1['comment_author_email'] = '';
}
if ( ! isset( $comment2['comment_author_email'] ) ) {
$comment2['comment_author_email'] = '';
}
$comments_match = (
isset( $comment1['comment_post_ID'], $comment2['comment_post_ID'] )
&& intval( $comment1['comment_post_ID'] ) == intval( $comment2['comment_post_ID'] )
&& (
// The comment author length max is 255 characters, limited by the TINYTEXT column type.
// If the comment author includes multibyte characters right around the 255-byte mark, they
// may be stripped when the author is saved in the DB, so a 300+ char author may turn into
// a 253-char author when it's saved, not 255 exactly. The longest possible character is
// theoretically 6 bytes, so we'll only look at the first 248 bytes to be safe.
substr( $comment1['comment_author'], 0, 248 ) == substr( $comment2['comment_author'], 0, 248 )
|| substr( stripslashes( $comment1['comment_author'] ), 0, 248 ) == substr( $comment2['comment_author'], 0, 248 )
|| substr( $comment1['comment_author'], 0, 248 ) == substr( stripslashes( $comment2['comment_author'] ), 0, 248 )
// Certain long comment author names will be truncated to nothing, depending on their encoding.
|| ( ! $comment1['comment_author'] && strlen( $comment2['comment_author'] ) > 248 )
|| ( ! $comment2['comment_author'] && strlen( $comment1['comment_author'] ) > 248 )
)
&& (
// The email max length is 100 characters, limited by the VARCHAR(100) column type.
// Same argument as above for only looking at the first 93 characters.
substr( $comment1['comment_author_email'], 0, 93 ) == substr( $comment2['comment_author_email'], 0, 93 )
|| substr( stripslashes( $comment1['comment_author_email'] ), 0, 93 ) == substr( $comment2['comment_author_email'], 0, 93 )
|| substr( $comment1['comment_author_email'], 0, 93 ) == substr( stripslashes( $comment2['comment_author_email'] ), 0, 93 )
// Very long emails can be truncated and then stripped if the [0:100] substring isn't a valid address.
|| ( ! $comment1['comment_author_email'] && strlen( $comment2['comment_author_email'] ) > 100 )
|| ( ! $comment2['comment_author_email'] && strlen( $comment1['comment_author_email'] ) > 100 )
)
);
return $comments_match;
}
// Does the supplied comment match the details of the one most recently stored in self::$last_comment?
public static function matches_last_comment( $comment ) {
return self::comments_match( self::$last_comment, $comment );
}
private static function get_user_agent() {
return isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null;
}
private static function get_referer() {
return isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : null;
}
// return a comma-separated list of role names for the given user
public static function get_user_roles( $user_id ) {
$comment_user = null;
$roles = false;
if ( !class_exists('WP_User') )
return false;
if ( $user_id > 0 ) {
$comment_user = new WP_User( $user_id );
if ( isset( $comment_user->roles ) )
$roles = implode( ',', $comment_user->roles );
}
if ( is_multisite() && is_super_admin( $user_id ) ) {
if ( empty( $roles ) ) {
$roles = 'super_admin';
} else {
$comment_user->roles[] = 'super_admin';
$roles = implode( ',', $comment_user->roles );
}
}
return $roles;
}
// filter handler used to return a spam result to pre_comment_approved
public static function last_comment_status( $approved, $comment ) {
if ( is_null( self::$last_comment_result ) ) {
// We didn't have reason to store the result of the last check.
return $approved;
}
// Only do this if it's the correct comment
if ( ! self::matches_last_comment( $comment ) ) {
self::log( "comment_is_spam mismatched comment, returning unaltered $approved" );
return $approved;
}
if ( 'trash' === $approved ) {
// If the last comment we checked has had its approval set to 'trash',
// then it failed the comment blacklist check. Let that blacklist override
// the spam check, since users have the (valid) expectation that when
// they fill out their blacklists, comments that match it will always
// end up in the trash.
return $approved;
}
// bump the counter here instead of when the filter is added to reduce the possibility of overcounting
if ( $incr = apply_filters('akismet_spam_count_incr', 1) )
update_option( 'akismet_spam_count', get_option('akismet_spam_count') + $incr );
return self::$last_comment_result;
}
/**
* If Akismet is temporarily unreachable, we don't want to "spam" the blogger with
* moderation emails for comments that will be automatically cleared or spammed on
* the next retry.
*
* For comments that will be rechecked later, empty the list of email addresses that
* the moderation email would be sent to.
*
* @param array $emails An array of email addresses that the moderation email will be sent to.
* @param int $comment_id The ID of the relevant comment.
* @return array An array of email addresses that the moderation email will be sent to.
*/
public static function disable_moderation_emails_if_unreachable( $emails, $comment_id ) {
if ( ! empty( self::$prevent_moderation_email_for_these_comments ) && ! empty( $emails ) ) {
$comment = get_comment( $comment_id );
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();
}
}
}
}
return $emails;
}
public static function _cmp_time( $a, $b ) {
return $a['time'] > $b['time'] ? -1 : 1;
}
public static function _get_microtime() {
$mtime = explode( ' ', microtime() );
return $mtime[1] + $mtime[0];
}
/**
* Make a POST request to the Akismet API.
*
* @param string $request The body of the request.
* @param string $path The path for the request.
* @param string $ip The specific IP address to hit.
* @return array A two-member array consisting of the headers and the response body, both empty in the case of a failure.
*/
public static function http_post( $request, $path, $ip=null ) {
$akismet_ua = sprintf( 'WordPress/%s | Akismet/%s', $GLOBALS['wp_version'], constant( 'AKISMET_VERSION' ) );
$akismet_ua = apply_filters( 'akismet_ua', $akismet_ua );
$host = self::API_HOST;
$api_key = self::get_api_key();
if ( $api_key ) {
$request = add_query_arg( 'api_key', $api_key, $request );
}
$http_host = $host;
// use a specific IP if provided
// needed by Akismet_Admin::check_server_connectivity()
if ( $ip && long2ip( ip2long( $ip ) ) ) {
$http_host = $ip;
}
$http_args = array(
'body' => $request,
'headers' => array(
'Content-Type' => 'application/x-www-form-urlencoded; charset=' . get_option( 'blog_charset' ),
'Host' => $host,
'User-Agent' => $akismet_ua,
),
'httpversion' => '1.0',
'timeout' => 15
);
$akismet_url = $http_akismet_url = "http://{$http_host}/1.1/{$path}";
/**
* Try SSL first; if that fails, try without it and don't try it again for a while.
*/
$ssl = $ssl_failed = false;
// Check if SSL requests were disabled fewer than X hours ago.
$ssl_disabled = get_option( 'akismet_ssl_disabled' );
if ( $ssl_disabled && $ssl_disabled < ( time() - 60 * 60 * 24 ) ) { // 24 hours
$ssl_disabled = false;
delete_option( 'akismet_ssl_disabled' );
}
else if ( $ssl_disabled ) {
do_action( 'akismet_ssl_disabled' );
}
if ( ! $ssl_disabled && ( $ssl = wp_http_supports( array( 'ssl' ) ) ) ) {
$akismet_url = set_url_scheme( $akismet_url, 'https' );
do_action( 'akismet_https_request_pre' );
}
$response = wp_remote_post( $akismet_url, $http_args );
Akismet::log( compact( 'akismet_url', 'http_args', 'response' ) );
if ( $ssl && is_wp_error( $response ) ) {
do_action( 'akismet_https_request_failure', $response );
// Intermittent connection problems may cause the first HTTPS
// request to fail and subsequent HTTP requests to succeed randomly.
// Retry the HTTPS request once before disabling SSL for a time.
$response = wp_remote_post( $akismet_url, $http_args );
Akismet::log( compact( 'akismet_url', 'http_args', 'response' ) );
if ( is_wp_error( $response ) ) {
$ssl_failed = true;
do_action( 'akismet_https_request_failure', $response );
do_action( 'akismet_http_request_pre' );
// Try the request again without SSL.
$response = wp_remote_post( $http_akismet_url, $http_args );
Akismet::log( compact( 'http_akismet_url', 'http_args', 'response' ) );
}
}
if ( is_wp_error( $response ) ) {
do_action( 'akismet_request_failure', $response );
return array( '', '' );
}
if ( $ssl_failed ) {
// The request failed when using SSL but succeeded without it. Disable SSL for future requests.
update_option( 'akismet_ssl_disabled', time() );
do_action( 'akismet_https_disabled' );
}
$simplified_response = array( $response['headers'], $response['body'] );
self::update_alert( $simplified_response );
return $simplified_response;
}
// 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 ) {
$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',
);
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 ];
}
$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 );
}
}
}
}
/**
* Mark akismet-frontend.js as deferred. 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.
*/
public static function set_form_js_async( $tag, $handle, $src ) {
if ( 'akismet-frontend' !== $handle ) {
return $tag;
}
return preg_replace( '/^';
}
$fields .= '';
return $fields;
}
public static function output_custom_form_fields( $post_id ) {
if ( 'fluentform/form_element_start' === current_filter() && did_action( 'fluentform_form_element_start' ) ) {
// Already did this via the legacy filter.
return;
}
// 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;
}
/**
* Ensure that any Akismet-added form fields are included in the comment-check call.
*
* @param array $form
* @param array $data Some plugins will supply the POST data via the filter, since they don't
* read it directly from $_POST.
* @return array $form
*/
public static function prepare_custom_form_values( $form, $data = null ) {
if ( 'fluentform/akismet_fields' === current_filter() && did_filter( 'fluentform_akismet_fields' ) ) {
// Already updated the form fields via the legacy filter.
return $form;
}
if ( is_null( $data ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$data = $_POST;
}
$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_';
}
foreach ( $data as $key => $val ) {
if ( 0 === strpos( $key, $prefix ) ) {
$form[ 'POST_ak_' . substr( $key, strlen( $prefix ) ) ] = $val;
}
}
return $form;
}
private static function bail_on_activation( $message, $deactivate = true ) {
?>
$plugin ) {
if ( $plugin === $akismet ) {
$plugins[$i] = false;
$update = true;
}
}
if ( $update ) {
update_option( 'active_plugins', array_filter( $plugins ) );
}
}
exit;
}
public static function view( $name, array $args = array() ) {
$args = apply_filters( 'akismet_view_arguments', $args, $name );
foreach ( $args as $key => $val ) {
$$key = $val;
}
load_plugin_textdomain( 'akismet' );
$file = AKISMET__PLUGIN_DIR . 'views/'. $name . '.php';
include( $file );
}
/**
* Attached to activate_{ plugin_basename( __FILES__ ) } by register_activation_hook()
* @static
*/
public static function plugin_activation() {
if ( version_compare( $GLOBALS['wp_version'], AKISMET__MINIMUM_WP_VERSION, '<' ) ) {
load_plugin_textdomain( 'akismet' );
$message = ''.sprintf(esc_html__( 'Akismet %s requires WordPress %s or higher.' , 'akismet'), AKISMET_VERSION, AKISMET__MINIMUM_WP_VERSION ).' '.sprintf(__('Please upgrade WordPress to a current version, or downgrade to version 2.4 of the Akismet plugin.', 'akismet'), 'https://codex.wordpress.org/Upgrading_WordPress', 'https://wordpress.org/extend/plugins/akismet/download/');
Akismet::bail_on_activation( $message );
} elseif ( ! empty( $_SERVER['SCRIPT_NAME'] ) && false !== strpos( $_SERVER['SCRIPT_NAME'], '/wp-admin/plugins.php' ) ) {
add_option( 'Activated_Akismet', true );
}
}
/**
* Removes all connection options
* @static
*/
public static function plugin_deactivation( ) {
self::deactivate_key( self::get_api_key() );
// Remove any scheduled cron jobs.
$akismet_cron_events = array(
'akismet_schedule_cron_recheck',
'akismet_scheduled_delete',
);
foreach ( $akismet_cron_events as $akismet_cron_event ) {
$timestamp = wp_next_scheduled( $akismet_cron_event );
if ( $timestamp ) {
wp_unschedule_event( $timestamp, $akismet_cron_event );
}
}
}
/**
* Essentially a copy of WP's build_query but one that doesn't expect pre-urlencoded values.
*
* @param array $args An array of key => value pairs
* @return string A string ready for use as a URL query string.
*/
public static function build_query( $args ) {
return _http_build_query( $args, '', '&' );
}
/**
* Log debugging info to the error log.
*
* Enabled when WP_DEBUG_LOG is enabled (and WP_DEBUG, since according to
* core, "WP_DEBUG_DISPLAY and WP_DEBUG_LOG perform no function unless
* WP_DEBUG is true), but can be disabled via the akismet_debug_log filter.
*
* @param mixed $akismet_debug The data to log.
*/
public static function log( $akismet_debug ) {
if ( apply_filters( 'akismet_debug_log', defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG && defined( 'AKISMET_DEBUG' ) && AKISMET_DEBUG ) ) {
error_log( print_r( compact( 'akismet_debug' ), true ) );
}
}
public static function pre_check_pingback( $method ) {
$pingback_args = array();
if ( $method !== 'pingback.ping' )
return;
// A lot of this code is tightly coupled with the IXR class because the xmlrpc_call action doesn't pass along any information besides the method name.
// This ticket should hopefully fix that: https://core.trac.wordpress.org/ticket/52524
// Until that happens, when it's a system.multicall, pre_check_pingback will be called once for every internal pingback call.
// Keep track of how many times this function has been called so we know which call to reference in the XML.
static $call_count = 0;
$call_count++;
global $wp_xmlrpc_server;
if ( !is_object( $wp_xmlrpc_server ) )
return false;
$is_multicall = false;
$multicall_count = 0;
if ( 'system.multicall' === $wp_xmlrpc_server->message->methodName ) {
$is_multicall = true;
if ( 0 === $call_count ) {
// Only pass along the number of entries in the multicall the first time we see it.
$multicall_count = is_countable( $wp_xmlrpc_server->message->params ) ? count( $wp_xmlrpc_server->message->params ) : 0;
}
/*
* $wp_xmlrpc_server->message looks like this:
*
(
[message] =>
[messageType] => methodCall
[faultCode] =>
[faultString] =>
[methodName] => system.multicall
[params] => Array
(
[0] => Array
(
[methodName] => pingback.ping
[params] => Array
(
[0] => http://www.example.net/?p=1 // Site that created the pingback.
[1] => https://www.example.com/?p=1 // Post being pingback'd on this site.
)
)
[1] => Array
(
[methodName] => pingback.ping
[params] => Array
(
[0] => http://www.example.net/?p=1 // Site that created the pingback.
[1] => https://www.example.com/?p=2 // Post being pingback'd on this site.
)
)
)
)
*/
// Use the params from the nth pingback.ping call in the multicall.
$pingback_calls_found = 0;
foreach ( $wp_xmlrpc_server->message->params as $xmlrpc_action ) {
if ( 'pingback.ping' === $xmlrpc_action['methodName'] ) {
$pingback_calls_found++;
}
if ( $call_count === $pingback_calls_found ) {
$pingback_args = $xmlrpc_action['params'];
break;
}
}
} else {
/*
* $wp_xmlrpc_server->message looks like this:
*
(
[message] =>
[messageType] => methodCall
[faultCode] =>
[faultString] =>
[methodName] => pingback.ping
[params] => Array
(
[0] => http://www.example.net/?p=1 // Site that created the pingback.
[1] => https://www.example.com/?p=2 // Post being pingback'd on this site.
)
)
*/
$pingback_args = $wp_xmlrpc_server->message->params;
}
if ( ! empty( $pingback_args[1] ) ) {
$post_id = url_to_postid( $pingback_args[1] );
// If pingbacks aren't open on this post, we'll still check whether this request is part of a potential DDOS,
// but indicate to the server that pingbacks are indeed closed so we don't include this request in the user's stats,
// since the user has already done their part by disabling pingbacks.
$pingbacks_closed = false;
$post = get_post( $post_id );
if ( ! $post || ! pings_open( $post ) ) {
$pingbacks_closed = true;
}
// Note: If is_multicall is true and multicall_count=0, then we know this is at least the 2nd pingback we've processed in this multicall.
$comment = array(
'comment_author_url' => $pingback_args[0],
'comment_post_ID' => $post_id,
'comment_author' => '',
'comment_author_email' => '',
'comment_content' => '',
'comment_type' => 'pingback',
'akismet_pre_check' => '1',
'comment_pingback_target' => $pingback_args[1],
'pingbacks_closed' => $pingbacks_closed ? '1' : '0',
'is_multicall' => $is_multicall,
'multicall_count' => $multicall_count,
);
$comment = self::auto_check_comment( $comment, 'xml-rpc' );
if ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) {
// Sad: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything.
$wp_xmlrpc_server->error( new IXR_Error( 0, 'Invalid discovery target' ) );
// Also note that if this was part of a multicall, a spam result will prevent the subsequent calls from being executed.
// This is probably fine, but it raises the bar for what should be acceptable as a false positive.
}
}
}
/**
* Ensure that we are loading expected scalar values from akismet_as_submitted commentmeta.
*
* @param mixed $meta_value
* @return mixed
*/
private static function sanitize_comment_as_submitted( $meta_value ) {
if ( empty( $meta_value ) ) {
return $meta_value;
}
$meta_value = (array) $meta_value;
foreach ( $meta_value as $key => $value ) {
if ( ! is_scalar( $value ) ) {
unset( $meta_value[ $key ] );
} else {
// These can change, so they're not explicitly listed in comment_as_submitted_allowed_keys.
if ( strpos( $key, 'POST_ak_' ) === 0 ) {
continue;
}
if ( ! isset( self::$comment_as_submitted_allowed_keys[ $key ] ) ) {
unset( $meta_value[ $key ] );
}
}
}
return $meta_value;
}
public static function predefined_api_key() {
if ( defined( 'WPCOM_API_KEY' ) ) {
return true;
}
return apply_filters( 'akismet_predefined_api_key', false );
}
/**
* Controls the display of a privacy related notice underneath the comment form using the `akismet_comment_form_privacy_notice` option and filter respectively.
* Default is top not display the notice, leaving the choice to site admins, or integrators.
*/
public static function display_comment_form_privacy_notice() {
if ( 'display' !== apply_filters( 'akismet_comment_form_privacy_notice', get_option( 'akismet_comment_form_privacy_notice', 'hide' ) ) ) {
return;
}
echo apply_filters(
'akismet_comment_form_privacy_notice_markup',
'