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