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