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