summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/akismet/class.akismet.php')
-rw-r--r--plugins/akismet/class.akismet.php477
1 files changed, 371 insertions, 106 deletions
diff --git a/plugins/akismet/class.akismet.php b/plugins/akismet/class.akismet.php
index 01753014..1681d0e1 100644
--- a/plugins/akismet/class.akismet.php
+++ b/plugins/akismet/class.akismet.php
@@ -5,12 +5,19 @@ class Akismet {
const API_PORT = 80;
const MAX_DELAY_BEFORE_MODERATION_EMAIL = 86400; // One day in seconds
+ public static $limit_notices = array(
+ 10501 => '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 .= '<p style="display: none !important;">';
+ $fields .= '<label>&#916;<textarea name="' . $prefix . 'hp_textarea" cols="45" rows="8" maxlength="100"></textarea></label>';
+
+ if ( ! function_exists( 'amp_is_request' ) || ! amp_is_request() ) {
+ $fields .= '<input type="hidden" id="ak_js" name="' . $prefix . 'js" value="' . mt_rand( 0, 250 ) . '"/>';
+ $fields .= '<script>document.getElementById( "ak_js" ).setAttribute( "value", ( new Date() ).getTime() );</script>';
}
- wp_register_script( 'akismet-form', plugin_dir_url( __FILE__ ) . '_inc/form.js', array(), AKISMET_VERSION, true );
- wp_enqueue_script( 'akismet-form' );
+ $fields .= '</p>';
+
+ 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( '</form>', self::get_akismet_form_fields() . '</form>', $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( '/^<script /i', '<script async="async" ', $tag );
- }
-
- public static function inject_ak_js( $fields ) {
- echo '<p style="display: none;">';
- echo '<input type="hidden" id="ak_js" name="ak_js" value="' . mt_rand( 0, 250 ) . '"/>';
- echo '</p>';
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ foreach ( $_POST 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 ) {
@@ -1277,7 +1444,7 @@ p {
$message = '<strong>'.sprintf(esc_html__( 'Akismet %s requires WordPress %s or higher.' , 'akismet'), AKISMET_VERSION, AKISMET__MINIMUM_WP_VERSION ).'</strong> '.sprintf(__('Please <a href="%1$s">upgrade WordPress</a> to a current version, or <a href="%2$s">downgrade to version 2.4 of the Akismet plugin</a>.', 'akismet'), 'https://codex.wordpress.org/Upgrading_WordPress', 'https://wordpress.org/extend/plugins/akismet/download/');
Akismet::bail_on_activation( $message );
- } else {
+ } elseif ( ! empty( $_SERVER['SCRIPT_NAME'] ) && false !== strpos( $_SERVER['SCRIPT_NAME'], '/wp-admin/plugins.php' ) ) {
add_option( 'Activated_Akismet', true );
}
}
@@ -1333,16 +1500,98 @@ p {
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;
-
- // Lame: tightly coupled with the IXR class.
- $args = $wp_xmlrpc_server->message->params;
-
- if ( !empty( $args[1] ) ) {
- $post_id = url_to_postid( $args[1] );
+
+ $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 = count( $wp_xmlrpc_server->message->params );
+ }
+
+ /*
+ * $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,
@@ -1355,23 +1604,30 @@ p {
$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' => $args[0],
+ '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' => $args[1],
+ 'comment_pingback_target' => $pingback_args[1],
'pingbacks_closed' => $pingbacks_closed ? '1' : '0',
+ 'is_multicall' => $is_multicall,
+ 'multicall_count' => $multicall_count,
);
- $comment = Akismet::auto_check_comment( $comment );
+ $comment = self::auto_check_comment( $comment, 'xml-rpc' );
if ( isset( $comment['akismet_result'] ) && 'true' == $comment['akismet_result'] ) {
- // Lame: tightly coupled with the IXR classes. Unfortunately the action provides no context and no way to return anything.
+ // 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.
}
}
}
@@ -1390,8 +1646,17 @@ p {
$meta_value = (array) $meta_value;
foreach ( $meta_value as $key => $value ) {
- if ( ! isset( self::$comment_as_submitted_allowed_keys[$key] ) || ! is_scalar( $value ) ) {
- unset( $meta_value[$key] );
+ 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 ] );
+ }
}
}