[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-content/plugins/akismet/ -> class.akismet-admin.php (source)

   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&amp;utm_campaign=plugin_static_link&amp;utm_medium=in_plugin&amp;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&amp;utm_campaign=plugin_static_link&amp;utm_medium=in_plugin&amp;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&amp;utm_campaign=plugin_static_link&amp;utm_medium=in_plugin&amp;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&#8217; 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&amp;utm_campaign=plugin_static_link&amp;utm_medium=in_plugin&amp;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&amp;utm_campaign=plugin_static_link&amp;utm_medium=in_plugin&amp;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&amp;utm_campaign=plugin_static_link&amp;utm_medium=in_plugin&amp;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&#8217;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&#8217;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&#8217;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&amp;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&amp;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  }


Generated : Wed Jun 24 08:20:11 2026 Cross-referenced by PHPXref