diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0da8b..f307e08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ #### Version 1.0.0 - 2024/11/12 * initial release + +#### Version 1.1.0 - 2024/11/18 +* adding admin UI button for running bulk update diff --git a/README.md b/README.md index 7f0bb91..e00ddfc 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,9 @@ Make sure no real IP addresses are stored in WP comments. ### Features -* a CLI command to bulk convert existing comment IP addresses to the masked +* a CLI command and admin UI to bulk convert existing comment IP addresses to the masked * auto-filtering of new comments to use masked IP. ### To Do -* add admin UI piece to bulk convert existing comments * add whitelisting of IPs diff --git a/includes/activate.php b/includes/activate.php index d221e53..40bad63 100644 --- a/includes/activate.php +++ b/includes/activate.php @@ -23,8 +23,5 @@ function activate() { // Include our action so that we may add to this later. do_action( Core\HOOK_PREFIX . 'activate_process' ); - - // And flush our rewrite rules. - flush_rewrite_rules(); } register_activation_hook( Core\FILE, __NAMESPACE__ . '\activate' ); diff --git a/includes/bulk-process.php b/includes/bulk-process.php new file mode 100644 index 0000000..ee65184 --- /dev/null +++ b/includes/bulk-process.php @@ -0,0 +1,200 @@ + true ] ); + } + + // Now get the IDs of the comments we have. + $fetch_comments = Database\get_ids_for_update(); + + // If none exist, say so. + if ( empty( $fetch_comments ) ) { + + // Now set my redirect link. + $redirect_link = Helpers\fetch_settings_url( ['ip-scrub-bulk-error' => 'no-comments', 'ip-scrub-bulk-success' => 'no'] ); + + // Do the redirect. + wp_safe_redirect( $redirect_link ); + exit; + } + + // Handle the WP_Error return on it's own. + if ( is_wp_error( $fetch_comments ) ) { + + // Now set my redirect link. + $redirect_link = Helpers\fetch_settings_url( ['ip-scrub-bulk-error' => 'query-error', 'ip-scrub-bulk-success' => 'no'] ); + + // Do the redirect. + wp_safe_redirect( $redirect_link ); + exit; + } + + // Attempt the update. + $attempt_update = Database\replace_batch_comment_ips( $fetch_comments ); + + // Bail if the update did. + if ( empty( $attempt_update ) || is_wp_error( $attempt_update ) ) { + + // Now set my redirect link. + $redirect_link = Helpers\fetch_settings_url( ['ip-scrub-bulk-error' => 'update-error', 'ip-scrub-bulk-success' => 'no'] ); + + // Do the redirect. + wp_safe_redirect( $redirect_link ); + exit; + } + + // Now set my redirect link. + $redirect_link = Helpers\fetch_settings_url( ['ip-scrub-bulk-count' => count( $fetch_comments ), 'ip-scrub-bulk-success' => 'yes'] ); + + // Do the redirect. + wp_safe_redirect( $redirect_link ); + exit; +} + +/** + * Check for the result of bulk action. + * + * @return void + */ +function admin_bulk_action_notice() { + + // Confirm we requested this action. + $confirm_result = filter_input( INPUT_GET, 'ip-scrub-bulk-success', FILTER_SANITIZE_SPECIAL_CHARS ); // phpcs:ignore -- no need for a nonce check. + + // Make sure it is what we want. + if ( empty( $confirm_result ) ) { + return; + } + + // Handle the success first. + if ( 'yes' === $confirm_result ) { + + // Set the counts. + $set_counts = filter_input( INPUT_GET, 'ip-scrub-bulk-count', FILTER_SANITIZE_NUMBER_INT ); + + // Set our notice text. + $set_notice = sprintf( _n( 'Success! %d comment was updated.', 'Success! %d comments were updated.', absint( $set_counts ), 'scrub-comment-author-ip' ), absint( $set_counts ) ); + + // Set the wrapper around it. + echo '
'; + + // Display the actual message. + echo '

' . wp_kses_post( $set_notice ) . '

'; + + // Close the wrapper. + echo '
'; + + // And be done. + return; + } + + // Handle the errors now. + $set_error = filter_input( INPUT_GET, 'ip-scrub-bulk-error', FILTER_SANITIZE_SPECIAL_CHARS ); + + // If we have no comments, show a warning. + if ( 'no-comments' === $set_error ) { + + // Set the notice text. + $set_notice = __( 'There are no comments requiring update at this time.', 'scrub-comment-author-ip' ); + + // Set the wrapper around it. + echo '
'; + + // Display the actual message. + echo '

' . wp_kses_post( $set_notice ) . '

'; + + // Close the wrapper. + echo '
'; + + // And finish. + return; + } + + // Handle the rest of the possible error messages. + switch ( $set_error ) { + + case 'query-error' : + $set_notice = __( 'There was an error attempting to retrieve the comments. Please check your error logs.', 'scrub-comment-author-ip' ); + break; + + case 'update-error' : + $set_notice = __( 'There was an error attempting to update the comments. Please check your error logs.', 'scrub-comment-author-ip' ); + break; + + default : + $set_notice = __( 'There was an unknown error. Please check your error logs.', 'scrub-comment-author-ip' ); + break; + } + + // Set the wrapper around it. + echo '
'; + + // Display the actual message. + echo '

' . wp_kses_post( $set_notice ) . '

'; + + // Close the wrapper. + echo '
'; + + // Nothing left to display. +} + +/** + * Add our custom strings to the vars. + * + * @param array $args The existing array of args. + * + * @return array $args The modified array of args. + */ +function admin_removable_args( $args ) { + + // Set an array of the args we wanna exclude. + $remove = [ + 'ip-scrub-bulk-error', + 'ip-scrub-bulk-count', + 'ip-scrub-bulk-success', + ]; + + // Include my new args and return. + return wp_parse_args( $remove, $args ); +} diff --git a/includes/database.php b/includes/database.php index cdcc515..bb5e210 100644 --- a/includes/database.php +++ b/includes/database.php @@ -15,6 +15,38 @@ // And pull in any other namespaces. use WP_Error; +/** + * Just get a simple count. + * + * @return array + */ +function get_count_for_update() { + + // Fetch the masked IP. + $masked_ip = Helpers\fetch_masked_ip(); + + // Call the global class. + global $wpdb; + + // Set up our query. + $query_args = $wpdb->prepare(" + SELECT COUNT(*) + FROM $wpdb->comments + WHERE comment_author_IP NOT LIKE '%s' + ", esc_attr( $masked_ip ) ); + + // Process the query. + $query_run = $wpdb->get_var( $query_args ); // phpcs:ignore -- we are skipping the overhead of get_comments. + + // Throw the error if we have one. + if ( is_wp_error( $query_run ) ) { + return new WP_Error( $query_run->get_error_code(), $query_run->get_error_message() ); + } + + // Return the count, whatever it may be. + return absint( $query_run ); +} + /** * Get all my comment IDs. * @@ -28,18 +60,15 @@ function get_ids_for_update() { // Call the global class. global $wpdb; - // Set my table name. - $table_name = $wpdb->prefix . 'comments'; - // Set up our query. $query_args = $wpdb->prepare(" SELECT comment_ID - FROM $table_name + FROM $wpdb->comments WHERE comment_author_IP NOT LIKE '%s' ", esc_attr( $masked_ip ) ); // Process the query. - $query_run = $wpdb->get_col( $query_args ); + $query_run = $wpdb->get_col( $query_args ); // phpcs:ignore -- we are skipping the overhead of get_comments. // Throw the error if we have one. if ( is_wp_error( $query_run ) ) { @@ -84,12 +113,9 @@ function replace_single_comment_ip( $comment_id = 0, $masked_ip = '' ) { // Call the global class. global $wpdb; - // Set my table name. - $table_name = $wpdb->prefix . 'comments'; - // Run the actual DB update. - $update_row = $wpdb->update( - $table_name, + $update_row = $wpdb->update( // phpcs:ignore -- we dont want to trigger anything else here. + $wpdb->comments, [ 'comment_author_IP' => $set_new_ip ], [ 'comment_ID' => absint( $comment_id ) ], [ '%s' ], @@ -112,7 +138,7 @@ function replace_single_comment_ip( $comment_id = 0, $masked_ip = '' ) { * * @return mixed */ -function replace_batch_comment_ips( $comment_ids = array() ) { +function replace_batch_comment_ips( $comment_ids = [] ) { // Bail if no comment IDs were provided. if ( empty( $comment_ids ) ) { @@ -133,15 +159,12 @@ function replace_batch_comment_ips( $comment_ids = array() ) { // Call the global class. global $wpdb; - // Set my table name. - $table_name = $wpdb->prefix . 'comments'; - // Now loop the IDs and run the update. foreach ( $comment_ids as $comment_id ) { // Run the actual DB update. - $update_row = $wpdb->update( - $table_name, + $update_row = $wpdb->update( // phpcs:ignore -- we dont want to trigger anything else here. + $wpdb->comments, [ 'comment_author_IP' => $masked_ip ], [ 'comment_ID' => absint( $comment_id ) ], [ '%s' ], diff --git a/includes/deactivate.php b/includes/deactivate.php index 48f61d0..4a72d2b 100644 --- a/includes/deactivate.php +++ b/includes/deactivate.php @@ -20,8 +20,5 @@ function deactivate() { // Include our action so that we may add to this later. do_action( Core\HOOK_PREFIX . 'deactivate_process' ); - - // And flush our rewrite rules. - flush_rewrite_rules(); } register_deactivation_hook( Core\FILE, __NAMESPACE__ . '\deactivate' ); diff --git a/includes/helpers.php b/includes/helpers.php index 54a9f84..d6e60c6 100644 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -36,7 +36,7 @@ function maybe_scrub_enabled( $return_type = 'string' ) { // Check for the stored "yes" to return. return ! empty( $set_option ) && 'yes' === sanitize_text_field( $set_option ) ? true : false; - // And break. + // Done. break; // Handle my yes / no string return. @@ -45,7 +45,7 @@ function maybe_scrub_enabled( $return_type = 'string' ) { // Check for the stored "yes" to return. return ! empty( $set_option ) && 'yes' === sanitize_text_field( $set_option ) ? 'yes' : 'no'; - // And break. + // Done. break; } @@ -66,3 +66,21 @@ function fetch_masked_ip() { // Confirm it's a valid IP before returning it. return ! empty( $masked_ip ) && filter_var( $masked_ip, FILTER_VALIDATE_IP ) ? $masked_ip : '127.0.0.1'; } + +/** + * Get the URL for our settings page with any custom args. + * + * @param array $args The possible array of args. + * + * @return string + */ +function fetch_settings_url( $args = [] ) { + + // If we have no args, just do the basic link. + if ( empty( $args ) ) { + return admin_url( 'options-discussion.php' ); + } + + // Now return it in args. + return add_query_arg( $args, admin_url( 'options-discussion.php' ) ); +} diff --git a/includes/settings-api.php b/includes/settings-api.php index 7c0cc37..e372556 100644 --- a/includes/settings-api.php +++ b/includes/settings-api.php @@ -16,8 +16,43 @@ /** * Start our engines. */ +add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\load_settings_field_css' ); add_action( 'admin_init', __NAMESPACE__ . '\load_comment_settings' ); +/** + * Include a small bit of CSS for our admin. + * + * @param string $hook_suffix The hook suffix on admin. + * + * @return void + */ +function load_settings_field_css( $hook_suffix ) { + + // Only load this on the comment settings page. + if ( empty( $hook_suffix ) || 'options-discussion.php' !== $hook_suffix ) { + return; + } + + // Set my CSS up. + $setup_css = ' + tr.ip-scrub-bulk-action-wrapper th, + tr.ip-scrub-bulk-action-wrapper td { + padding-top: 0; + } + + tr.ip-scrub-bulk-action-wrapper a.ip-scrub-bulk-admin-button { + margin-bottom: 5px; + } + + tr.ip-scrub-bulk-action-wrapper p.ip-scrub-bulk-button-explain { + margin-top: 0; + } + '; + + // And add the CSS. + wp_add_inline_style( 'common', $setup_css ); +} + /** * Add a checkbox to the comment settings for removing IP addresses. * @@ -25,11 +60,27 @@ */ function load_comment_settings() { + // Define the args for the setting registration. + $setup_args = [ + 'type' => 'string', + 'show_in_rest' => false, + 'default' => 'yes', + 'sanitize_callback' => __NAMESPACE__ . '\sanitize_scrub_setting', + ]; + // Add out checkbox with a sanitiation callback. - register_setting( 'discussion', Core\OPTION_KEY, __NAMESPACE__ . '\sanitize_scrub_setting' ); + register_setting( 'discussion', Core\OPTION_KEY, $setup_args ); - // Load the actual field itself. - add_settings_field( 'ip-scrub-enable', __( 'Scrub Comment IPs', 'scrub-comment-author-ip' ), __NAMESPACE__ . '\display_field', 'discussion', 'default' ); + // Load the actual checkbox field itself. + add_settings_field( 'ip-scrub-enable', __( 'Scrub Comment IPs', 'scrub-comment-author-ip' ), __NAMESPACE__ . '\display_field', 'discussion', 'default', [ 'class' => 'ip-scrub-enable-wrapper' ] ); + + // Get the amount we could fix. + $get_bulk_nums = Database\get_count_for_update(); + + // Load the button for the bulk action. + if ( ! empty( $get_bulk_nums ) ) { + add_settings_field( 'ip-scrub-bulk-action', '', __NAMESPACE__ . '\bulk_action_field', 'discussion', 'default', [ 'class' => 'ip-scrub-bulk-action-wrapper', 'counts' => $get_bulk_nums ] ); + } } /** @@ -40,16 +91,16 @@ function load_comment_settings() { function display_field() { // Set a label with our default IP. - $set_label = sprintf( __( 'Replace the comment author IP address with %s', 'scrub-comment-author-ip' ), '' . esc_attr( Helpers\fetch_masked_ip() ) . '' ); + $set_label = sprintf( __( 'Replace the comment author IP address with %s', 'scrub-comment-author-ip' ), '' . esc_html( Helpers\fetch_masked_ip() ) . '' ); // Add a legend output for screen readers. - echo '' . __( 'Scrub Comment IPs', 'scrub-comment-author-ip' ) . ''; + echo '' . esc_html__( 'Scrub Comment IPs', 'scrub-comment-author-ip' ) . ''; // We are wrapping the entire thing in a label. echo ''; } +/** + * Display a button for the bulk action. + * + * @return HTML + */ +function bulk_action_field( $args ) { + + // Set the bulk args up. + $set_bulk_args = [ + 'ip-scrub-run-bulk' => 'yes', + 'ip-scrub-nonce' => wp_create_nonce( 'ip_scrub_bulk' ), + ]; + + // Set up the link for runniing the bulk update. + $set_bulk_link = Helpers\fetch_settings_url( $set_bulk_args ); + + // And show the button link. + echo '' . esc_html__( 'Bulk Cleanup', 'scrub-comment-author-ip' ) . ''; + + // If we have a lot of comments, show the CLI message. + // @todo decide on a large number. + if ( ! empty( $args['counts'] ) && 200 < absint( $args['counts'] ) ) { + echo '

' . esc_html__( 'For sites with a large amount of comments, it is suggested to use the WP-CLI command included with this plugin.', 'scrub-comment-author-ip' ) . '

'; + } +} + /** * Make sure the setting is valid. * diff --git a/includes/uninstall.php b/includes/uninstall.php index a3fbf30..e06f6d5 100644 --- a/includes/uninstall.php +++ b/includes/uninstall.php @@ -23,8 +23,5 @@ function uninstall() { // Include our action so that we may add to this later. do_action( Core\HOOK_PREFIX . 'uninstall_process' ); - - // And flush our rewrite rules. - flush_rewrite_rules(); } register_uninstall_hook( Core\FILE, __NAMESPACE__ . '\uninstall' ); diff --git a/scrub-comment-author-ip.php b/scrub-comment-author-ip.php index 021a2ca..1ededdd 100644 --- a/scrub-comment-author-ip.php +++ b/scrub-comment-author-ip.php @@ -7,7 +7,7 @@ * Author URI: https://andrewnorcross.com * Text Domain: scrub-comment-author-ip * Domain Path: /languages - * Version: 1.0.0 + * Version: 1.1.0 * * @package ScrubCommentAuthorIP */ @@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) exit; // Define our version. -define( __NAMESPACE__ . '\VERS', '1.0.0' ); +define( __NAMESPACE__ . '\VERS', '1.1.0' ); // Plugin root file. define( __NAMESPACE__ . '\FILE', __FILE__ ); @@ -43,6 +43,7 @@ require_once __DIR__ . '/includes/helpers.php'; require_once __DIR__ . '/includes/settings-api.php'; require_once __DIR__ . '/includes/database.php'; +require_once __DIR__ . '/includes/bulk-process.php'; require_once __DIR__ . '/includes/comment-process.php'; // Check that we have the CLI constant available.