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