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


Generated : Wed May 6 08:20:15 2026 Cross-referenced by PHPXref