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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref