ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class.akismet-admin.php (51105B)


      1 <?php
      2 
      3 class Akismet_Admin {
      4 	const NONCE = 'akismet-update-key';
      5 
      6 	private static $initiated = false;
      7 	private static $notices   = array();
      8 	private static $allowed   = array(
      9 	    'a' => array(
     10 	        'href' => true,
     11 	        'title' => true,
     12 	    ),
     13 	    'b' => array(),
     14 	    'code' => array(),
     15 	    'del' => array(
     16 	        'datetime' => true,
     17 	    ),
     18 	    'em' => array(),
     19 	    'i' => array(),
     20 	    'q' => array(
     21 	        'cite' => true,
     22 	    ),
     23 	    'strike' => array(),
     24 	    'strong' => array(),
     25 	);
     26 
     27 	public static function init() {
     28 		if ( ! self::$initiated ) {
     29 			self::init_hooks();
     30 		}
     31 
     32 		if ( isset( $_POST['action'] ) && $_POST['action'] == 'enter-key' ) {
     33 			self::enter_api_key();
     34 		}
     35 	}
     36 
     37 	public static function init_hooks() {
     38 		// The standalone stats page was removed in 3.0 for an all-in-one config and stats page.
     39 		// Redirect any links that might have been bookmarked or in browser history.
     40 		if ( isset( $_GET['page'] ) && 'akismet-stats-display' == $_GET['page'] ) {
     41 			wp_safe_redirect( esc_url_raw( self::get_page_url( 'stats' ) ), 301 );
     42 			die;
     43 		}
     44 
     45 		self::$initiated = true;
     46 
     47 		add_action( 'admin_init', array( 'Akismet_Admin', 'admin_init' ) );
     48 		add_action( 'admin_menu', array( 'Akismet_Admin', 'admin_menu' ), 5 ); # Priority 5, so it's called before Jetpack's admin_menu.
     49 		add_action( 'admin_notices', array( 'Akismet_Admin', 'display_notice' ) );
     50 		add_action( 'admin_enqueue_scripts', array( 'Akismet_Admin', 'load_resources' ) );
     51 		add_action( 'activity_box_end', array( 'Akismet_Admin', 'dashboard_stats' ) );
     52 		add_action( 'rightnow_end', array( 'Akismet_Admin', 'rightnow_stats' ) );
     53 		add_action( 'manage_comments_nav', array( 'Akismet_Admin', 'check_for_spam_button' ) );
     54 		add_action( 'admin_action_akismet_recheck_queue', array( 'Akismet_Admin', 'recheck_queue' ) );
     55 		add_action( 'wp_ajax_akismet_recheck_queue', array( 'Akismet_Admin', 'recheck_queue' ) );
     56 		add_action( 'wp_ajax_comment_author_deurl', array( 'Akismet_Admin', 'remove_comment_author_url' ) );
     57 		add_action( 'wp_ajax_comment_author_reurl', array( 'Akismet_Admin', 'add_comment_author_url' ) );
     58 		add_action( 'jetpack_auto_activate_akismet', array( 'Akismet_Admin', 'connect_jetpack_user' ) );
     59 
     60 		add_filter( 'plugin_action_links', array( 'Akismet_Admin', 'plugin_action_links' ), 10, 2 );
     61 		add_filter( 'comment_row_actions', array( 'Akismet_Admin', 'comment_row_action' ), 10, 2 );
     62 		
     63 		add_filter( 'plugin_action_links_'.plugin_basename( plugin_dir_path( __FILE__ ) . 'akismet.php'), array( 'Akismet_Admin', 'admin_plugin_settings_link' ) );
     64 		
     65 		add_filter( 'wxr_export_skip_commentmeta', array( 'Akismet_Admin', 'exclude_commentmeta_from_export' ), 10, 3 );
     66 		
     67 		add_filter( 'all_plugins', array( 'Akismet_Admin', 'modify_plugin_description' ) );
     68 
     69 		// priority=1 because we need ours to run before core's comment anonymizer runs, and that's registered at priority=10
     70 		add_filter( 'wp_privacy_personal_data_erasers', array( 'Akismet_Admin', 'register_personal_data_eraser' ), 1 );
     71 	}
     72 
     73 	public static function admin_init() {
     74 		if ( get_option( 'Activated_Akismet' ) ) {
     75 			delete_option( 'Activated_Akismet' );
     76 			if ( ! headers_sent() ) {
     77 				wp_redirect( add_query_arg( array( 'page' => 'akismet-key-config', 'view' => 'start' ), class_exists( 'Jetpack' ) ? admin_url( 'admin.php' ) : admin_url( 'options-general.php' ) ) );
     78 			}
     79 		}
     80 
     81 		load_plugin_textdomain( 'akismet' );
     82 		add_meta_box( 'akismet-status', __('Comment History', 'akismet'), array( 'Akismet_Admin', 'comment_status_meta_box' ), 'comment', 'normal' );
     83 
     84 		if ( function_exists( 'wp_add_privacy_policy_content' ) ) {
     85 			wp_add_privacy_policy_content(
     86 				__( 'Akismet', 'akismet' ),
     87 				__( 'We collect information about visitors who comment on Sites that use our Akismet anti-spam service. The information we collect depends on how the User sets up Akismet for the Site, but typically includes the commenter\'s IP address, user agent, referrer, and Site URL (along with other information directly provided by the commenter such as their name, username, email address, and the comment itself).', 'akismet' )
     88 			);
     89 		}
     90 	}
     91 
     92 	public static function admin_menu() {
     93 		if ( class_exists( 'Jetpack' ) )
     94 			add_action( 'jetpack_admin_menu', array( 'Akismet_Admin', 'load_menu' ) );
     95 		else
     96 			self::load_menu();
     97 	}
     98 
     99 	public static function admin_head() {
    100 		if ( !current_user_can( 'manage_options' ) )
    101 			return;
    102 	}
    103 	
    104 	public static function admin_plugin_settings_link( $links ) { 
    105   		$settings_link = '<a href="'.esc_url( self::get_page_url() ).'">'.__('Settings', 'akismet').'</a>';
    106   		array_unshift( $links, $settings_link ); 
    107   		return $links; 
    108 	}
    109 
    110 	public static function load_menu() {
    111 		if ( class_exists( 'Jetpack' ) ) {
    112 			$hook = add_submenu_page( 'jetpack', __( 'Akismet Anti-Spam' , 'akismet'), __( 'Akismet Anti-Spam' , 'akismet'), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
    113 		}
    114 		else {
    115 			$hook = add_options_page( __('Akismet Anti-Spam', 'akismet'), __('Akismet Anti-Spam', 'akismet'), 'manage_options', 'akismet-key-config', array( 'Akismet_Admin', 'display_page' ) );
    116 		}
    117 		
    118 		if ( $hook ) {
    119 			add_action( "load-$hook", array( 'Akismet_Admin', 'admin_help' ) );
    120 		}
    121 	}
    122 
    123 	public static function load_resources() {
    124 		global $hook_suffix;
    125 
    126 		if ( in_array( $hook_suffix, apply_filters( 'akismet_admin_page_hook_suffixes', array(
    127 			'index.php', # dashboard
    128 			'edit-comments.php',
    129 			'comment.php',
    130 			'post.php',
    131 			'settings_page_akismet-key-config',
    132 			'jetpack_page_akismet-key-config',
    133 			'plugins.php',
    134 		) ) ) ) {
    135 			wp_register_style( 'akismet.css', plugin_dir_url( __FILE__ ) . '_inc/akismet.css', array(), AKISMET_VERSION );
    136 			wp_enqueue_style( 'akismet.css');
    137 
    138 			wp_register_script( 'akismet.js', plugin_dir_url( __FILE__ ) . '_inc/akismet.js', array('jquery'), AKISMET_VERSION );
    139 			wp_enqueue_script( 'akismet.js' );
    140 		
    141 			$inline_js = array(
    142 				'comment_author_url_nonce' => wp_create_nonce( 'comment_author_url_nonce' ),
    143 				'strings' => array(
    144 					'Remove this URL' => __( 'Remove this URL' , 'akismet'),
    145 					'Removing...'     => __( 'Removing...' , 'akismet'),
    146 					'URL removed'     => __( 'URL removed' , 'akismet'),
    147 					'(undo)'          => __( '(undo)' , 'akismet'),
    148 					'Re-adding...'    => __( 'Re-adding...' , 'akismet'),
    149 				)
    150 			);
    151 
    152 			if ( isset( $_GET['akismet_recheck'] ) && wp_verify_nonce( $_GET['akismet_recheck'], 'akismet_recheck' ) ) {
    153 				$inline_js['start_recheck'] = true;
    154 			}
    155 
    156 			if ( apply_filters( 'akismet_enable_mshots', true ) ) {
    157 				$inline_js['enable_mshots'] = true;
    158 			}
    159 
    160 			wp_localize_script( 'akismet.js', 'WPAkismet', $inline_js );
    161 		}
    162 	}
    163 
    164 	/**
    165 	 * Add help to the Akismet page
    166 	 *
    167 	 * @return false if not the Akismet page
    168 	 */
    169 	public static function admin_help() {
    170 		$current_screen = get_current_screen();
    171 
    172 		// Screen Content
    173 		if ( current_user_can( 'manage_options' ) ) {
    174 			if ( !Akismet::get_api_key() || ( isset( $_GET['view'] ) && $_GET['view'] == 'start' ) ) {
    175 				//setup page
    176 				$current_screen->add_help_tab(
    177 					array(
    178 						'id'		=> 'overview',
    179 						'title'		=> __( 'Overview' , 'akismet'),
    180 						'content'	=>
    181 							'<p><strong>' . esc_html__( 'Akismet Setup' , 'akismet') . '</strong></p>' .
    182 							'<p>' . esc_html__( 'Akismet filters out spam, so you can focus on more important things.' , 'akismet') . '</p>' .
    183 							'<p>' . esc_html__( 'On this page, you are able to set up the Akismet plugin.' , 'akismet') . '</p>',
    184 					)
    185 				);
    186 
    187 				$current_screen->add_help_tab(
    188 					array(
    189 						'id'		=> 'setup-signup',
    190 						'title'		=> __( 'New to Akismet' , 'akismet'),
    191 						'content'	=>
    192 							'<p><strong>' . esc_html__( 'Akismet Setup' , 'akismet') . '</strong></p>' .
    193 							'<p>' . esc_html__( 'You need to enter an API key to activate the Akismet service on your site.' , 'akismet') . '</p>' .
    194 							'<p>' . sprintf( __( 'Sign up for an account on %s to get an API Key.' , 'akismet'), '<a href="https://akismet.com/plugin-signup/" target="_blank">Akismet.com</a>' ) . '</p>',
    195 					)
    196 				);
    197 
    198 				$current_screen->add_help_tab(
    199 					array(
    200 						'id'		=> 'setup-manual',
    201 						'title'		=> __( 'Enter an API Key' , 'akismet'),
    202 						'content'	=>
    203 							'<p><strong>' . esc_html__( 'Akismet Setup' , 'akismet') . '</strong></p>' .
    204 							'<p>' . esc_html__( 'If you already have an API key' , 'akismet') . '</p>' .
    205 							'<ol>' .
    206 								'<li>' . esc_html__( 'Copy and paste the API key into the text field.' , 'akismet') . '</li>' .
    207 								'<li>' . esc_html__( 'Click the Use this Key button.' , 'akismet') . '</li>' .
    208 							'</ol>',
    209 					)
    210 				);
    211 			}
    212 			elseif ( isset( $_GET['view'] ) && $_GET['view'] == 'stats' ) {
    213 				//stats page
    214 				$current_screen->add_help_tab(
    215 					array(
    216 						'id'		=> 'overview',
    217 						'title'		=> __( 'Overview' , 'akismet'),
    218 						'content'	=>
    219 							'<p><strong>' . esc_html__( 'Akismet Stats' , 'akismet') . '</strong></p>' .
    220 							'<p>' . esc_html__( 'Akismet filters out spam, so you can focus on more important things.' , 'akismet') . '</p>' .
    221 							'<p>' . esc_html__( 'On this page, you are able to view stats on spam filtered on your site.' , 'akismet') . '</p>',
    222 					)
    223 				);
    224 			}
    225 			else {
    226 				//configuration page
    227 				$current_screen->add_help_tab(
    228 					array(
    229 						'id'		=> 'overview',
    230 						'title'		=> __( 'Overview' , 'akismet'),
    231 						'content'	=>
    232 							'<p><strong>' . esc_html__( 'Akismet Configuration' , 'akismet') . '</strong></p>' .
    233 							'<p>' . esc_html__( 'Akismet filters out spam, so you can focus on more important things.' , 'akismet') . '</p>' .
    234 							'<p>' . esc_html__( 'On this page, you are able to update your Akismet settings and view spam stats.' , 'akismet') . '</p>',
    235 					)
    236 				);
    237 
    238 				$current_screen->add_help_tab(
    239 					array(
    240 						'id'		=> 'settings',
    241 						'title'		=> __( 'Settings' , 'akismet'),
    242 						'content'	=>
    243 							'<p><strong>' . esc_html__( 'Akismet Configuration' , 'akismet') . '</strong></p>' .
    244 							( Akismet::predefined_api_key() ? '' : '<p><strong>' . esc_html__( 'API Key' , 'akismet') . '</strong> - ' . esc_html__( 'Enter/remove an API key.' , 'akismet') . '</p>' ) .
    245 							'<p><strong>' . esc_html__( 'Comments' , 'akismet') . '</strong> - ' . esc_html__( 'Show the number of approved comments beside each comment author in the comments list page.' , 'akismet') . '</p>' .
    246 							'<p><strong>' . esc_html__( 'Strictness' , 'akismet') . '</strong> - ' . esc_html__( 'Choose to either discard the worst spam automatically or to always put all spam in spam folder.' , 'akismet') . '</p>',
    247 					)
    248 				);
    249 
    250 				if ( ! Akismet::predefined_api_key() ) {
    251 					$current_screen->add_help_tab(
    252 						array(
    253 							'id'		=> 'account',
    254 							'title'		=> __( 'Account' , 'akismet'),
    255 							'content'	=>
    256 								'<p><strong>' . esc_html__( 'Akismet Configuration' , 'akismet') . '</strong></p>' .
    257 								'<p><strong>' . esc_html__( 'Subscription Type' , 'akismet') . '</strong> - ' . esc_html__( 'The Akismet subscription plan' , 'akismet') . '</p>' .
    258 								'<p><strong>' . esc_html__( 'Status' , 'akismet') . '</strong> - ' . esc_html__( 'The subscription status - active, cancelled or suspended' , 'akismet') . '</p>',
    259 						)
    260 					);
    261 				}
    262 			}
    263 		}
    264 
    265 		// Help Sidebar
    266 		$current_screen->set_help_sidebar(
    267 			'<p><strong>' . esc_html__( 'For more information:' , 'akismet') . '</strong></p>' .
    268 			'<p><a href="https://akismet.com/faq/" target="_blank">'     . esc_html__( 'Akismet FAQ' , 'akismet') . '</a></p>' .
    269 			'<p><a href="https://akismet.com/support/" target="_blank">' . esc_html__( 'Akismet Support' , 'akismet') . '</a></p>'
    270 		);
    271 	}
    272 
    273 	public static function enter_api_key() {
    274 		if ( ! current_user_can( 'manage_options' ) ) {
    275 			die( __( 'Cheatin&#8217; uh?', 'akismet' ) );
    276 		}
    277 
    278 		if ( !wp_verify_nonce( $_POST['_wpnonce'], self::NONCE ) )
    279 			return false;
    280 
    281 		foreach( array( 'akismet_strictness', 'akismet_show_user_comments_approved' ) as $option ) {
    282 			update_option( $option, isset( $_POST[$option] ) && (int) $_POST[$option] == 1 ? '1' : '0' );
    283 		}
    284 
    285 		if ( ! empty( $_POST['akismet_comment_form_privacy_notice'] ) ) {
    286 			self::set_form_privacy_notice_option( $_POST['akismet_comment_form_privacy_notice'] );
    287 		} else {
    288 			self::set_form_privacy_notice_option( 'hide' );
    289 		}
    290 
    291 		if ( Akismet::predefined_api_key() ) {
    292 			return false; //shouldn't have option to save key if already defined
    293 		}
    294 		
    295 		$new_key = preg_replace( '/[^a-f0-9]/i', '', $_POST['key'] );
    296 		$old_key = Akismet::get_api_key();
    297 
    298 		if ( empty( $new_key ) ) {
    299 			if ( !empty( $old_key ) ) {
    300 				delete_option( 'wordpress_api_key' );
    301 				self::$notices[] = 'new-key-empty';
    302 			}
    303 		}
    304 		elseif ( $new_key != $old_key ) {
    305 			self::save_key( $new_key );
    306 		}
    307 
    308 		return true;
    309 	}
    310 
    311 	public static function save_key( $api_key ) {
    312 		$key_status = Akismet::verify_key( $api_key );
    313 
    314 		if ( $key_status == 'valid' ) {
    315 			$akismet_user = self::get_akismet_user( $api_key );
    316 			
    317 			if ( $akismet_user ) {				
    318 				if ( in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) ) )
    319 					update_option( 'wordpress_api_key', $api_key );
    320 				
    321 				if ( $akismet_user->status == 'active' )
    322 					self::$notices['status'] = 'new-key-valid';
    323 				elseif ( $akismet_user->status == 'notice' )
    324 					self::$notices['status'] = $akismet_user;
    325 				else
    326 					self::$notices['status'] = $akismet_user->status;
    327 			}
    328 			else
    329 				self::$notices['status'] = 'new-key-invalid';
    330 		}
    331 		elseif ( in_array( $key_status, array( 'invalid', 'failed' ) ) )
    332 			self::$notices['status'] = 'new-key-'.$key_status;
    333 	}
    334 
    335 	public static function dashboard_stats() {
    336 		if ( did_action( 'rightnow_end' ) ) {
    337 			return; // We already displayed this info in the "Right Now" section
    338 		}
    339 
    340 		if ( !$count = get_option('akismet_spam_count') )
    341 			return;
    342 
    343 		global $submenu;
    344 
    345 		echo '<h3>' . esc_html( _x( 'Spam', 'comments' , 'akismet') ) . '</h3>';
    346 
    347 		echo '<p>'.sprintf( _n(
    348 				'<a href="%1$s">Akismet</a> has protected your site from <a href="%2$s">%3$s spam comment</a>.',
    349 				'<a href="%1$s">Akismet</a> has protected your site from <a href="%2$s">%3$s spam comments</a>.',
    350 				$count
    351 			, 'akismet'), 'https://akismet.com/wordpress/', esc_url( add_query_arg( array( 'page' => 'akismet-admin' ), admin_url( isset( $submenu['edit-comments.php'] ) ? 'edit-comments.php' : 'edit.php' ) ) ), number_format_i18n($count) ).'</p>';
    352 	}
    353 
    354 	// WP 2.5+
    355 	public static function rightnow_stats() {
    356 		if ( $count = get_option('akismet_spam_count') ) {
    357 			$intro = sprintf( _n(
    358 				'<a href="%1$s">Akismet</a> has protected your site from %2$s spam comment already. ',
    359 				'<a href="%1$s">Akismet</a> has protected your site from %2$s spam comments already. ',
    360 				$count
    361 			, 'akismet'), 'https://akismet.com/wordpress/', number_format_i18n( $count ) );
    362 		} else {
    363 			$intro = sprintf( __('<a href="%s">Akismet</a> blocks spam from getting to your blog. ', 'akismet'), 'https://akismet.com/wordpress/' );
    364 		}
    365 
    366 		$link = add_query_arg( array( 'comment_status' => 'spam' ), admin_url( 'edit-comments.php' ) );
    367 
    368 		if ( $queue_count = self::get_spam_count() ) {
    369 			$queue_text = sprintf( _n(
    370 				'There&#8217;s <a href="%2$s">%1$s comment</a> in your spam queue right now.',
    371 				'There are <a href="%2$s">%1$s comments</a> in your spam queue right now.',
    372 				$queue_count
    373 			, 'akismet'), number_format_i18n( $queue_count ), esc_url( $link ) );
    374 		} else {
    375 			$queue_text = sprintf( __( "There&#8217;s nothing in your <a href='%s'>spam queue</a> at the moment." , 'akismet'), esc_url( $link ) );
    376 		}
    377 
    378 		$text = $intro . '<br />' . $queue_text;
    379 		echo "<p class='akismet-right-now'>$text</p>\n";
    380 	}
    381 
    382 	public static function check_for_spam_button( $comment_status ) {
    383 		// The "Check for Spam" button should only appear when the page might be showing
    384 		// a comment with comment_approved=0, which means an un-trashed, un-spammed,
    385 		// not-yet-moderated comment.
    386 		if ( 'all' != $comment_status && 'moderated' != $comment_status ) {
    387 			return;
    388 		}
    389 
    390 		$link = '';
    391 
    392 		$comments_count = wp_count_comments();
    393 		
    394 		echo '</div>';
    395 		echo '<div class="alignleft actions">';
    396 
    397 		$classes = array(
    398 			'button-secondary',
    399 			'checkforspam',
    400 			'button-disabled'	// Disable button until the page is loaded
    401 		);
    402 
    403 		if ( $comments_count->moderated > 0 ) {
    404 			$classes[] = 'enable-on-load';
    405 
    406 			if ( ! Akismet::get_api_key() ) {
    407 				$link = add_query_arg( array( 'page' => 'akismet-key-config' ), class_exists( 'Jetpack' ) ? admin_url( 'admin.php' ) : admin_url( 'options-general.php' ) );
    408 				$classes[] = 'ajax-disabled';
    409 			}
    410 		}
    411 
    412 		echo '<a
    413 				class="' . esc_attr( implode( ' ', $classes ) ) . '"' .
    414 				( ! empty( $link ) ? ' href="' . esc_url( $link ) . '"' : '' ) .
    415 				/* translators: The placeholder is for showing how much of the process has completed, as a percent. e.g., "Checking for Spam (40%)" */
    416 				' data-progress-label="' . esc_attr( __( 'Checking for Spam (%1$s%)', 'akismet' ) ) . '"
    417 				data-success-url="' . esc_attr( remove_query_arg( array( 'akismet_recheck', 'akismet_recheck_error' ), add_query_arg( array( 'akismet_recheck_complete' => 1, 'recheck_count' => urlencode( '__recheck_count__' ), 'spam_count' => urlencode( '__spam_count__' ) ) ) ) ) . '"
    418 				data-failure-url="' . esc_attr( remove_query_arg( array( 'akismet_recheck', 'akismet_recheck_complete' ), add_query_arg( array( 'akismet_recheck_error' => 1 ) ) ) ) . '"
    419 				data-pending-comment-count="' . esc_attr( $comments_count->moderated ) . '"
    420 				data-nonce="' . esc_attr( wp_create_nonce( 'akismet_check_for_spam' ) ) . '"
    421 				' . ( ! in_array( 'ajax-disabled', $classes ) ? 'onclick="return false;"' : '' ) . '
    422 				>' . esc_html__('Check for Spam', 'akismet') . '</a>';
    423 		echo '<span class="checkforspam-spinner"></span>';
    424 	}
    425 
    426 	public static function recheck_queue() {
    427 		global $wpdb;
    428 
    429 		Akismet::fix_scheduled_recheck();
    430 
    431 		if ( ! ( isset( $_GET['recheckqueue'] ) || ( isset( $_REQUEST['action'] ) && 'akismet_recheck_queue' == $_REQUEST['action'] ) ) ) {
    432 			return;
    433 		}
    434 		
    435 		if ( ! wp_verify_nonce( $_POST['nonce'], 'akismet_check_for_spam' ) ) {
    436 			wp_send_json( array(
    437 				'error' => __( "You don't have permission to do that."),
    438 			));
    439 			return;
    440 		}
    441 
    442 		$result_counts = self::recheck_queue_portion( empty( $_POST['offset'] ) ? 0 : $_POST['offset'], empty( $_POST['limit'] ) ? 100 : $_POST['limit'] );
    443 
    444 		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
    445 			wp_send_json( array(
    446 				'counts' => $result_counts,
    447 			));
    448 		}
    449 		else {
    450 			$redirect_to = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : admin_url( 'edit-comments.php' );
    451 			wp_safe_redirect( $redirect_to );
    452 			exit;
    453 		}
    454 	}
    455 	
    456 	public static function recheck_queue_portion( $start = 0, $limit = 100 ) {
    457 		global $wpdb;
    458 		
    459 		$paginate = '';
    460 
    461 		if ( $limit <= 0 ) {
    462 			$limit = 100;
    463 		}
    464 
    465 		if ( $start < 0 ) {
    466 			$start = 0;
    467 		}
    468 
    469 		$moderation = $wpdb->get_col( $wpdb->prepare( "SELECT * FROM {$wpdb->comments} WHERE comment_approved = '0' LIMIT %d OFFSET %d", $limit, $start ) );
    470 
    471 		$result_counts = array(
    472 			'processed' => count( $moderation ),
    473 			'spam' => 0,
    474 			'ham' => 0,
    475 			'error' => 0,
    476 		);
    477 
    478 		foreach ( $moderation as $comment_id ) {
    479 			$api_response = Akismet::recheck_comment( $comment_id, 'recheck_queue' );
    480 
    481 			if ( 'true' === $api_response ) {
    482 				++$result_counts['spam'];
    483 			}
    484 			elseif ( 'false' === $api_response ) {
    485 				++$result_counts['ham'];
    486 			}
    487 			else {
    488 				++$result_counts['error'];
    489 			}
    490 		}
    491 
    492 		return $result_counts;
    493 	}
    494 
    495 	// Adds an 'x' link next to author URLs, clicking will remove the author URL and show an undo link
    496 	public static function remove_comment_author_url() {
    497 		if ( !empty( $_POST['id'] ) && check_admin_referer( 'comment_author_url_nonce' ) ) {
    498 			$comment_id = intval( $_POST['id'] );
    499 			$comment = get_comment( $comment_id, ARRAY_A );
    500 			if ( $comment && current_user_can( 'edit_comment', $comment['comment_ID'] ) ) {
    501 				$comment['comment_author_url'] = '';
    502 				do_action( 'comment_remove_author_url' );
    503 				print( wp_update_comment( $comment ) );
    504 				die();
    505 			}
    506 		}
    507 	}
    508 
    509 	public static function add_comment_author_url() {
    510 		if ( !empty( $_POST['id'] ) && !empty( $_POST['url'] ) && check_admin_referer( 'comment_author_url_nonce' ) ) {
    511 			$comment_id = intval( $_POST['id'] );
    512 			$comment = get_comment( $comment_id, ARRAY_A );
    513 			if ( $comment && current_user_can( 'edit_comment', $comment['comment_ID'] ) ) {
    514 				$comment['comment_author_url'] = esc_url( $_POST['url'] );
    515 				do_action( 'comment_add_author_url' );
    516 				print( wp_update_comment( $comment ) );
    517 				die();
    518 			}
    519 		}
    520 	}
    521 
    522 	public static function comment_row_action( $a, $comment ) {
    523 		$akismet_result = get_comment_meta( $comment->comment_ID, 'akismet_result', true );
    524 		$akismet_error  = get_comment_meta( $comment->comment_ID, 'akismet_error', true );
    525 		$user_result    = get_comment_meta( $comment->comment_ID, 'akismet_user_result', true);
    526 		$comment_status = wp_get_comment_status( $comment->comment_ID );
    527 		$desc = null;
    528 		if ( $akismet_error ) {
    529 			$desc = __( 'Awaiting spam check' , 'akismet');
    530 		} elseif ( !$user_result || $user_result == $akismet_result ) {
    531 			// Show the original Akismet result if the user hasn't overridden it, or if their decision was the same
    532 			if ( $akismet_result == 'true' && $comment_status != 'spam' && $comment_status != 'trash' )
    533 				$desc = __( 'Flagged as spam by Akismet' , 'akismet');
    534 			elseif ( $akismet_result == 'false' && $comment_status == 'spam' )
    535 				$desc = __( 'Cleared by Akismet' , 'akismet');
    536 		} else {
    537 			$who = get_comment_meta( $comment->comment_ID, 'akismet_user', true );
    538 			if ( $user_result == 'true' )
    539 				$desc = sprintf( __('Flagged as spam by %s', 'akismet'), $who );
    540 			else
    541 				$desc = sprintf( __('Un-spammed by %s', 'akismet'), $who );
    542 		}
    543 
    544 		// add a History item to the hover links, just after Edit
    545 		if ( $akismet_result ) {
    546 			$b = array();
    547 			foreach ( $a as $k => $item ) {
    548 				$b[ $k ] = $item;
    549 				if (
    550 					$k == 'edit'
    551 					|| $k == 'unspam'
    552 				) {
    553 					$b['history'] = '<a href="comment.php?action=editcomment&amp;c='.$comment->comment_ID.'#akismet-status" title="'. esc_attr__( 'View comment history' , 'akismet') . '"> '. esc_html__('History', 'akismet') . '</a>';
    554 				}
    555 			}
    556 
    557 			$a = $b;
    558 		}
    559 
    560 		if ( $desc )
    561 			echo '<span class="akismet-status" commentid="'.$comment->comment_ID.'"><a href="comment.php?action=editcomment&amp;c='.$comment->comment_ID.'#akismet-status" title="' . esc_attr__( 'View comment history' , 'akismet') . '">'.esc_html( $desc ).'</a></span>';
    562 
    563 		$show_user_comments_option = get_option( 'akismet_show_user_comments_approved' );
    564 		
    565 		if ( $show_user_comments_option === false ) {
    566 			// Default to active if the user hasn't made a decision.
    567 			$show_user_comments_option = '1';
    568 		}
    569 		
    570 		$show_user_comments = apply_filters( 'akismet_show_user_comments_approved', $show_user_comments_option );
    571 		$show_user_comments = $show_user_comments === 'false' ? false : $show_user_comments; //option used to be saved as 'false' / 'true'
    572 		
    573 		if ( $show_user_comments ) {
    574 			$comment_count = Akismet::get_user_comments_approved( $comment->user_id, $comment->comment_author_email, $comment->comment_author, $comment->comment_author_url );
    575 			$comment_count = intval( $comment_count );
    576 			echo '<span class="akismet-user-comment-count" commentid="'.$comment->comment_ID.'" style="display:none;"><br><span class="akismet-user-comment-counts">'. sprintf( esc_html( _n( '%s approved', '%s approved', $comment_count , 'akismet') ), number_format_i18n( $comment_count ) ) . '</span></span>';
    577 		}
    578 
    579 		return $a;
    580 	}
    581 
    582 	public static function comment_status_meta_box( $comment ) {
    583 		$history = Akismet::get_comment_history( $comment->comment_ID );
    584 
    585 		if ( $history ) {
    586 			foreach ( $history as $row ) {
    587 				$time = date( 'D d M Y @ h:i:s a', $row['time'] ) . ' GMT';
    588 				
    589 				$message = '';
    590 				
    591 				if ( ! empty( $row['message'] ) ) {
    592 					// Old versions of Akismet stored the message as a literal string in the commentmeta.
    593 					// New versions don't do that for two reasons:
    594 					// 1) Save space.
    595 					// 2) The message can be translated into the current language of the blog, not stuck 
    596 					//    in the language of the blog when the comment was made.
    597 					$message = esc_html( $row['message'] );
    598 				}
    599 				
    600 				// If possible, use a current translation.
    601 				switch ( $row['event'] ) {
    602 					case 'recheck-spam';
    603 						$message = esc_html( __( 'Akismet re-checked and caught this comment as spam.', 'akismet' ) );
    604 					break;
    605 					case 'check-spam':
    606 						$message = esc_html( __( 'Akismet caught this comment as spam.', 'akismet' ) );
    607 					break;
    608 					case 'recheck-ham':
    609 						$message = esc_html( __( 'Akismet re-checked and cleared this comment.', 'akismet' ) );
    610 					break;
    611 					case 'check-ham':
    612 						$message = esc_html( __( 'Akismet cleared this comment.', 'akismet' ) );
    613 					break;
    614 					case 'wp-blacklisted':
    615 					case 'wp-disallowed':
    616 						$message = sprintf(
    617 							/* translators: The placeholder is a WordPress PHP function name. */
    618 							esc_html( __( 'Comment was caught by %s.', 'akismet' ) ),
    619 							function_exists( 'wp_check_comment_disallowed_list' ) ? '<code>wp_check_comment_disallowed_list</code>' : '<code>wp_blacklist_check</code>'
    620 						);
    621 					break;
    622 					case 'report-spam':
    623 						if ( isset( $row['user'] ) ) {
    624 							$message = esc_html( sprintf( __( '%s reported this comment as spam.', 'akismet' ), $row['user'] ) );
    625 						}
    626 						else if ( ! $message ) {
    627 							$message = esc_html( __( 'This comment was reported as spam.', 'akismet' ) );
    628 						}
    629 					break;
    630 					case 'report-ham':
    631 						if ( isset( $row['user'] ) ) {
    632 							$message = esc_html( sprintf( __( '%s reported this comment as not spam.', 'akismet' ), $row['user'] ) );
    633 						}
    634 						else if ( ! $message ) {
    635 							$message = esc_html( __( 'This comment was reported as not spam.', 'akismet' ) );
    636 						}
    637 					break;
    638 					case 'cron-retry-spam':
    639 						$message = esc_html( __( 'Akismet caught this comment as spam during an automatic retry.' , 'akismet') );
    640 					break;
    641 					case 'cron-retry-ham':
    642 						$message = esc_html( __( 'Akismet cleared this comment during an automatic retry.', 'akismet') );
    643 					break;
    644 					case 'check-error':
    645 						if ( isset( $row['meta'], $row['meta']['response'] ) ) {
    646 							$message = sprintf( esc_html( __( 'Akismet was unable to check this comment (response: %s) but will automatically retry later.', 'akismet') ), '<code>' . esc_html( $row['meta']['response'] ) . '</code>' );
    647 						}
    648 						else {
    649 							$message = esc_html( __( 'Akismet was unable to check this comment but will automatically retry later.', 'akismet' ) );
    650 						}
    651 					break;
    652 					case 'recheck-error':
    653 						if ( isset( $row['meta'], $row['meta']['response'] ) ) {
    654 							$message = sprintf( esc_html( __( 'Akismet was unable to recheck this comment (response: %s).', 'akismet') ), '<code>' . esc_html( $row['meta']['response'] ) . '</code>' );
    655 						}
    656 						else {
    657 							$message = esc_html( __( 'Akismet was unable to recheck this comment.', 'akismet' ) );
    658 						}
    659 					break;
    660 					default:
    661 						if ( preg_match( '/^status-changed/', $row['event'] ) ) {
    662 							// Half of these used to be saved without the dash after 'status-changed'.
    663 							// See https://plugins.trac.wordpress.org/changeset/1150658/akismet/trunk
    664 							$new_status = preg_replace( '/^status-changed-?/', '', $row['event'] );
    665 							$message = sprintf( esc_html( __( 'Comment status was changed to %s', 'akismet' ) ), '<code>' . esc_html( $new_status ) . '</code>' );
    666 						}
    667 						else if ( preg_match( '/^status-/', $row['event'] ) ) {
    668 							$new_status = preg_replace( '/^status-/', '', $row['event'] );
    669 
    670 							if ( isset( $row['user'] ) ) {
    671 								$message = sprintf( esc_html( __( '%1$s changed the comment status to %2$s.', 'akismet' ) ), $row['user'], '<code>' . esc_html( $new_status ) . '</code>' );
    672 							}
    673 						}
    674 					break;
    675 					
    676 				}
    677 
    678 				if ( ! empty( $message ) ) {
    679 					echo '<p>';
    680 					echo '<span style="color: #999;" alt="' . $time . '" title="' . $time . '">' . sprintf( esc_html__('%s ago', 'akismet'), human_time_diff( $row['time'] ) ) . '</span>';
    681 					echo ' - ';
    682 					echo $message; // esc_html() is done above so that we can use HTML in some messages.
    683 					echo '</p>';
    684 				}
    685 			}
    686 		}
    687 		else {
    688 			echo '<p>';
    689 			echo esc_html( __( 'No comment history.', 'akismet' ) );
    690 			echo '</p>';
    691 		}
    692 	}
    693 
    694 	public static function plugin_action_links( $links, $file ) {
    695 		if ( $file == plugin_basename( plugin_dir_url( __FILE__ ) . '/akismet.php' ) ) {
    696 			$links[] = '<a href="' . esc_url( self::get_page_url() ) . '">'.esc_html__( 'Settings' , 'akismet').'</a>';
    697 		}
    698 
    699 		return $links;
    700 	}
    701 
    702 	// Total spam in queue
    703 	// get_option( 'akismet_spam_count' ) is the total caught ever
    704 	public static function get_spam_count( $type = false ) {
    705 		global $wpdb;
    706 
    707 		if ( !$type ) { // total
    708 			$count = wp_cache_get( 'akismet_spam_count', 'widget' );
    709 			if ( false === $count ) {
    710 				$count = wp_count_comments();
    711 				$count = $count->spam;
    712 				wp_cache_set( 'akismet_spam_count', $count, 'widget', 3600 );
    713 			}
    714 			return $count;
    715 		} elseif ( 'comments' == $type || 'comment' == $type ) { // comments
    716 			$type = '';
    717 		}
    718 
    719 		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(comment_ID) FROM {$wpdb->comments} WHERE comment_approved = 'spam' AND comment_type = %s", $type ) );
    720 	}
    721 
    722 	// Check connectivity between the WordPress blog and Akismet's servers.
    723 	// Returns an associative array of server IP addresses, where the key is the IP address, and value is true (available) or false (unable to connect).
    724 	public static function check_server_ip_connectivity() {
    725 		
    726 		$servers = $ips = array();
    727 
    728 		// Some web hosts may disable this function
    729 		if ( function_exists('gethostbynamel') ) {	
    730 			
    731 			$ips = gethostbynamel( 'rest.akismet.com' );
    732 			if ( $ips && is_array($ips) && count($ips) ) {
    733 				$api_key = Akismet::get_api_key();
    734 				
    735 				foreach ( $ips as $ip ) {
    736 					$response = Akismet::verify_key( $api_key, $ip );
    737 					// even if the key is invalid, at least we know we have connectivity
    738 					if ( $response == 'valid' || $response == 'invalid' )
    739 						$servers[$ip] = 'connected';
    740 					else
    741 						$servers[$ip] = $response ? $response : 'unable to connect';
    742 				}
    743 			}
    744 		}
    745 		
    746 		return $servers;
    747 	}
    748 	
    749 	// Simpler connectivity check
    750 	public static function check_server_connectivity($cache_timeout = 86400) {
    751 		
    752 		$debug = array();
    753 		$debug[ 'PHP_VERSION' ]         = PHP_VERSION;
    754 		$debug[ 'WORDPRESS_VERSION' ]   = $GLOBALS['wp_version'];
    755 		$debug[ 'AKISMET_VERSION' ]     = AKISMET_VERSION;
    756 		$debug[ 'AKISMET__PLUGIN_DIR' ] = AKISMET__PLUGIN_DIR;
    757 		$debug[ 'SITE_URL' ]            = site_url();
    758 		$debug[ 'HOME_URL' ]            = home_url();
    759 		
    760 		$servers = get_option('akismet_available_servers');
    761 		if ( (time() - get_option('akismet_connectivity_time') < $cache_timeout) && $servers !== false ) {
    762 			$servers = self::check_server_ip_connectivity();
    763 			update_option('akismet_available_servers', $servers);
    764 			update_option('akismet_connectivity_time', time());
    765 		}
    766 
    767 		if ( wp_http_supports( array( 'ssl' ) ) ) {
    768 			$response = wp_remote_get( 'https://rest.akismet.com/1.1/test' );
    769 		}
    770 		else {
    771 			$response = wp_remote_get( 'http://rest.akismet.com/1.1/test' );
    772 		}
    773 
    774 		$debug[ 'gethostbynamel' ]  = function_exists('gethostbynamel') ? 'exists' : 'not here';
    775 		$debug[ 'Servers' ]         = $servers;
    776 		$debug[ 'Test Connection' ] = $response;
    777 		
    778 		Akismet::log( $debug );
    779 		
    780 		if ( $response && 'connected' == wp_remote_retrieve_body( $response ) )
    781 			return true;
    782 		
    783 		return false;
    784 	}
    785 
    786 	// Check the server connectivity and store the available servers in an option. 
    787 	public static function get_server_connectivity($cache_timeout = 86400) {
    788 		return self::check_server_connectivity( $cache_timeout );
    789 	}
    790 
    791 	/**
    792 	 * Find out whether any comments in the Pending queue have not yet been checked by Akismet.
    793 	 *
    794 	 * @return bool
    795 	 */
    796 	public static function are_any_comments_waiting_to_be_checked() {
    797 		return !! get_comments( array(
    798 			// Exclude comments that are not pending. This would happen if someone manually approved or spammed a comment
    799 			// that was waiting to be checked. The akismet_error meta entry will eventually be removed by the cron recheck job.
    800 			'status' => 'hold',
    801 			
    802 			// This is the commentmeta that is saved when a comment couldn't be checked.
    803 			'meta_key' => 'akismet_error',
    804 			
    805 			// We only need to know whether at least one comment is waiting for a check.
    806 			'number' => 1,
    807 		) );
    808 	}
    809 
    810 	public static function get_page_url( $page = 'config' ) {
    811 
    812 		$args = array( 'page' => 'akismet-key-config' );
    813 
    814 		if ( $page == 'stats' )
    815 			$args = array( 'page' => 'akismet-key-config', 'view' => 'stats' );
    816 		elseif ( $page == 'delete_key' )
    817 			$args = array( 'page' => 'akismet-key-config', 'view' => 'start', 'action' => 'delete-key', '_wpnonce' => wp_create_nonce( self::NONCE ) );
    818 
    819 		$url = add_query_arg( $args, class_exists( 'Jetpack' ) ? admin_url( 'admin.php' ) : admin_url( 'options-general.php' ) );
    820 
    821 		return $url;
    822 	}
    823 	
    824 	public static function get_akismet_user( $api_key ) {
    825 		$akismet_user = false;
    826 
    827 		$subscription_verification = Akismet::http_post( Akismet::build_query( array( 'key' => $api_key, 'blog' => get_option( 'home' ) ) ), 'get-subscription' );
    828 
    829 		if ( ! empty( $subscription_verification[1] ) ) {
    830 			if ( 'invalid' !== $subscription_verification[1] ) {
    831 				$akismet_user = json_decode( $subscription_verification[1] );
    832 			}
    833 		}
    834 
    835 		return $akismet_user;
    836 	}
    837 	
    838 	public static function get_stats( $api_key ) {
    839 		$stat_totals = array();
    840 
    841 		foreach( array( '6-months', 'all' ) as $interval ) {
    842 			$response = Akismet::http_post( Akismet::build_query( array( 'blog' => get_option( 'home' ), 'key' => $api_key, 'from' => $interval ) ), 'get-stats' );
    843 
    844 			if ( ! empty( $response[1] ) ) {
    845 				$stat_totals[$interval] = json_decode( $response[1] );
    846 			}
    847 		}
    848 
    849 		return $stat_totals;
    850 	}
    851 	
    852 	public static function verify_wpcom_key( $api_key, $user_id, $extra = array() ) {
    853 		$akismet_account = Akismet::http_post( Akismet::build_query( array_merge( array(
    854 			'user_id'          => $user_id,
    855 			'api_key'          => $api_key,
    856 			'get_account_type' => 'true'
    857 		), $extra ) ), 'verify-wpcom-key' );
    858 
    859 		if ( ! empty( $akismet_account[1] ) )
    860 			$akismet_account = json_decode( $akismet_account[1] );
    861 
    862 		Akismet::log( compact( 'akismet_account' ) );
    863 		
    864 		return $akismet_account;
    865 	}
    866 	
    867 	public static function connect_jetpack_user() {
    868 	
    869 		if ( $jetpack_user = self::get_jetpack_user() ) {
    870 			if ( isset( $jetpack_user['user_id'] ) && isset(  $jetpack_user['api_key'] ) ) {
    871 				$akismet_user = self::verify_wpcom_key( $jetpack_user['api_key'], $jetpack_user['user_id'], array( 'action' => 'connect_jetpack_user' ) );
    872 							
    873 				if ( is_object( $akismet_user ) ) {
    874 					self::save_key( $akismet_user->api_key );
    875 					return in_array( $akismet_user->status, array( 'active', 'active-dunning', 'no-sub' ) );
    876 				}
    877 			}
    878 		}
    879 		
    880 		return false;
    881 	}
    882 
    883 	public static function display_alert() {
    884 		Akismet::view( 'notice', array(
    885 			'type' => 'alert',
    886 			'code' => (int) get_option( 'akismet_alert_code' ),
    887 			'msg'  => get_option( 'akismet_alert_msg' )
    888 		) );
    889 	}
    890 
    891 	public static function get_usage_limit_alert_data() {
    892 		return array(
    893 			'type'         => 'usage-limit',
    894 			'code'         => (int) get_option( 'akismet_alert_code' ),
    895 			'msg'          => get_option( 'akismet_alert_msg' ),
    896 			'api_calls'    => get_option( 'akismet_alert_api_calls' ),
    897 			'usage_limit'  => get_option( 'akismet_alert_usage_limit' ),
    898 			'upgrade_plan' => get_option( 'akismet_alert_upgrade_plan' ),
    899 			'upgrade_url'  => get_option( 'akismet_alert_upgrade_url' ),
    900 			'upgrade_type' => get_option( 'akismet_alert_upgrade_type' ),
    901 		);
    902 	}
    903 
    904 	public static function display_usage_limit_alert() {
    905 		Akismet::view( 'notice', self::get_usage_limit_alert_data() );
    906 	}
    907 
    908 	public static function display_spam_check_warning() {
    909 		Akismet::fix_scheduled_recheck();
    910 
    911 		if ( wp_next_scheduled('akismet_schedule_cron_recheck') > time() && self::are_any_comments_waiting_to_be_checked() ) {
    912 			$link_text = apply_filters( 'akismet_spam_check_warning_link_text', sprintf( __( 'Please check your <a href="%s">Akismet configuration</a> and contact your web host if problems persist.', 'akismet'), esc_url( self::get_page_url() ) ) );
    913 			Akismet::view( 'notice', array( 'type' => 'spam-check', 'link_text' => $link_text ) );
    914 		}
    915 	}
    916 
    917 	public static function display_api_key_warning() {
    918 		Akismet::view( 'notice', array( 'type' => 'plugin' ) );
    919 	}
    920 
    921 	public static function display_page() {
    922 		if ( !Akismet::get_api_key() || ( isset( $_GET['view'] ) && $_GET['view'] == 'start' ) )
    923 			self::display_start_page();
    924 		elseif ( isset( $_GET['view'] ) && $_GET['view'] == 'stats' )
    925 			self::display_stats_page();
    926 		else
    927 			self::display_configuration_page();
    928 	}
    929 
    930 	public static function display_start_page() {
    931 		if ( isset( $_GET['action'] ) ) {
    932 			if ( $_GET['action'] == 'delete-key' ) {
    933 				if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], self::NONCE ) )
    934 					delete_option( 'wordpress_api_key' );
    935 			}
    936 		}
    937 
    938 		if ( $api_key = Akismet::get_api_key() && ( empty( self::$notices['status'] ) || 'existing-key-invalid' != self::$notices['status'] ) ) {
    939 			self::display_configuration_page();
    940 			return;
    941 		}
    942 		
    943 		//the user can choose to auto connect their API key by clicking a button on the akismet done page
    944 		//if jetpack, get verified api key by using connected wpcom user id
    945 		//if no jetpack, get verified api key by using an akismet token	
    946 		
    947 		$akismet_user = false;
    948 		
    949 		if ( isset( $_GET['token'] ) && preg_match('/^(\d+)-[0-9a-f]{20}$/', $_GET['token'] ) )
    950 			$akismet_user = self::verify_wpcom_key( '', '', array( 'token' => $_GET['token'] ) );
    951 		elseif ( $jetpack_user = self::get_jetpack_user() )
    952 			$akismet_user = self::verify_wpcom_key( $jetpack_user['api_key'], $jetpack_user['user_id'] );
    953 			
    954 		if ( isset( $_GET['action'] ) ) {
    955 			if ( $_GET['action'] == 'save-key' ) {
    956 				if ( is_object( $akismet_user ) ) {
    957 					self::save_key( $akismet_user->api_key );
    958 					self::display_configuration_page();
    959 					return;
    960 				}
    961 			}
    962 		}
    963 
    964 		Akismet::view( 'start', compact( 'akismet_user' ) );
    965 
    966 		/*
    967 		// To see all variants when testing.
    968 		$akismet_user->status = 'no-sub';
    969 		Akismet::view( 'start', compact( 'akismet_user' ) );
    970 		$akismet_user->status = 'cancelled';
    971 		Akismet::view( 'start', compact( 'akismet_user' ) );
    972 		$akismet_user->status = 'suspended';
    973 		Akismet::view( 'start', compact( 'akismet_user' ) );
    974 		$akismet_user->status = 'other';
    975 		Akismet::view( 'start', compact( 'akismet_user' ) );
    976 		$akismet_user = false;
    977 		*/
    978 	}
    979 
    980 	public static function display_stats_page() {
    981 		Akismet::view( 'stats' );
    982 	}
    983 
    984 	public static function display_configuration_page() {
    985 		$api_key      = Akismet::get_api_key();
    986 		$akismet_user = self::get_akismet_user( $api_key );
    987 		
    988 		if ( ! $akismet_user ) {
    989 			// This could happen if the user's key became invalid after it was previously valid and successfully set up.
    990 			self::$notices['status'] = 'existing-key-invalid';
    991 			self::display_start_page();
    992 			return;
    993 		}
    994 
    995 		$stat_totals  = self::get_stats( $api_key );
    996 
    997 		// If unset, create the new strictness option using the old discard option to determine its default.
    998 		// If the old option wasn't set, default to discarding the blatant spam.
    999 		if ( get_option( 'akismet_strictness' ) === false ) {
   1000 			add_option( 'akismet_strictness', ( get_option( 'akismet_discard_month' ) === 'false' ? '0' : '1' ) );
   1001 		}
   1002 		
   1003 		// Sync the local "Total spam blocked" count with the authoritative count from the server.
   1004 		if ( isset( $stat_totals['all'], $stat_totals['all']->spam ) ) {
   1005 			update_option( 'akismet_spam_count', $stat_totals['all']->spam );
   1006 		}
   1007 
   1008 		$notices = array();
   1009 
   1010 		if ( empty( self::$notices ) ) {
   1011 			if ( ! empty( $stat_totals['all'] ) && isset( $stat_totals['all']->time_saved ) && $akismet_user->status == 'active' && $akismet_user->account_type == 'free-api-key' ) {
   1012 
   1013 				$time_saved = false;
   1014 
   1015 				if ( $stat_totals['all']->time_saved > 1800 ) {
   1016 					$total_in_minutes = round( $stat_totals['all']->time_saved / 60 );
   1017 					$total_in_hours   = round( $total_in_minutes / 60 );
   1018 					$total_in_days    = round( $total_in_hours / 8 );
   1019 					$cleaning_up      = __( 'Cleaning up spam takes time.' , 'akismet');
   1020 
   1021 					if ( $total_in_days > 1 )
   1022 						$time_saved = $cleaning_up . ' ' . sprintf( _n( 'Akismet has saved you %s day!', 'Akismet has saved you %s days!', $total_in_days, 'akismet' ), number_format_i18n( $total_in_days ) );
   1023 					elseif ( $total_in_hours > 1 )
   1024 						$time_saved = $cleaning_up . ' ' . sprintf( _n( 'Akismet has saved you %d hour!', 'Akismet has saved you %d hours!', $total_in_hours, 'akismet' ), $total_in_hours );
   1025 					elseif ( $total_in_minutes >= 30 )
   1026 						$time_saved = $cleaning_up . ' ' . sprintf( _n( 'Akismet has saved you %d minute!', 'Akismet has saved you %d minutes!', $total_in_minutes, 'akismet' ), $total_in_minutes );
   1027 				}
   1028 				
   1029 				$notices[] =  array( 'type' => 'active-notice', 'time_saved' => $time_saved );
   1030 			}
   1031 			
   1032 			if ( !empty( $akismet_user->limit_reached ) && in_array( $akismet_user->limit_reached, array( 'yellow', 'red' ) ) ) {
   1033 				$notices[] = array( 'type' => 'limit-reached', 'level' => $akismet_user->limit_reached );
   1034 			}
   1035 		}
   1036 		
   1037 		if ( !isset( self::$notices['status'] ) && in_array( $akismet_user->status, array( 'cancelled', 'suspended', 'missing', 'no-sub' ) ) ) {
   1038 			$notices[] = array( 'type' => $akismet_user->status );
   1039 		}
   1040 
   1041 		$alert_code = get_option( 'akismet_alert_code' );
   1042 		if ( isset( Akismet::$limit_notices[ $alert_code ] ) ) {
   1043 			$notices[] = self::get_usage_limit_alert_data();
   1044 		}
   1045 
   1046 		/*
   1047 		// To see all variants when testing.
   1048 		$notices[] = array( 'type' => 'active-notice', 'time_saved' => 'Cleaning up spam takes time. Akismet has saved you 1 minute!' );
   1049 		$notices[] = array( 'type' => 'plugin' );
   1050 		$notices[] = array( 'type' => 'spam-check', 'link_text' => 'Link text.' );
   1051 		$notices[] = array( 'type' => 'notice', 'notice_header' => 'This is the notice header.', 'notice_text' => 'This is the notice text.' );
   1052 		$notices[] = array( 'type' => 'missing-functions' );
   1053 		$notices[] = array( 'type' => 'servers-be-down' );
   1054 		$notices[] = array( 'type' => 'active-dunning' );
   1055 		$notices[] = array( 'type' => 'cancelled' );
   1056 		$notices[] = array( 'type' => 'suspended' );
   1057 		$notices[] = array( 'type' => 'missing' );
   1058 		$notices[] = array( 'type' => 'no-sub' );
   1059 		$notices[] = array( 'type' => 'new-key-valid' );
   1060 		$notices[] = array( 'type' => 'new-key-invalid' );
   1061 		$notices[] = array( 'type' => 'existing-key-invalid' );
   1062 		$notices[] = array( 'type' => 'new-key-failed' );
   1063 		$notices[] = array( 'type' => 'limit-reached', 'level' => 'yellow' );
   1064 		$notices[] = array( 'type' => 'limit-reached', 'level' => 'red' );
   1065 		$notices[] = array( 'type' => 'usage-limit', 'api_calls' => '15000', 'usage_limit' => '10000', 'upgrade_plan' => 'Enterprise', 'upgrade_url' => 'https://akismet.com/account/' );
   1066 		*/
   1067 		
   1068 		Akismet::log( compact( 'stat_totals', 'akismet_user' ) );
   1069 		Akismet::view( 'config', compact( 'api_key', 'akismet_user', 'stat_totals', 'notices' ) );
   1070 	}
   1071 
   1072 	public static function display_notice() {
   1073 		global $hook_suffix;
   1074 
   1075 		if ( in_array( $hook_suffix, array( 'jetpack_page_akismet-key-config', 'settings_page_akismet-key-config' ) ) ) {
   1076 			// This page manages the notices and puts them inline where they make sense.
   1077 			return;
   1078 		}
   1079 
   1080 		if ( in_array( $hook_suffix, array( 'edit-comments.php' ) ) && (int) get_option( 'akismet_alert_code' ) > 0 ) {
   1081 			Akismet::verify_key( Akismet::get_api_key() ); //verify that the key is still in alert state
   1082 
   1083 			$alert_code = get_option( 'akismet_alert_code' );
   1084 			if ( isset( Akismet::$limit_notices[ $alert_code ] ) ) {
   1085 				self::display_usage_limit_alert();
   1086 			} elseif ( $alert_code > 0 ) {
   1087 				self::display_alert();
   1088 			}
   1089 		}
   1090 		elseif ( ( 'plugins.php' === $hook_suffix || 'edit-comments.php' === $hook_suffix ) && ! Akismet::get_api_key() ) {
   1091 			// Show the "Set Up Akismet" banner on the comments and plugin pages if no API key has been set.
   1092 			self::display_api_key_warning();
   1093 		}
   1094 		elseif ( $hook_suffix == 'edit-comments.php' && wp_next_scheduled( 'akismet_schedule_cron_recheck' ) ) {
   1095 			self::display_spam_check_warning();
   1096 		}
   1097 		
   1098 		if ( isset( $_GET['akismet_recheck_complete'] ) ) {
   1099 			$recheck_count = (int) $_GET['recheck_count'];
   1100 			$spam_count = (int) $_GET['spam_count'];
   1101 			
   1102 			if ( $recheck_count === 0 ) {
   1103 				$message = __( 'There were no comments to check. Akismet will only check comments awaiting moderation.', 'akismet' );
   1104 			}
   1105 			else {
   1106 				$message = sprintf( _n( 'Akismet checked %s comment.', 'Akismet checked %s comments.', $recheck_count, 'akismet' ), number_format( $recheck_count ) );
   1107 				$message .= ' ';
   1108 			
   1109 				if ( $spam_count === 0 ) {
   1110 					$message .= __( 'No comments were caught as spam.', 'akismet' );
   1111 				}
   1112 				else {
   1113 					$message .= sprintf( _n( '%s comment was caught as spam.', '%s comments were caught as spam.', $spam_count, 'akismet' ), number_format( $spam_count ) );
   1114 				}
   1115 			}
   1116 			
   1117 			echo '<div class="notice notice-success"><p>' . esc_html( $message ) . '</p></div>';
   1118 		}
   1119 		else if ( isset( $_GET['akismet_recheck_error'] ) ) {
   1120 			echo '<div class="notice notice-error"><p>' . esc_html( __( 'Akismet could not recheck your comments for spam.', 'akismet' ) ) . '</p></div>';
   1121 		}
   1122 	}
   1123 
   1124 	public static function display_status() {
   1125 		if ( ! self::get_server_connectivity() ) {
   1126 			Akismet::view( 'notice', array( 'type' => 'servers-be-down' ) );
   1127 		}
   1128 		else if ( ! empty( self::$notices ) ) {
   1129 			foreach ( self::$notices as $index => $type ) {
   1130 				if ( is_object( $type ) ) {
   1131 					$notice_header = $notice_text = '';
   1132 					
   1133 					if ( property_exists( $type, 'notice_header' ) ) {
   1134 						$notice_header = wp_kses( $type->notice_header, self::$allowed );
   1135 					}
   1136 				
   1137 					if ( property_exists( $type, 'notice_text' ) ) {
   1138 						$notice_text = wp_kses( $type->notice_text, self::$allowed );
   1139 					}
   1140 					
   1141 					if ( property_exists( $type, 'status' ) ) {
   1142 						$type = wp_kses( $type->status, self::$allowed );
   1143 						Akismet::view( 'notice', compact( 'type', 'notice_header', 'notice_text' ) );
   1144 						
   1145 						unset( self::$notices[ $index ] );
   1146 					}
   1147 				}
   1148 				else {
   1149 					Akismet::view( 'notice', compact( 'type' ) );
   1150 					
   1151 					unset( self::$notices[ $index ] );
   1152 				}
   1153 			}
   1154 		}
   1155 	}
   1156 
   1157 	private static function get_jetpack_user() {
   1158 		if ( !class_exists('Jetpack') )
   1159 			return false;
   1160 
   1161 		if ( defined( 'JETPACK__VERSION' ) && version_compare( JETPACK__VERSION, '7.7', '<' ) ) {
   1162 			// For version of Jetpack prior to 7.7.
   1163 			Jetpack::load_xml_rpc_client();
   1164 		}
   1165 
   1166 		$xml = new Jetpack_IXR_ClientMulticall( array( 'user_id' => get_current_user_id() ) );
   1167 
   1168 		$xml->addCall( 'wpcom.getUserID' );
   1169 		$xml->addCall( 'akismet.getAPIKey' );
   1170 		$xml->query();
   1171 
   1172 		Akismet::log( compact( 'xml' ) );
   1173 
   1174 		if ( !$xml->isError() ) {
   1175 			$responses = $xml->getResponse();
   1176 			if ( count( $responses ) > 1 ) {
   1177 				// Due to a quirk in how Jetpack does multi-calls, the response order
   1178 				// can't be trusted to match the call order. It's a good thing our
   1179 				// return values can be mostly differentiated from each other.
   1180 				$first_response_value = array_shift( $responses[0] );
   1181 				$second_response_value = array_shift( $responses[1] );
   1182 				
   1183 				// If WPCOM ever reaches 100 billion users, this will fail. :-)
   1184 				if ( preg_match( '/^[a-f0-9]{12}$/i', $first_response_value ) ) {
   1185 					$api_key = $first_response_value;
   1186 					$user_id = (int) $second_response_value;
   1187 				}
   1188 				else {
   1189 					$api_key = $second_response_value;
   1190 					$user_id = (int) $first_response_value;
   1191 				}
   1192 				
   1193 				return compact( 'api_key', 'user_id' );
   1194 			}
   1195 		}
   1196 		return false;
   1197 	}
   1198 	
   1199 	/**
   1200 	 * Some commentmeta isn't useful in an export file. Suppress it (when supported).
   1201 	 *
   1202 	 * @param bool $exclude
   1203 	 * @param string $key The meta key
   1204 	 * @param object $meta The meta object
   1205 	 * @return bool Whether to exclude this meta entry from the export.
   1206 	 */
   1207 	public static function exclude_commentmeta_from_export( $exclude, $key, $meta ) {
   1208 		if ( in_array( $key, array( 'akismet_as_submitted', 'akismet_rechecking', 'akismet_delayed_moderation_email' ) ) ) {
   1209 			return true;
   1210 		}
   1211 		
   1212 		return $exclude;
   1213 	}
   1214 	
   1215 	/**
   1216 	 * When Akismet is active, remove the "Activate Akismet" step from the plugin description.
   1217 	 */
   1218 	public static function modify_plugin_description( $all_plugins ) {
   1219 		if ( isset( $all_plugins['akismet/akismet.php'] ) ) {
   1220 			if ( Akismet::get_api_key() ) {
   1221 				$all_plugins['akismet/akismet.php']['Description'] = __( 'Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. Your site is fully configured and being protected, even while you sleep.', 'akismet' );
   1222 			}
   1223 			else {
   1224 				$all_plugins['akismet/akismet.php']['Description'] = __( 'Used by millions, Akismet is quite possibly the best way in the world to <strong>protect your blog from spam</strong>. It keeps your site protected even while you sleep. To get started, just go to <a href="admin.php?page=akismet-key-config">your Akismet Settings page</a> to set up your API key.', 'akismet' );
   1225 			}
   1226 		}
   1227 		
   1228 		return $all_plugins;
   1229 	}
   1230 
   1231 	private static function set_form_privacy_notice_option( $state ) {
   1232 		if ( in_array( $state, array( 'display', 'hide' ) ) ) {
   1233 			update_option( 'akismet_comment_form_privacy_notice', $state );
   1234 		}
   1235 	}
   1236 	
   1237 	public static function register_personal_data_eraser( $erasers ) {
   1238 		$erasers['akismet'] = array(
   1239 			'eraser_friendly_name' => __( 'Akismet', 'akismet' ),
   1240 			'callback' => array( 'Akismet_Admin', 'erase_personal_data' ),
   1241 		);
   1242 
   1243 		return $erasers;
   1244 	}
   1245 	
   1246 	/**
   1247 	 * When a user requests that their personal data be removed, Akismet has a duty to discard
   1248 	 * any personal data we store outside of the comment itself. Right now, that is limited
   1249 	 * to the copy of the comment we store in the akismet_as_submitted commentmeta.
   1250 	 *
   1251 	 * FWIW, this information would be automatically deleted after 15 days.
   1252 	 * 
   1253 	 * @param $email_address string The email address of the user who has requested erasure.
   1254 	 * @param $page int This function can (and will) be called multiple times to prevent timeouts,
   1255 	 *                  so this argument is used for pagination.
   1256 	 * @return array
   1257 	 * @see https://developer.wordpress.org/plugins/privacy/adding-the-personal-data-eraser-to-your-plugin/
   1258 	 */
   1259 	public static function erase_personal_data( $email_address, $page = 1 ) {
   1260 		$items_removed = false;
   1261 		
   1262 		$number = 50;
   1263 		$page = (int) $page;
   1264 
   1265 		$comments = get_comments(
   1266 			array(
   1267 				'author_email' => $email_address,
   1268 				'number'       => $number,
   1269 				'paged'        => $page,
   1270 				'order_by'     => 'comment_ID',
   1271 				'order'        => 'ASC',
   1272 			)
   1273 		);
   1274 
   1275 		foreach ( (array) $comments as $comment ) {
   1276 			$comment_as_submitted = get_comment_meta( $comment->comment_ID, 'akismet_as_submitted', true );
   1277 			
   1278 			if ( $comment_as_submitted ) {
   1279 				delete_comment_meta( $comment->comment_ID, 'akismet_as_submitted' );
   1280 				$items_removed = true;
   1281 			}
   1282 		}
   1283 
   1284 		// Tell core if we have more comments to work on still
   1285 		$done = count( $comments ) < $number;
   1286 		
   1287 		return array(
   1288 			'items_removed' => $items_removed,
   1289 			'items_retained' => false, // always false in this example
   1290 			'messages' => array(), // no messages in this example
   1291 			'done' => $done,
   1292 		);
   1293 	}
   1294 }