wp/wp-content/plugins/akismet/class.akismet.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     8 	private static $last_comment = '';
     8 	private static $last_comment = '';
     9 	private static $initiated = false;
     9 	private static $initiated = false;
    10 	private static $prevent_moderation_email_for_these_comments = array();
    10 	private static $prevent_moderation_email_for_these_comments = array();
    11 	private static $last_comment_result = null;
    11 	private static $last_comment_result = null;
    12 	private static $comment_as_submitted_allowed_keys = array( 'blog' => '', 'blog_charset' => '', 'blog_lang' => '', 'blog_ua' => '', 'comment_agent' => '', 'comment_author' => '', 'comment_author_IP' => '', 'comment_author_email' => '', 'comment_author_url' => '', 'comment_content' => '', 'comment_date_gmt' => '', 'comment_tags' => '', 'comment_type' => '', 'guid' => '', 'is_test' => '', 'permalink' => '', 'reporter' => '', 'site_domain' => '', 'submit_referer' => '', 'submit_uri' => '', 'user_ID' => '', 'user_agent' => '', 'user_id' => '', 'user_ip' => '' );
    12 	private static $comment_as_submitted_allowed_keys = array( 'blog' => '', 'blog_charset' => '', 'blog_lang' => '', 'blog_ua' => '', 'comment_agent' => '', 'comment_author' => '', 'comment_author_IP' => '', 'comment_author_email' => '', 'comment_author_url' => '', 'comment_content' => '', 'comment_date_gmt' => '', 'comment_tags' => '', 'comment_type' => '', 'guid' => '', 'is_test' => '', 'permalink' => '', 'reporter' => '', 'site_domain' => '', 'submit_referer' => '', 'submit_uri' => '', 'user_ID' => '', 'user_agent' => '', 'user_id' => '', 'user_ip' => '' );
    13 
    13 	private static $is_rest_api_call = false;
       
    14 	
    14 	public static function init() {
    15 	public static function init() {
    15 		if ( ! self::$initiated ) {
    16 		if ( ! self::$initiated ) {
    16 			self::init_hooks();
    17 			self::init_hooks();
    17 		}
    18 		}
    18 	}
    19 	}
    23 	private static function init_hooks() {
    24 	private static function init_hooks() {
    24 		self::$initiated = true;
    25 		self::$initiated = true;
    25 
    26 
    26 		add_action( 'wp_insert_comment', array( 'Akismet', 'auto_check_update_meta' ), 10, 2 );
    27 		add_action( 'wp_insert_comment', array( 'Akismet', 'auto_check_update_meta' ), 10, 2 );
    27 		add_filter( 'preprocess_comment', array( 'Akismet', 'auto_check_comment' ), 1 );
    28 		add_filter( 'preprocess_comment', array( 'Akismet', 'auto_check_comment' ), 1 );
       
    29 		add_filter( 'rest_pre_insert_comment', array( 'Akismet', 'rest_auto_check_comment' ), 1 );
       
    30 
    28 		add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments' ) );
    31 		add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments' ) );
    29 		add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments_meta' ) );
    32 		add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_old_comments_meta' ) );
       
    33 		add_action( 'akismet_scheduled_delete', array( 'Akismet', 'delete_orphaned_commentmeta' ) );
    30 		add_action( 'akismet_schedule_cron_recheck', array( 'Akismet', 'cron_recheck' ) );
    34 		add_action( 'akismet_schedule_cron_recheck', array( 'Akismet', 'cron_recheck' ) );
    31 
    35 
    32 		$akismet_comment_nonce_option = apply_filters( 'akismet_comment_nonce', get_option( 'akismet_comment_nonce' ) );
    36 		add_action( 'comment_form',  array( 'Akismet',  'add_comment_nonce' ), 1 );
    33 
       
    34 		if ( $akismet_comment_nonce_option == 'true' || $akismet_comment_nonce_option == '' )
       
    35 			add_action( 'comment_form',  array( 'Akismet',  'add_comment_nonce' ), 1 );
       
    36 
    37 
    37 		add_action( 'admin_head-edit-comments.php', array( 'Akismet', 'load_form_js' ) );
    38 		add_action( 'admin_head-edit-comments.php', array( 'Akismet', 'load_form_js' ) );
    38 		add_action( 'comment_form', array( 'Akismet', 'load_form_js' ) );
    39 		add_action( 'comment_form', array( 'Akismet', 'load_form_js' ) );
    39 		add_action( 'comment_form', array( 'Akismet', 'inject_ak_js' ) );
    40 		add_action( 'comment_form', array( 'Akismet', 'inject_ak_js' ) );
       
    41 		add_filter( 'script_loader_tag', array( 'Akismet', 'set_form_js_async' ), 10, 3 );
    40 
    42 
    41 		add_filter( 'comment_moderation_recipients', array( 'Akismet', 'disable_moderation_emails_if_unreachable' ), 1000, 2 );
    43 		add_filter( 'comment_moderation_recipients', array( 'Akismet', 'disable_moderation_emails_if_unreachable' ), 1000, 2 );
    42 		add_filter( 'pre_comment_approved', array( 'Akismet', 'last_comment_status' ), 10, 2 );
    44 		add_filter( 'pre_comment_approved', array( 'Akismet', 'last_comment_status' ), 10, 2 );
    43 		
    45 		
    44 		add_action( 'transition_comment_status', array( 'Akismet', 'transition_comment_status' ), 10, 3 );
    46 		add_action( 'transition_comment_status', array( 'Akismet', 'transition_comment_status' ), 10, 3 );
    45 
    47 
    46 		// Run this early in the pingback call, before doing a remote fetch of the source uri
    48 		// Run this early in the pingback call, before doing a remote fetch of the source uri
    47 		add_action( 'xmlrpc_call', array( 'Akismet', 'pre_check_pingback' ) );
    49 		add_action( 'xmlrpc_call', array( 'Akismet', 'pre_check_pingback' ) );
    48 
    50 		
    49 		if ( '3.0.5' == $GLOBALS['wp_version'] ) {
    51 		// Jetpack compatibility
    50 			remove_filter( 'comment_text', 'wp_kses_data' );
    52 		add_filter( 'jetpack_options_whitelist', array( 'Akismet', 'add_to_jetpack_options_whitelist' ) );
    51 			if ( is_admin() )
    53 		add_action( 'update_option_wordpress_api_key', array( 'Akismet', 'updated_option' ), 10, 2 );
    52 				add_filter( 'comment_text', 'wp_kses_post' );
    54 		add_action( 'add_option_wordpress_api_key', array( 'Akismet', 'added_option' ), 10, 2 );
    53 		}
    55 
       
    56 		add_action( 'comment_form_after',  array( 'Akismet',  'display_comment_form_privacy_notice' ) );
    54 	}
    57 	}
    55 
    58 
    56 	public static function get_api_key() {
    59 	public static function get_api_key() {
    57 		return apply_filters( 'akismet_get_api_key', defined('WPCOM_API_KEY') ? constant('WPCOM_API_KEY') : get_option('wordpress_api_key') );
    60 		return apply_filters( 'akismet_get_api_key', defined('WPCOM_API_KEY') ? constant('WPCOM_API_KEY') : get_option('wordpress_api_key') );
    58 	}
    61 	}
    59 
    62 
    60 	public static function check_key_status( $key, $ip = null ) {
    63 	public static function check_key_status( $key, $ip = null ) {
    61 		return self::http_post( Akismet::build_query( array( 'key' => $key, 'blog' => get_option('home') ) ), 'verify-key', $ip );
    64 		return self::http_post( Akismet::build_query( array( 'key' => $key, 'blog' => get_option( 'home' ) ) ), 'verify-key', $ip );
    62 	}
    65 	}
    63 
    66 
    64 	public static function verify_key( $key, $ip = null ) {
    67 	public static function verify_key( $key, $ip = null ) {
    65 		$response = self::check_key_status( $key, $ip );
    68 		$response = self::check_key_status( $key, $ip );
    66 
    69 
    69 
    72 
    70 		return $response[1];
    73 		return $response[1];
    71 	}
    74 	}
    72 
    75 
    73 	public static function deactivate_key( $key ) {
    76 	public static function deactivate_key( $key ) {
    74 		$response = self::http_post( Akismet::build_query( array( 'key' => $key, 'blog' => get_option('home') ) ), 'deactivate' );
    77 		$response = self::http_post( Akismet::build_query( array( 'key' => $key, 'blog' => get_option( 'home' ) ) ), 'deactivate' );
    75 
    78 
    76 		if ( $response[1] != 'deactivated' )
    79 		if ( $response[1] != 'deactivated' )
    77 			return 'failed';
    80 			return 'failed';
    78 
    81 
    79 		return $response[1];
    82 		return $response[1];
       
    83 	}
       
    84 
       
    85 	/**
       
    86 	 * Add the akismet option to the Jetpack options management whitelist.
       
    87 	 *
       
    88 	 * @param array $options The list of whitelisted option names.
       
    89 	 * @return array The updated whitelist
       
    90 	 */
       
    91 	public static function add_to_jetpack_options_whitelist( $options ) {
       
    92 		$options[] = 'wordpress_api_key';
       
    93 		return $options;
       
    94 	}
       
    95 
       
    96 	/**
       
    97 	 * When the akismet option is updated, run the registration call.
       
    98 	 *
       
    99 	 * This should only be run when the option is updated from the Jetpack/WP.com
       
   100 	 * API call, and only if the new key is different than the old key.
       
   101 	 *
       
   102 	 * @param mixed  $old_value   The old option value.
       
   103 	 * @param mixed  $value       The new option value.
       
   104 	 */
       
   105 	public static function updated_option( $old_value, $value ) {
       
   106 		// Not an API call
       
   107 		if ( ! class_exists( 'WPCOM_JSON_API_Update_Option_Endpoint' ) ) {
       
   108 			return;
       
   109 		}
       
   110 		// Only run the registration if the old key is different.
       
   111 		if ( $old_value !== $value ) {
       
   112 			self::verify_key( $value );
       
   113 		}
       
   114 	}
       
   115 	
       
   116 	/**
       
   117 	 * Treat the creation of an API key the same as updating the API key to a new value.
       
   118 	 *
       
   119 	 * @param mixed  $option_name   Will always be "wordpress_api_key", until something else hooks in here.
       
   120 	 * @param mixed  $value         The option value.
       
   121 	 */
       
   122 	public static function added_option( $option_name, $value ) {
       
   123 		if ( 'wordpress_api_key' === $option_name ) {
       
   124 			return self::updated_option( '', $value );
       
   125 		}
       
   126 	}
       
   127 	
       
   128 	public static function rest_auto_check_comment( $commentdata ) {
       
   129 		self::$is_rest_api_call = true;
       
   130 		
       
   131 		return self::auto_check_comment( $commentdata );
    80 	}
   132 	}
    81 
   133 
    82 	public static function auto_check_comment( $commentdata ) {
   134 	public static function auto_check_comment( $commentdata ) {
    83 		self::$last_comment_result = null;
   135 		self::$last_comment_result = null;
    84 
   136 
    85 		$comment = $commentdata;
   137 		$comment = $commentdata;
    86 
   138 
    87 		$comment['user_ip']      = self::get_ip_address();
   139 		$comment['user_ip']      = self::get_ip_address();
    88 		$comment['user_agent']   = self::get_user_agent();
   140 		$comment['user_agent']   = self::get_user_agent();
    89 		$comment['referrer']     = self::get_referer();
   141 		$comment['referrer']     = self::get_referer();
    90 		$comment['blog']         = get_option('home');
   142 		$comment['blog']         = get_option( 'home' );
    91 		$comment['blog_lang']    = get_locale();
   143 		$comment['blog_lang']    = get_locale();
    92 		$comment['blog_charset'] = get_option('blog_charset');
   144 		$comment['blog_charset'] = get_option('blog_charset');
    93 		$comment['permalink']    = get_permalink( $comment['comment_post_ID'] );
   145 		$comment['permalink']    = get_permalink( $comment['comment_post_ID'] );
    94 
   146 
    95 		if ( !empty( $comment['user_ID'] ) )
   147 		if ( ! empty( $comment['user_ID'] ) ) {
    96 			$comment['user_role'] = Akismet::get_user_roles( $comment['user_ID'] );
   148 			$comment['user_role'] = Akismet::get_user_roles( $comment['user_ID'] );
    97 
   149 		}
       
   150 
       
   151 		/** See filter documentation in init_hooks(). */
    98 		$akismet_nonce_option = apply_filters( 'akismet_comment_nonce', get_option( 'akismet_comment_nonce' ) );
   152 		$akismet_nonce_option = apply_filters( 'akismet_comment_nonce', get_option( 'akismet_comment_nonce' ) );
    99 		$comment['akismet_comment_nonce'] = 'inactive';
   153 		$comment['akismet_comment_nonce'] = 'inactive';
   100 		if ( $akismet_nonce_option == 'true' || $akismet_nonce_option == '' ) {
   154 		if ( $akismet_nonce_option == 'true' || $akismet_nonce_option == '' ) {
   101 			$comment['akismet_comment_nonce'] = 'failed';
   155 			$comment['akismet_comment_nonce'] = 'failed';
   102 			if ( isset( $_POST['akismet_comment_nonce'] ) && wp_verify_nonce( $_POST['akismet_comment_nonce'], 'akismet_comment_nonce_' . $comment['comment_post_ID'] ) )
   156 			if ( isset( $_POST['akismet_comment_nonce'] ) && wp_verify_nonce( $_POST['akismet_comment_nonce'], 'akismet_comment_nonce_' . $comment['comment_post_ID'] ) )
   114 		foreach( $_POST as $key => $value ) {
   168 		foreach( $_POST as $key => $value ) {
   115 			if ( is_string( $value ) )
   169 			if ( is_string( $value ) )
   116 				$comment["POST_{$key}"] = $value;
   170 				$comment["POST_{$key}"] = $value;
   117 		}
   171 		}
   118 
   172 
   119 		$ignore = array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'PHP_AUTH_PW' );
       
   120 
       
   121 		foreach ( $_SERVER as $key => $value ) {
   173 		foreach ( $_SERVER as $key => $value ) {
   122 			if ( !in_array( $key, $ignore ) && is_string($value) )
   174 			if ( ! is_string( $value ) ) {
   123 				$comment["$key"] = $value;
   175 				continue;
   124 			else
   176 			}
   125 				$comment["$key"] = '';
   177 
       
   178 			if ( preg_match( "/^HTTP_COOKIE/", $key ) ) {
       
   179 				continue;
       
   180 			}
       
   181 
       
   182 			// Send any potentially useful $_SERVER vars, but avoid sending junk we don't need.
       
   183 			if ( preg_match( "/^(HTTP_|REMOTE_ADDR|REQUEST_URI|DOCUMENT_URI)/", $key ) ) {
       
   184 				$comment[ "$key" ] = $value;
       
   185 			}
   126 		}
   186 		}
   127 
   187 
   128 		$post = get_post( $comment['comment_post_ID'] );
   188 		$post = get_post( $comment['comment_post_ID'] );
   129 		$comment[ 'comment_post_modified_gmt' ] = $post->post_modified_gmt;
   189 
       
   190 		if ( ! is_null( $post ) ) {
       
   191 			// $post can technically be null, although in the past, it's always been an indicator of another plugin interfering.
       
   192 			$comment[ 'comment_post_modified_gmt' ] = $post->post_modified_gmt;
       
   193 		}
   130 
   194 
   131 		$response = self::http_post( Akismet::build_query( $comment ), 'comment-check' );
   195 		$response = self::http_post( Akismet::build_query( $comment ), 'comment-check' );
   132 
   196 
   133 		do_action( 'akismet_comment_check_response', $response );
   197 		do_action( 'akismet_comment_check_response', $response );
   134 
   198 
   150 			$discard = ( isset( $commentdata['akismet_pro_tip'] ) && $commentdata['akismet_pro_tip'] === 'discard' && self::allow_discard() );
   214 			$discard = ( isset( $commentdata['akismet_pro_tip'] ) && $commentdata['akismet_pro_tip'] === 'discard' && self::allow_discard() );
   151 
   215 
   152 			do_action( 'akismet_spam_caught', $discard );
   216 			do_action( 'akismet_spam_caught', $discard );
   153 
   217 
   154 			if ( $discard ) {
   218 			if ( $discard ) {
       
   219 				// The spam is obvious, so we're bailing out early. 
   155 				// akismet_result_spam() won't be called so bump the counter here
   220 				// akismet_result_spam() won't be called so bump the counter here
   156 				if ( $incr = apply_filters('akismet_spam_count_incr', 1) )
   221 				if ( $incr = apply_filters( 'akismet_spam_count_incr', 1 ) ) {
   157 					update_option( 'akismet_spam_count', get_option('akismet_spam_count') + $incr );
   222 					update_option( 'akismet_spam_count', get_option( 'akismet_spam_count' ) + $incr );
   158 				$redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : get_permalink( $post );
   223 				}
   159 				wp_safe_redirect( esc_url_raw( $redirect_to ) );
   224 
   160 				die();
   225 				if ( self::$is_rest_api_call ) {
       
   226 					return new WP_Error( 'akismet_rest_comment_discarded', __( 'Comment discarded.', 'akismet' ) );
       
   227 				}
       
   228 				else {
       
   229 					// Redirect back to the previous page, or failing that, the post permalink, or failing that, the homepage of the blog.
       
   230 					$redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ( $post ? get_permalink( $post ) : home_url() );
       
   231 					wp_safe_redirect( esc_url_raw( $redirect_to ) );
       
   232 					die();
       
   233 				}
       
   234 			}
       
   235 			else if ( self::$is_rest_api_call ) {
       
   236 				// The way the REST API structures its calls, we can set the comment_approved value right away.
       
   237 				$commentdata['comment_approved'] = 'spam';
   161 			}
   238 			}
   162 		}
   239 		}
   163 		
   240 		
   164 		// if the response is neither true nor false, hold the comment for moderation and schedule a recheck
   241 		// if the response is neither true nor false, hold the comment for moderation and schedule a recheck
   165 		if ( 'true' != $response[1] && 'false' != $response[1] ) {
   242 		if ( 'true' != $response[1] && 'false' != $response[1] ) {
   166 			if ( !current_user_can('moderate_comments') ) {
   243 			if ( !current_user_can('moderate_comments') ) {
   167 				// Comment status should be moderated
   244 				// Comment status should be moderated
   168 				self::$last_comment_result = '0';
   245 				self::$last_comment_result = '0';
   169 			}
   246 			}
   170 			if ( function_exists('wp_next_scheduled') && function_exists('wp_schedule_single_event') ) {
   247 
   171 				if ( !wp_next_scheduled( 'akismet_schedule_cron_recheck' ) ) {
   248 			if ( ! wp_next_scheduled( 'akismet_schedule_cron_recheck' ) ) {
   172 					wp_schedule_single_event( time() + 1200, 'akismet_schedule_cron_recheck' );
   249 				wp_schedule_single_event( time() + 1200, 'akismet_schedule_cron_recheck' );
   173 					do_action( 'akismet_scheduled_recheck', 'invalid-response-' . $response[1] );
   250 				do_action( 'akismet_scheduled_recheck', 'invalid-response-' . $response[1] );
   174 				}
       
   175 			}
   251 			}
   176 
   252 
   177 			self::$prevent_moderation_email_for_these_comments[] = $commentdata;
   253 			self::$prevent_moderation_email_for_these_comments[] = $commentdata;
   178 		}
   254 		}
   179 
   255 
   180 		if ( function_exists('wp_next_scheduled') && function_exists('wp_schedule_event') ) {
   256 		// Delete old comments daily
   181 			// WP 2.1+: delete old comments daily
   257 		if ( ! wp_next_scheduled( 'akismet_scheduled_delete' ) ) {
   182 			if ( !wp_next_scheduled( 'akismet_scheduled_delete' ) )
   258 			wp_schedule_event( time(), 'daily', 'akismet_scheduled_delete' );
   183 				wp_schedule_event( time(), 'daily', 'akismet_scheduled_delete' );
   259 		}
   184 		}
   260 
   185 		elseif ( (mt_rand(1, 10) == 3) ) {
       
   186 			// WP 2.0: run this one time in ten
       
   187 			self::delete_old_comments();
       
   188 		}
       
   189 		
       
   190 		self::set_last_comment( $commentdata );
   261 		self::set_last_comment( $commentdata );
   191 		self::fix_scheduled_recheck();
   262 		self::fix_scheduled_recheck();
   192 
   263 
   193 		return $commentdata;
   264 		return $commentdata;
   194 	}
   265 	}
   214 	}
   285 	}
   215 
   286 
   216 	// this fires on wp_insert_comment.  we can't update comment_meta when auto_check_comment() runs
   287 	// this fires on wp_insert_comment.  we can't update comment_meta when auto_check_comment() runs
   217 	// because we don't know the comment ID at that point.
   288 	// because we don't know the comment ID at that point.
   218 	public static function auto_check_update_meta( $id, $comment ) {
   289 	public static function auto_check_update_meta( $id, $comment ) {
   219 
       
   220 		// failsafe for old WP versions
       
   221 		if ( !function_exists('add_comment_meta') )
       
   222 			return false;
       
   223 
       
   224 		if ( !isset( self::$last_comment['comment_author_email'] ) )
       
   225 			self::$last_comment['comment_author_email'] = '';
       
   226 
       
   227 		// wp_insert_comment() might be called in other contexts, so make sure this is the same comment
   290 		// wp_insert_comment() might be called in other contexts, so make sure this is the same comment
   228 		// as was checked by auto_check_comment
   291 		// as was checked by auto_check_comment
   229 		if ( is_object( $comment ) && !empty( self::$last_comment ) && is_array( self::$last_comment ) ) {
   292 		if ( is_object( $comment ) && !empty( self::$last_comment ) && is_array( self::$last_comment ) ) {
   230 			if ( self::matches_last_comment( $comment ) ) {
   293 			if ( self::matches_last_comment( $comment ) ) {
   231 					
   294 					
   243 							);
   306 							);
   244 					}
   307 					}
   245 					elseif ( self::$last_comment['akismet_result'] == 'false' ) {
   308 					elseif ( self::$last_comment['akismet_result'] == 'false' ) {
   246 						update_comment_meta( $comment->comment_ID, 'akismet_result', 'false' );
   309 						update_comment_meta( $comment->comment_ID, 'akismet_result', 'false' );
   247 						self::update_comment_history( $comment->comment_ID, '', 'check-ham' );
   310 						self::update_comment_history( $comment->comment_ID, '', 'check-ham' );
   248 						if ( $comment->comment_approved == 'spam' ) {
   311 						// Status could be spam or trash, depending on the WP version and whether this change applies:
       
   312 						// https://core.trac.wordpress.org/changeset/34726
       
   313 						if ( $comment->comment_approved == 'spam' || $comment->comment_approved == 'trash' ) {
   249 							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) )
   314 							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) )
   250 								self::update_comment_history( $comment->comment_ID, '', 'wp-blacklisted' );
   315 								self::update_comment_history( $comment->comment_ID, '', 'wp-blacklisted' );
   251 							else
   316 							else
   252 								self::update_comment_history( $comment->comment_ID, '', 'status-changed-'.$comment->comment_approved );
   317 								self::update_comment_history( $comment->comment_ID, '', 'status-changed-'.$comment->comment_approved );
   253 						}
   318 						}
   297 
   362 
   298 			$wpdb->queries = array();
   363 			$wpdb->queries = array();
   299 
   364 
   300 			foreach ( $comment_ids as $comment_id ) {
   365 			foreach ( $comment_ids as $comment_id ) {
   301 				do_action( 'delete_comment', $comment_id );
   366 				do_action( 'delete_comment', $comment_id );
   302 			}
   367 				do_action( 'akismet_batch_delete_count', __FUNCTION__ );
   303 
   368 			}
   304 			$comma_comment_ids = implode( ', ', array_map('intval', $comment_ids) );
   369 
   305 
   370 			// Prepared as strings since comment_id is an unsigned BIGINT, and using %d will constrain the value to the maximum signed BIGINT.
   306 			$wpdb->query("DELETE FROM {$wpdb->comments} WHERE comment_id IN ( $comma_comment_ids )");
   371 			$format_string = implode( ", ", array_fill( 0, count( $comment_ids ), '%s' ) );
   307 			$wpdb->query("DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN ( $comma_comment_ids )");
   372 
       
   373 			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->comments} WHERE comment_id IN ( " . $format_string . " )", $comment_ids ) );
       
   374 			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->commentmeta} WHERE comment_id IN ( " . $format_string . " )", $comment_ids ) );
   308 
   375 
   309 			clean_comment_cache( $comment_ids );
   376 			clean_comment_cache( $comment_ids );
       
   377 			do_action( 'akismet_delete_comment_batch', count( $comment_ids ) );
   310 		}
   378 		}
   311 
   379 
   312 		if ( apply_filters( 'akismet_optimize_table', ( mt_rand(1, 5000) == 11), $wpdb->comments ) ) // lucky number
   380 		if ( apply_filters( 'akismet_optimize_table', ( mt_rand(1, 5000) == 11), $wpdb->comments ) ) // lucky number
   313 			$wpdb->query("OPTIMIZE TABLE {$wpdb->comments}");
   381 			$wpdb->query("OPTIMIZE TABLE {$wpdb->comments}");
   314 	}
   382 	}
   316 	public static function delete_old_comments_meta() {
   384 	public static function delete_old_comments_meta() {
   317 		global $wpdb;
   385 		global $wpdb;
   318 
   386 
   319 		$interval = apply_filters( 'akismet_delete_commentmeta_interval', 15 );
   387 		$interval = apply_filters( 'akismet_delete_commentmeta_interval', 15 );
   320 
   388 
   321 		# enfore a minimum of 1 day
   389 		# enforce a minimum of 1 day
   322 		$interval = absint( $interval );
   390 		$interval = absint( $interval );
   323 		if ( $interval < 1 )
   391 		if ( $interval < 1 )
   324 			$interval = 1;
   392 			$interval = 1;
   325 
   393 
   326 		// akismet_as_submitted meta values are large, so expire them
   394 		// akismet_as_submitted meta values are large, so expire them
   331 
   399 
   332 			$wpdb->queries = array();
   400 			$wpdb->queries = array();
   333 
   401 
   334 			foreach ( $comment_ids as $comment_id ) {
   402 			foreach ( $comment_ids as $comment_id ) {
   335 				delete_comment_meta( $comment_id, 'akismet_as_submitted' );
   403 				delete_comment_meta( $comment_id, 'akismet_as_submitted' );
   336 			}
   404 				do_action( 'akismet_batch_delete_count', __FUNCTION__ );
       
   405 			}
       
   406 
       
   407 			do_action( 'akismet_delete_commentmeta_batch', count( $comment_ids ) );
       
   408 		}
       
   409 
       
   410 		if ( apply_filters( 'akismet_optimize_table', ( mt_rand(1, 5000) == 11), $wpdb->commentmeta ) ) // lucky number
       
   411 			$wpdb->query("OPTIMIZE TABLE {$wpdb->commentmeta}");
       
   412 	}
       
   413 
       
   414 	// Clear out comments meta that no longer have corresponding comments in the database
       
   415 	public static function delete_orphaned_commentmeta() {
       
   416 		global $wpdb;
       
   417 
       
   418 		$last_meta_id = 0;
       
   419 		$start_time = isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime( true );
       
   420 		$max_exec_time = max( ini_get('max_execution_time') - 5, 3 );
       
   421 
       
   422 		while ( $commentmeta_results = $wpdb->get_results( $wpdb->prepare( "SELECT m.meta_id, m.comment_id, m.meta_key FROM {$wpdb->commentmeta} as m LEFT JOIN {$wpdb->comments} as c USING(comment_id) WHERE c.comment_id IS NULL AND m.meta_id > %d ORDER BY m.meta_id LIMIT 1000", $last_meta_id ) ) ) {
       
   423 			if ( empty( $commentmeta_results ) )
       
   424 				return;
       
   425 
       
   426 			$wpdb->queries = array();
       
   427 
       
   428 			$commentmeta_deleted = 0;
       
   429 
       
   430 			foreach ( $commentmeta_results as $commentmeta ) {
       
   431 				if ( 'akismet_' == substr( $commentmeta->meta_key, 0, 8 ) ) {
       
   432 					delete_comment_meta( $commentmeta->comment_id, $commentmeta->meta_key );
       
   433 					do_action( 'akismet_batch_delete_count', __FUNCTION__ );
       
   434 					$commentmeta_deleted++;
       
   435 				}
       
   436 
       
   437 				$last_meta_id = $commentmeta->meta_id;
       
   438 			}
       
   439 
       
   440 			do_action( 'akismet_delete_commentmeta_batch', $commentmeta_deleted );
       
   441 
       
   442 			// If we're getting close to max_execution_time, quit for this round.
       
   443 			if ( microtime(true) - $start_time > $max_exec_time )
       
   444 				return;
   337 		}
   445 		}
   338 
   446 
   339 		if ( apply_filters( 'akismet_optimize_table', ( mt_rand(1, 5000) == 11), $wpdb->commentmeta ) ) // lucky number
   447 		if ( apply_filters( 'akismet_optimize_table', ( mt_rand(1, 5000) == 11), $wpdb->commentmeta ) ) // lucky number
   340 			$wpdb->query("OPTIMIZE TABLE {$wpdb->commentmeta}");
   448 			$wpdb->query("OPTIMIZE TABLE {$wpdb->commentmeta}");
   341 	}
   449 	}
   353 		return 0;
   461 		return 0;
   354 	}
   462 	}
   355 
   463 
   356 	// get the full comment history for a given comment, as an array in reverse chronological order
   464 	// get the full comment history for a given comment, as an array in reverse chronological order
   357 	public static function get_comment_history( $comment_id ) {
   465 	public static function get_comment_history( $comment_id ) {
   358 
       
   359 		// failsafe for old WP versions
       
   360 		if ( !function_exists('add_comment_meta') )
       
   361 			return false;
       
   362 
       
   363 		$history = get_comment_meta( $comment_id, 'akismet_history', false );
   466 		$history = get_comment_meta( $comment_id, 'akismet_history', false );
   364 		usort( $history, array( 'Akismet', '_cmp_time' ) );
   467 		usort( $history, array( 'Akismet', '_cmp_time' ) );
   365 		return $history;
   468 		return $history;
   366 	}
   469 	}
   367 
   470 
   374 	 * @param array $meta Metadata about the history entry. e.g., the user that reported or changed the status of a given comment.
   477 	 * @param array $meta Metadata about the history entry. e.g., the user that reported or changed the status of a given comment.
   375 	 */
   478 	 */
   376 	public static function update_comment_history( $comment_id, $message, $event=null, $meta=null ) {
   479 	public static function update_comment_history( $comment_id, $message, $event=null, $meta=null ) {
   377 		global $current_user;
   480 		global $current_user;
   378 
   481 
   379 		// failsafe for old WP versions
       
   380 		if ( !function_exists('add_comment_meta') )
       
   381 			return false;
       
   382 
       
   383 		$user = '';
   482 		$user = '';
   384 
   483 
   385 		$event = array(
   484 		$event = array(
   386 			'time'    => self::_get_microtime(),
   485 			'time'    => self::_get_microtime(),
   387 			'event'   => $event,
   486 			'event'   => $event,
   401 
   500 
   402 	public static function check_db_comment( $id, $recheck_reason = 'recheck_queue' ) {
   501 	public static function check_db_comment( $id, $recheck_reason = 'recheck_queue' ) {
   403 		global $wpdb;
   502 		global $wpdb;
   404 
   503 
   405 		$c = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->comments} WHERE comment_ID = %d", $id ), ARRAY_A );
   504 		$c = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->comments} WHERE comment_ID = %d", $id ), ARRAY_A );
   406 		if ( !$c )
   505 		
   407 			return;
   506 		if ( ! $c ) {
       
   507 			return new WP_Error( 'invalid-comment-id', __( 'Comment not found.', 'akismet' ) );
       
   508 		}
   408 
   509 
   409 		$c['user_ip']        = $c['comment_author_IP'];
   510 		$c['user_ip']        = $c['comment_author_IP'];
   410 		$c['user_agent']     = $c['comment_agent'];
   511 		$c['user_agent']     = $c['comment_agent'];
   411 		$c['referrer']       = '';
   512 		$c['referrer']       = '';
   412 		$c['blog']           = get_option('home');
   513 		$c['blog']           = get_option( 'home' );
   413 		$c['blog_lang']      = get_locale();
   514 		$c['blog_lang']      = get_locale();
   414 		$c['blog_charset']   = get_option('blog_charset');
   515 		$c['blog_charset']   = get_option('blog_charset');
   415 		$c['permalink']      = get_permalink($c['comment_post_ID']);
   516 		$c['permalink']      = get_permalink($c['comment_post_ID']);
   416 		$c['recheck_reason'] = $recheck_reason;
   517 		$c['recheck_reason'] = $recheck_reason;
   417 
   518 
       
   519 		$c['user_role'] = '';
       
   520 		if ( ! empty( $c['user_ID'] ) ) {
       
   521 			$c['user_role'] = Akismet::get_user_roles( $c['user_ID'] );
       
   522 		}
       
   523 
   418 		if ( self::is_test_mode() )
   524 		if ( self::is_test_mode() )
   419 			$c['is_test'] = 'true';
   525 			$c['is_test'] = 'true';
   420 
   526 
   421 		$response = self::http_post( Akismet::build_query( $c ), 'comment-check' );
   527 		$response = self::http_post( Akismet::build_query( $c ), 'comment-check' );
   422 
   528 
   423 		return ( is_array( $response ) && ! empty( $response[1] ) ) ? $response[1] : false;
   529 		if ( ! empty( $response[1] ) ) {
   424 	}
   530 			return $response[1];
   425 	
   531 		}
   426 	
   532 
       
   533 		return false;
       
   534 	}
       
   535 	
       
   536 	public static function recheck_comment( $id, $recheck_reason = 'recheck_queue' ) {
       
   537 		add_comment_meta( $id, 'akismet_rechecking', true );
       
   538 		
       
   539 		$api_response = self::check_db_comment( $id, $recheck_reason );
       
   540 
       
   541 		delete_comment_meta( $id, 'akismet_rechecking' );
       
   542 
       
   543 		if ( is_wp_error( $api_response ) ) {
       
   544 			// Invalid comment ID.
       
   545 		}
       
   546 		else if ( 'true' === $api_response ) {
       
   547 			wp_set_comment_status( $id, 'spam' );
       
   548 			update_comment_meta( $id, 'akismet_result', 'true' );
       
   549 			delete_comment_meta( $id, 'akismet_error' );
       
   550 			delete_comment_meta( $id, 'akismet_delayed_moderation_email' );
       
   551 			Akismet::update_comment_history( $id, '', 'recheck-spam' );
       
   552 		}
       
   553 		elseif ( 'false' === $api_response ) {
       
   554 			update_comment_meta( $id, 'akismet_result', 'false' );
       
   555 			delete_comment_meta( $id, 'akismet_error' );
       
   556 			delete_comment_meta( $id, 'akismet_delayed_moderation_email' );
       
   557 			Akismet::update_comment_history( $id, '', 'recheck-ham' );
       
   558 		}
       
   559 		else {
       
   560 			// abnormal result: error
       
   561 			update_comment_meta( $id, 'akismet_result', 'error' );
       
   562 			Akismet::update_comment_history(
       
   563 				$id,
       
   564 				'',
       
   565 				'recheck-error',
       
   566 				array( 'response' => substr( $api_response, 0, 50 ) )
       
   567 			);
       
   568 		}
       
   569 
       
   570 		return $api_response;
       
   571 	}
   427 
   572 
   428 	public static function transition_comment_status( $new_status, $old_status, $comment ) {
   573 	public static function transition_comment_status( $new_status, $old_status, $comment ) {
   429 		
   574 		
   430 		if ( $new_status == $old_status )
   575 		if ( $new_status == $old_status )
   431 			return;
   576 			return;
   442 			
   587 			
   443 		// if this is present, it means the status has been changed by a re-check, not an explicit user action
   588 		// if this is present, it means the status has been changed by a re-check, not an explicit user action
   444 		if ( get_comment_meta( $comment->comment_ID, 'akismet_rechecking' ) )
   589 		if ( get_comment_meta( $comment->comment_ID, 'akismet_rechecking' ) )
   445 			return;
   590 			return;
   446 		
   591 		
   447 		global $current_user;
       
   448 		$reporter = '';
       
   449 		if ( is_object( $current_user ) )
       
   450 			$reporter = $current_user->user_login;
       
   451 
       
   452 		// Assumption alert:
   592 		// Assumption alert:
   453 		// We want to submit comments to Akismet only when a moderator explicitly spams or approves it - not if the status
   593 		// We want to submit comments to Akismet only when a moderator explicitly spams or approves it - not if the status
   454 		// is changed automatically by another plugin.  Unfortunately WordPress doesn't provide an unambiguous way to
   594 		// is changed automatically by another plugin.  Unfortunately WordPress doesn't provide an unambiguous way to
   455 		// determine why the transition_comment_status action was triggered.  And there are several different ways by which
   595 		// determine why the transition_comment_status action was triggered.  And there are several different ways by which
   456 		// to spam and unspam comments: bulk actions, ajax, links in moderation emails, the dashboard, and perhaps others.
   596 		// to spam and unspam comments: bulk actions, ajax, links in moderation emails, the dashboard, and perhaps others.
   457 		// We'll assume that this is an explicit user action if certain POST/GET variables exist.
   597 		// We'll assume that this is an explicit user action if certain POST/GET variables exist.
   458 		if ( ( isset( $_POST['status'] ) && in_array( $_POST['status'], array( 'spam', 'unspam' ) ) ) ||
   598 		if (
   459 			 ( isset( $_POST['spam'] )   && (int) $_POST['spam'] == 1 ) ||
   599 			 // status=spam: Marking as spam via the REST API or...
   460 			 ( isset( $_POST['unspam'] ) && (int) $_POST['unspam'] == 1 ) ||
   600 			 // status=unspam: I'm not sure. Maybe this used to be used instead of status=approved? Or the UI for removing from spam but not approving has been since removed?...
   461 			 ( isset( $_POST['comment_status'] )  && in_array( $_POST['comment_status'], array( 'spam', 'unspam' ) ) ) ||
   601 			 // status=approved: Unspamming via the REST API (Calypso) or...
   462 			 ( isset( $_GET['action'] )  && in_array( $_GET['action'], array( 'spam', 'unspam' ) ) ) ||
   602 			 ( isset( $_POST['status'] ) && in_array( $_POST['status'], array( 'spam', 'unspam', 'approved', ) ) )
   463 			 ( isset( $_POST['action'] ) && in_array( $_POST['action'], array( 'editedcomment' ) ) )
   603 			 // spam=1: Clicking "Spam" underneath a comment in wp-admin and allowing the AJAX request to happen.
       
   604 			 || ( isset( $_POST['spam'] ) && (int) $_POST['spam'] == 1 )
       
   605 			 // unspam=1: Clicking "Not Spam" underneath a comment in wp-admin and allowing the AJAX request to happen. Or, clicking "Undo" after marking something as spam.
       
   606 			 || ( isset( $_POST['unspam'] ) && (int) $_POST['unspam'] == 1 )
       
   607 			 // comment_status=spam/unspam: It's unclear where this is happening.
       
   608 			 || ( isset( $_POST['comment_status'] )  && in_array( $_POST['comment_status'], array( 'spam', 'unspam' ) ) )
       
   609 			 // action=spam: Choosing "Mark as Spam" from the Bulk Actions dropdown in wp-admin (or the "Spam it" link in notification emails).
       
   610 			 // action=unspam: Choosing "Not Spam" from the Bulk Actions dropdown in wp-admin.
       
   611 			 // action=spamcomment: Following the "Spam" link below a comment in wp-admin (not allowing AJAX request to happen).
       
   612 			 // action=unspamcomment: Following the "Not Spam" link below a comment in wp-admin (not allowing AJAX request to happen).
       
   613 			 || ( isset( $_GET['action'] ) && in_array( $_GET['action'], array( 'spam', 'unspam', 'spamcomment', 'unspamcomment', ) ) )
       
   614 			 // action=editedcomment: Editing a comment via wp-admin (and possibly changing its status).
       
   615 			 || ( isset( $_POST['action'] ) && in_array( $_POST['action'], array( 'editedcomment' ) ) )
       
   616 			 // for=jetpack: Moderation via the WordPress app, Calypso, anything powered by the Jetpack connection.
       
   617 			 || ( isset( $_GET['for'] ) && ( 'jetpack' == $_GET['for'] ) && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) ) 
       
   618 			 // Certain WordPress.com API requests
       
   619 			 || ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST )
       
   620 			 // WordPress.org REST API requests
       
   621 			 || ( defined( 'REST_REQUEST' ) && REST_REQUEST )
   464 		 ) {
   622 		 ) {
   465 			if ( $new_status == 'spam' && ( $old_status == 'approved' || $old_status == 'unapproved' || !$old_status ) ) {
   623 			if ( $new_status == 'spam' && ( $old_status == 'approved' || $old_status == 'unapproved' || !$old_status ) ) {
   466 				return self::submit_spam_comment( $comment->comment_ID );
   624 				return self::submit_spam_comment( $comment->comment_ID );
   467 			} elseif ( $old_status == 'spam' && ( $new_status == 'approved' || $new_status == 'unapproved' ) ) {
   625 			} elseif ( $old_status == 'spam' && ( $new_status == 'approved' || $new_status == 'unapproved' ) ) {
   468 				return self::submit_nonspam_comment( $comment->comment_ID );
   626 				return self::submit_nonspam_comment( $comment->comment_ID );
   489 		$as_submitted = self::sanitize_comment_as_submitted( get_comment_meta( $comment_id, 'akismet_as_submitted', true ) );
   647 		$as_submitted = self::sanitize_comment_as_submitted( get_comment_meta( $comment_id, 'akismet_as_submitted', true ) );
   490 
   648 
   491 		if ( $as_submitted && is_array( $as_submitted ) && isset( $as_submitted['comment_content'] ) )
   649 		if ( $as_submitted && is_array( $as_submitted ) && isset( $as_submitted['comment_content'] ) )
   492 			$comment = (object) array_merge( (array)$comment, $as_submitted );
   650 			$comment = (object) array_merge( (array)$comment, $as_submitted );
   493 
   651 
   494 		$comment->blog         = get_bloginfo('url');
   652 		$comment->blog         = get_option( 'home' );
   495 		$comment->blog_lang    = get_locale();
   653 		$comment->blog_lang    = get_locale();
   496 		$comment->blog_charset = get_option('blog_charset');
   654 		$comment->blog_charset = get_option('blog_charset');
   497 		$comment->permalink    = get_permalink($comment->comment_post_ID);
   655 		$comment->permalink    = get_permalink($comment->comment_post_ID);
   498 
   656 
   499 		if ( is_object($current_user) )
   657 		if ( is_object($current_user) )
   501 
   659 
   502 		if ( is_object($current_site) )
   660 		if ( is_object($current_site) )
   503 			$comment->site_domain = $current_site->domain;
   661 			$comment->site_domain = $current_site->domain;
   504 
   662 
   505 		$comment->user_role = '';
   663 		$comment->user_role = '';
   506 		if ( isset( $comment->user_ID ) )
   664 		if ( ! empty( $comment->user_ID ) ) {
   507 			$comment->user_role = Akismet::get_user_roles( $comment->user_ID );
   665 			$comment->user_role = Akismet::get_user_roles( $comment->user_ID );
       
   666 		}
   508 
   667 
   509 		if ( self::is_test_mode() )
   668 		if ( self::is_test_mode() )
   510 			$comment->is_test = 'true';
   669 			$comment->is_test = 'true';
   511 
   670 
   512 		$post = get_post( $comment->comment_post_ID );
   671 		$post = get_post( $comment->comment_post_ID );
   513 		$comment->comment_post_modified_gmt = $post->post_modified_gmt;
   672 
       
   673 		if ( ! is_null( $post ) ) {
       
   674 			$comment->comment_post_modified_gmt = $post->post_modified_gmt;
       
   675 		}
   514 
   676 
   515 		$response = Akismet::http_post( Akismet::build_query( $comment ), 'submit-spam' );
   677 		$response = Akismet::http_post( Akismet::build_query( $comment ), 'submit-spam' );
   516 		if ( $comment->reporter ) {
   678 		if ( $comment->reporter ) {
   517 			self::update_comment_history( $comment_id, '', 'report-spam' );
   679 			self::update_comment_history( $comment_id, '', 'report-spam' );
   518 			update_comment_meta( $comment_id, 'akismet_user_result', 'true' );
   680 			update_comment_meta( $comment_id, 'akismet_user_result', 'true' );
   535 		$as_submitted = self::sanitize_comment_as_submitted( get_comment_meta( $comment_id, 'akismet_as_submitted', true ) );
   697 		$as_submitted = self::sanitize_comment_as_submitted( get_comment_meta( $comment_id, 'akismet_as_submitted', true ) );
   536 
   698 
   537 		if ( $as_submitted && is_array($as_submitted) && isset($as_submitted['comment_content']) )
   699 		if ( $as_submitted && is_array($as_submitted) && isset($as_submitted['comment_content']) )
   538 			$comment = (object) array_merge( (array)$comment, $as_submitted );
   700 			$comment = (object) array_merge( (array)$comment, $as_submitted );
   539 
   701 
   540 		$comment->blog         = get_bloginfo('url');
   702 		$comment->blog         = get_option( 'home' );
   541 		$comment->blog_lang    = get_locale();
   703 		$comment->blog_lang    = get_locale();
   542 		$comment->blog_charset = get_option('blog_charset');
   704 		$comment->blog_charset = get_option('blog_charset');
   543 		$comment->permalink    = get_permalink( $comment->comment_post_ID );
   705 		$comment->permalink    = get_permalink( $comment->comment_post_ID );
   544 		$comment->user_role    = '';
   706 		$comment->user_role    = '';
   545 
   707 
   547 			$comment->reporter = $current_user->user_login;
   709 			$comment->reporter = $current_user->user_login;
   548 
   710 
   549 		if ( is_object($current_site) )
   711 		if ( is_object($current_site) )
   550 			$comment->site_domain = $current_site->domain;
   712 			$comment->site_domain = $current_site->domain;
   551 
   713 
   552 		if ( isset( $comment->user_ID ) )
   714 		if ( ! empty( $comment->user_ID ) ) {
   553 			$comment->user_role = Akismet::get_user_roles($comment->user_ID);
   715 			$comment->user_role = Akismet::get_user_roles( $comment->user_ID );
       
   716 		}
   554 
   717 
   555 		if ( Akismet::is_test_mode() )
   718 		if ( Akismet::is_test_mode() )
   556 			$comment->is_test = 'true';
   719 			$comment->is_test = 'true';
   557 
   720 
   558 		$post = get_post( $comment->comment_post_ID );
   721 		$post = get_post( $comment->comment_post_ID );
   559 		$comment->comment_post_modified_gmt = $post->post_modified_gmt;
   722 
       
   723 		if ( ! is_null( $post ) ) {
       
   724 			$comment->comment_post_modified_gmt = $post->post_modified_gmt;
       
   725 		}
   560 
   726 
   561 		$response = self::http_post( Akismet::build_query( $comment ), 'submit-ham' );
   727 		$response = self::http_post( Akismet::build_query( $comment ), 'submit-ham' );
   562 		if ( $comment->reporter ) {
   728 		if ( $comment->reporter ) {
   563 			self::update_comment_history( $comment_id, '', 'report-ham' );
   729 			self::update_comment_history( $comment_id, '', 'report-ham' );
   564 			update_comment_meta( $comment_id, 'akismet_user_result', 'false' );
   730 			update_comment_meta( $comment_id, 'akismet_user_result', 'false' );
   588 		load_plugin_textdomain( 'akismet' );
   754 		load_plugin_textdomain( 'akismet' );
   589 
   755 
   590 		foreach ( (array) $comment_errors as $comment_id ) {
   756 		foreach ( (array) $comment_errors as $comment_id ) {
   591 			// if the comment no longer exists, or is too old, remove the meta entry from the queue to avoid getting stuck
   757 			// if the comment no longer exists, or is too old, remove the meta entry from the queue to avoid getting stuck
   592 			$comment = get_comment( $comment_id );
   758 			$comment = get_comment( $comment_id );
   593 			if ( !$comment || strtotime( $comment->comment_date_gmt ) < strtotime( "-15 days" ) ) {
   759 
       
   760 			if (
       
   761 				! $comment // Comment has been deleted
       
   762 				|| strtotime( $comment->comment_date_gmt ) < strtotime( "-15 days" ) // Comment is too old.
       
   763 				|| $comment->comment_approved !== "0" // Comment is no longer in the Pending queue
       
   764 				) {
       
   765 				echo "Deleting";
   594 				delete_comment_meta( $comment_id, 'akismet_error' );
   766 				delete_comment_meta( $comment_id, 'akismet_error' );
   595 				delete_comment_meta( $comment_id, 'akismet_delayed_moderation_email' );
   767 				delete_comment_meta( $comment_id, 'akismet_delayed_moderation_email' );
   596 				continue;
   768 				continue;
   597 			}
   769 			}
   598 
   770 
   669 			do_action( 'akismet_scheduled_recheck', 'fix-scheduled-recheck' );
   841 			do_action( 'akismet_scheduled_recheck', 'fix-scheduled-recheck' );
   670 		}
   842 		}
   671 	}
   843 	}
   672 
   844 
   673 	public static function add_comment_nonce( $post_id ) {
   845 	public static function add_comment_nonce( $post_id ) {
   674 		echo '<p style="display: none;">';
   846 		/**
   675 		wp_nonce_field( 'akismet_comment_nonce_' . $post_id, 'akismet_comment_nonce', FALSE );
   847 		 * To disable the Akismet comment nonce, add a filter for the 'akismet_comment_nonce' tag
   676 		echo '</p>';
   848 		 * and return any string value that is not 'true' or '' (empty string).
       
   849 		 *
       
   850 		 * Don't return boolean false, because that implies that the 'akismet_comment_nonce' option
       
   851 		 * has not been set and that Akismet should just choose the default behavior for that
       
   852 		 * situation.
       
   853 		 */
       
   854 		$akismet_comment_nonce_option = apply_filters( 'akismet_comment_nonce', get_option( 'akismet_comment_nonce' ) );
       
   855 
       
   856 		if ( $akismet_comment_nonce_option == 'true' || $akismet_comment_nonce_option == '' ) {
       
   857 			echo '<p style="display: none;">';
       
   858 			wp_nonce_field( 'akismet_comment_nonce_' . $post_id, 'akismet_comment_nonce', FALSE );
       
   859 			echo '</p>';
       
   860 		}
   677 	}
   861 	}
   678 
   862 
   679 	public static function is_test_mode() {
   863 	public static function is_test_mode() {
   680 		return defined('AKISMET_TEST_MODE') && AKISMET_TEST_MODE;
   864 		return defined('AKISMET_TEST_MODE') && AKISMET_TEST_MODE;
   681 	}
   865 	}
   701 	 * @return bool Whether the two comments should be treated as the same comment.
   885 	 * @return bool Whether the two comments should be treated as the same comment.
   702 	 */
   886 	 */
   703 	private static function comments_match( $comment1, $comment2 ) {
   887 	private static function comments_match( $comment1, $comment2 ) {
   704 		$comment1 = (array) $comment1;
   888 		$comment1 = (array) $comment1;
   705 		$comment2 = (array) $comment2;
   889 		$comment2 = (array) $comment2;
   706 		
   890 
   707 		return (
   891 		// Set default values for these strings that we check in order to simplify
       
   892 		// the checks and avoid PHP warnings.
       
   893 		if ( ! isset( $comment1['comment_author'] ) ) {
       
   894 			$comment1['comment_author'] = '';
       
   895 		}
       
   896 
       
   897 		if ( ! isset( $comment2['comment_author'] ) ) {
       
   898 			$comment2['comment_author'] = '';
       
   899 		}
       
   900 
       
   901 		if ( ! isset( $comment1['comment_author_email'] ) ) {
       
   902 			$comment1['comment_author_email'] = '';
       
   903 		}
       
   904 
       
   905 		if ( ! isset( $comment2['comment_author_email'] ) ) {
       
   906 			$comment2['comment_author_email'] = '';
       
   907 		}
       
   908 
       
   909 		$comments_match = (
   708 			   isset( $comment1['comment_post_ID'], $comment2['comment_post_ID'] )
   910 			   isset( $comment1['comment_post_ID'], $comment2['comment_post_ID'] )
   709 			&& intval( $comment1['comment_post_ID'] ) == intval( $comment2['comment_post_ID'] )
   911 			&& intval( $comment1['comment_post_ID'] ) == intval( $comment2['comment_post_ID'] )
   710 			&& (
   912 			&& (
   711 				$comment1['comment_author'] == $comment2['comment_author']
   913 				// The comment author length max is 255 characters, limited by the TINYTEXT column type.
   712 				|| stripslashes( $comment1['comment_author'] ) == $comment2['comment_author']
   914 				// If the comment author includes multibyte characters right around the 255-byte mark, they
   713 				|| $comment1['comment_author'] == stripslashes( $comment2['comment_author'] )
   915 				// may be stripped when the author is saved in the DB, so a 300+ char author may turn into
       
   916 				// a 253-char author when it's saved, not 255 exactly.  The longest possible character is
       
   917 				// theoretically 6 bytes, so we'll only look at the first 248 bytes to be safe.
       
   918 				substr( $comment1['comment_author'], 0, 248 ) == substr( $comment2['comment_author'], 0, 248 )
       
   919 				|| substr( stripslashes( $comment1['comment_author'] ), 0, 248 ) == substr( $comment2['comment_author'], 0, 248 )
       
   920 				|| substr( $comment1['comment_author'], 0, 248 ) == substr( stripslashes( $comment2['comment_author'] ), 0, 248 )
       
   921 				// Certain long comment author names will be truncated to nothing, depending on their encoding.
       
   922 				|| ( ! $comment1['comment_author'] && strlen( $comment2['comment_author'] ) > 248 )
       
   923 				|| ( ! $comment2['comment_author'] && strlen( $comment1['comment_author'] ) > 248 )
   714 				)
   924 				)
   715 			&& (
   925 			&& (
   716 				$comment1['comment_author_email'] == $comment2['comment_author_email']
   926 				// The email max length is 100 characters, limited by the VARCHAR(100) column type.
   717 				|| stripslashes( $comment1['comment_author_email'] ) == $comment2['comment_author_email']
   927 				// Same argument as above for only looking at the first 93 characters.
   718 				|| $comment1['comment_author_email'] == stripslashes( $comment2['comment_author_email'] )
   928 				substr( $comment1['comment_author_email'], 0, 93 ) == substr( $comment2['comment_author_email'], 0, 93 )
       
   929 				|| substr( stripslashes( $comment1['comment_author_email'] ), 0, 93 ) == substr( $comment2['comment_author_email'], 0, 93 )
       
   930 				|| substr( $comment1['comment_author_email'], 0, 93 ) == substr( stripslashes( $comment2['comment_author_email'] ), 0, 93 )
       
   931 				// Very long emails can be truncated and then stripped if the [0:100] substring isn't a valid address.
       
   932 				|| ( ! $comment1['comment_author_email'] && strlen( $comment2['comment_author_email'] ) > 100 )
       
   933 				|| ( ! $comment2['comment_author_email'] && strlen( $comment1['comment_author_email'] ) > 100 )
   719 			)
   934 			)
   720 		);
   935 		);
       
   936 
       
   937 		return $comments_match;
   721 	}
   938 	}
   722 	
   939 	
   723 	// Does the supplied comment match the details of the one most recently stored in self::$last_comment?
   940 	// Does the supplied comment match the details of the one most recently stored in self::$last_comment?
   724 	public static function matches_last_comment( $comment ) {
   941 	public static function matches_last_comment( $comment ) {
   725 		if ( is_object( $comment ) )
       
   726 			$comment = (array) $comment;
       
   727 
       
   728 		return self::comments_match( self::$last_comment, $comment );
   942 		return self::comments_match( self::$last_comment, $comment );
   729 	}
   943 	}
   730 
   944 
   731 	private static function get_user_agent() {
   945 	private static function get_user_agent() {
   732 		return isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null;
   946 		return isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null;
   761 		return $roles;
   975 		return $roles;
   762 	}
   976 	}
   763 
   977 
   764 	// filter handler used to return a spam result to pre_comment_approved
   978 	// filter handler used to return a spam result to pre_comment_approved
   765 	public static function last_comment_status( $approved, $comment ) {
   979 	public static function last_comment_status( $approved, $comment ) {
       
   980 		if ( is_null( self::$last_comment_result ) ) {
       
   981 			// We didn't have reason to store the result of the last check.
       
   982 			return $approved;
       
   983 		}
       
   984 
   766 		// Only do this if it's the correct comment
   985 		// Only do this if it's the correct comment
   767 		if ( is_null(self::$last_comment_result) || ! self::matches_last_comment( $comment ) ) {
   986 		if ( ! self::matches_last_comment( $comment ) ) {
   768 			self::log( "comment_is_spam mismatched comment, returning unaltered $approved" );
   987 			self::log( "comment_is_spam mismatched comment, returning unaltered $approved" );
       
   988 			return $approved;
       
   989 		}
       
   990 
       
   991 		if ( 'trash' === $approved ) {
       
   992 			// If the last comment we checked has had its approval set to 'trash',
       
   993 			// then it failed the comment blacklist check. Let that blacklist override
       
   994 			// the spam check, since users have the (valid) expectation that when
       
   995 			// they fill out their blacklists, comments that match it will always
       
   996 			// end up in the trash.
   769 			return $approved;
   997 			return $approved;
   770 		}
   998 		}
   771 
   999 
   772 		// bump the counter here instead of when the filter is added to reduce the possibility of overcounting
  1000 		// bump the counter here instead of when the filter is added to reduce the possibility of overcounting
   773 		if ( $incr = apply_filters('akismet_spam_count_incr', 1) )
  1001 		if ( $incr = apply_filters('akismet_spam_count_incr', 1) )
   868 		}
  1096 		}
   869 		else if ( $ssl_disabled ) {
  1097 		else if ( $ssl_disabled ) {
   870 			do_action( 'akismet_ssl_disabled' );
  1098 			do_action( 'akismet_ssl_disabled' );
   871 		}
  1099 		}
   872 
  1100 
   873 		if ( ! $ssl_disabled && function_exists( 'wp_http_supports') && ( $ssl = wp_http_supports( array( 'ssl' ) ) ) ) {
  1101 		if ( ! $ssl_disabled && ( $ssl = wp_http_supports( array( 'ssl' ) ) ) ) {
   874 			$akismet_url = set_url_scheme( $akismet_url, 'https' );
  1102 			$akismet_url = set_url_scheme( $akismet_url, 'https' );
   875 
  1103 
   876 			do_action( 'akismet_https_request_pre' );
  1104 			do_action( 'akismet_https_request_pre' );
   877 		}
  1105 		}
   878 
  1106 
   923 
  1151 
   924 		return $simplified_response;
  1152 		return $simplified_response;
   925 	}
  1153 	}
   926 
  1154 
   927 	// given a response from an API call like check_key_status(), update the alert code options if an alert is present.
  1155 	// given a response from an API call like check_key_status(), update the alert code options if an alert is present.
   928 	private static function update_alert( $response ) {
  1156 	public static function update_alert( $response ) {
   929 		$code = $msg = null;
  1157 		$code = $msg = null;
   930 		if ( isset( $response[0]['x-akismet-alert-code'] ) ) {
  1158 		if ( isset( $response[0]['x-akismet-alert-code'] ) ) {
   931 			$code = $response[0]['x-akismet-alert-code'];
  1159 			$code = $response[0]['x-akismet-alert-code'];
   932 			$msg  = $response[0]['x-akismet-alert-msg'];
  1160 			$msg  = $response[0]['x-akismet-alert-msg'];
   933 		}
  1161 		}
   944 			}
  1172 			}
   945 		}
  1173 		}
   946 	}
  1174 	}
   947 
  1175 
   948 	public static function load_form_js() {
  1176 	public static function load_form_js() {
   949 		// WP < 3.3 can't enqueue a script this late in the game and still have it appear in the footer.
  1177 		wp_register_script( 'akismet-form', plugin_dir_url( __FILE__ ) . '_inc/form.js', array(), AKISMET_VERSION, true );
   950 		// Once we drop support for everything pre-3.3, this can change back to a single enqueue call.
  1178 		wp_enqueue_script( 'akismet-form' );
   951 		wp_register_script( 'akismet-form', AKISMET__PLUGIN_URL . '_inc/form.js', array(), AKISMET_VERSION, true );
  1179 	}
   952 		add_action( 'wp_footer', array( 'Akismet', 'print_form_js' ) );
  1180 	
   953 		add_action( 'admin_footer', array( 'Akismet', 'print_form_js' ) );
  1181 	/**
   954 	}
  1182 	 * Mark form.js as async. Because nothing depends on it, it can run at any time
   955 	
  1183 	 * after it's loaded, and the browser won't have to wait for it to load to continue
   956 	public static function print_form_js() {
  1184 	 * parsing the rest of the page.
   957 		wp_print_scripts( 'akismet-form' );
  1185 	 */
   958 	}
  1186 	public static function set_form_js_async( $tag, $handle, $src ) {
   959 
  1187 		if ( 'akismet-form' !== $handle ) {
       
  1188 			return $tag;
       
  1189 		}
       
  1190 		
       
  1191 		return preg_replace( '/^<script /i', '<script async="async" ', $tag );
       
  1192 	}
       
  1193 	
   960 	public static function inject_ak_js( $fields ) {
  1194 	public static function inject_ak_js( $fields ) {
   961 		echo '<p style="display: none;">';
  1195 		echo '<p style="display: none;">';
   962 		echo '<input type="hidden" id="ak_js" name="ak_js" value="' . mt_rand( 0, 250 ) . '"/>';
  1196 		echo '<input type="hidden" id="ak_js" name="ak_js" value="' . mt_rand( 0, 250 ) . '"/>';
   963 		echo '</p>';
  1197 		echo '</p>';
   964 	}
  1198 	}
   966 	private static function bail_on_activation( $message, $deactivate = true ) {
  1200 	private static function bail_on_activation( $message, $deactivate = true ) {
   967 ?>
  1201 ?>
   968 <!doctype html>
  1202 <!doctype html>
   969 <html>
  1203 <html>
   970 <head>
  1204 <head>
   971 <meta charset="<?php bloginfo( 'charset' ); ?>">
  1205 <meta charset="<?php bloginfo( 'charset' ); ?>" />
   972 <style>
  1206 <style>
   973 * {
  1207 * {
   974 	text-align: center;
  1208 	text-align: center;
   975 	margin: 0;
  1209 	margin: 0;
   976 	padding: 0;
  1210 	padding: 0;
   979 p {
  1213 p {
   980 	margin-top: 1em;
  1214 	margin-top: 1em;
   981 	font-size: 18px;
  1215 	font-size: 18px;
   982 }
  1216 }
   983 </style>
  1217 </style>
       
  1218 </head>
   984 <body>
  1219 <body>
   985 <p><?php echo esc_html( $message ); ?></p>
  1220 <p><?php echo esc_html( $message ); ?></p>
   986 </body>
  1221 </body>
   987 </html>
  1222 </html>
   988 <?php
  1223 <?php
  1024 	 */
  1259 	 */
  1025 	public static function plugin_activation() {
  1260 	public static function plugin_activation() {
  1026 		if ( version_compare( $GLOBALS['wp_version'], AKISMET__MINIMUM_WP_VERSION, '<' ) ) {
  1261 		if ( version_compare( $GLOBALS['wp_version'], AKISMET__MINIMUM_WP_VERSION, '<' ) ) {
  1027 			load_plugin_textdomain( 'akismet' );
  1262 			load_plugin_textdomain( 'akismet' );
  1028 			
  1263 			
  1029 			$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', 'http://wordpress.org/extend/plugins/akismet/download/');
  1264 			$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/');
  1030 
  1265 
  1031 			Akismet::bail_on_activation( $message );
  1266 			Akismet::bail_on_activation( $message );
  1032 		}
  1267 		}
  1033 	}
  1268 	}
  1034 
  1269 
  1035 	/**
  1270 	/**
  1036 	 * Removes all connection options
  1271 	 * Removes all connection options
  1037 	 * @static
  1272 	 * @static
  1038 	 */
  1273 	 */
  1039 	public static function plugin_deactivation( ) {
  1274 	public static function plugin_deactivation( ) {
  1040 		return self::deactivate_key( self::get_api_key() );
  1275 		self::deactivate_key( self::get_api_key() );
       
  1276 		
       
  1277 		// Remove any scheduled cron jobs.
       
  1278 		$akismet_cron_events = array(
       
  1279 			'akismet_schedule_cron_recheck',
       
  1280 			'akismet_scheduled_delete',
       
  1281 		);
       
  1282 		
       
  1283 		foreach ( $akismet_cron_events as $akismet_cron_event ) {
       
  1284 			$timestamp = wp_next_scheduled( $akismet_cron_event );
       
  1285 			
       
  1286 			if ( $timestamp ) {
       
  1287 				wp_unschedule_event( $timestamp, $akismet_cron_event );
       
  1288 			}
       
  1289 		}
  1041 	}
  1290 	}
  1042 	
  1291 	
  1043 	/**
  1292 	/**
  1044 	 * Essentially a copy of WP's build_query but one that doesn't expect pre-urlencoded values.
  1293 	 * Essentially a copy of WP's build_query but one that doesn't expect pre-urlencoded values.
  1045 	 *
  1294 	 *
  1051 	}
  1300 	}
  1052 
  1301 
  1053 	/**
  1302 	/**
  1054 	 * Log debugging info to the error log.
  1303 	 * Log debugging info to the error log.
  1055 	 *
  1304 	 *
  1056 	 * Enabled when WP_DEBUG_LOG is enabled, but can be disabled via the akismet_debug_log filter.
  1305 	 * Enabled when WP_DEBUG_LOG is enabled (and WP_DEBUG, since according to
       
  1306 	 * core, "WP_DEBUG_DISPLAY and WP_DEBUG_LOG perform no function unless
       
  1307 	 * WP_DEBUG is true), but can be disabled via the akismet_debug_log filter.
  1057 	 *
  1308 	 *
  1058 	 * @param mixed $akismet_debug The data to log.
  1309 	 * @param mixed $akismet_debug The data to log.
  1059 	 */
  1310 	 */
  1060 	public static function log( $akismet_debug ) {
  1311 	public static function log( $akismet_debug ) {
  1061 		if ( apply_filters( 'akismet_debug_log', defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) ) {
  1312 		if ( apply_filters( 'akismet_debug_log', defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG && defined( 'AKISMET_DEBUG' ) && AKISMET_DEBUG ) ) {
  1062 			error_log( print_r( compact( 'akismet_debug' ), true ) );
  1313 			error_log( print_r( compact( 'akismet_debug' ), true ) );
  1063 		}
  1314 		}
  1064 	}
  1315 	}
  1065 
  1316 
  1066 	public static function pre_check_pingback( $method ) {
  1317 	public static function pre_check_pingback( $method ) {
  1143 			}
  1394 			}
  1144 		}
  1395 		}
  1145 
  1396 
  1146 		return $meta_value;
  1397 		return $meta_value;
  1147 	}
  1398 	}
       
  1399 	
       
  1400 	public static function predefined_api_key() {
       
  1401 		if ( defined( 'WPCOM_API_KEY' ) ) {
       
  1402 			return true;
       
  1403 		}
       
  1404 		
       
  1405 		return apply_filters( 'akismet_predefined_api_key', false );
       
  1406 	}
       
  1407 
       
  1408 	/**
       
  1409 	 * Controls the display of a privacy related notice underneath the comment form using the `akismet_comment_form_privacy_notice` option and filter respectively.
       
  1410 	 * Default is top not display the notice, leaving the choice to site admins, or integrators.
       
  1411 	 */
       
  1412 	public static function display_comment_form_privacy_notice() {
       
  1413 		if ( 'display' !== apply_filters( 'akismet_comment_form_privacy_notice', get_option( 'akismet_comment_form_privacy_notice', 'hide' ) ) ) {
       
  1414 			return;
       
  1415 		}
       
  1416 		echo apply_filters(
       
  1417 			'akismet_comment_form_privacy_notice_markup',
       
  1418 			'<p class="akismet_comment_form_privacy_notice">' . sprintf(
       
  1419 				__( 'This site uses Akismet to reduce spam. <a href="%s" target="_blank" rel="nofollow noopener">Learn how your comment data is processed</a>.', 'akismet' ),
       
  1420 				'https://akismet.com/privacy/'
       
  1421 			) . '</p>'
       
  1422 		);
       
  1423 	}
  1148 }
  1424 }