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


Generated : Sun Aug 23 08:20:23 2026 Cross-referenced by PHPXref