Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions admin/views/visitors.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@
elseif ( isset( $_GET['lookup_error'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended
// translators: %s is the error message returned by the lookup.
echo '<div class="notice notice-error is-dismissible"><p>' . esc_html( sprintf( __( 'Lookup error: %s', 'ipquery' ), urldecode( sanitize_text_field( wp_unslash( $_GET['lookup_error'] ) ) ) ) ) . '</p></div>'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
elseif ( isset( $_GET['country_deleted'] ) && 'false' !== $_GET['country_deleted'] ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$ipquery_deleted_count = (int) $_GET['country_deleted']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$ipquery_deleted_country = esc_html( strtoupper( sanitize_text_field( wp_unslash( $_GET['country_code'] ?? '' ) ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
Comment thread
guibranco marked this conversation as resolved.
printf(
'<div class="notice notice-success is-dismissible"><p>' . esc_html(
sprintf(
// translators: %1$d is the number of records deleted, %2$s is the country code.
_n(
'%1$d record deleted for country: %2$s.',
'%1$d records deleted for country: %2$s.',
$ipquery_deleted_count,
'ipquery'
),
$ipquery_deleted_count,
$ipquery_deleted_country
)
) . '</p></div>'
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
elseif ( isset( $_GET['country_delete_error'] ) || ( isset( $_GET['country_deleted'] ) && 'false' === $_GET['country_deleted'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended
echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'Failed to delete records. Please try again.', 'ipquery' ) . '</p></div>';
endif;
?>

Expand Down Expand Up @@ -250,4 +270,44 @@ function ipquery_sortable_col( string $col, string $label, string $current_order
</form>
</div>

<!-- Delete by Country -->
<div class="ipquery-panel ipquery-panel--spaced">
<h3><?php esc_html_e( 'Delete by Country', 'ipquery' ); ?></h3>
<p><?php esc_html_e( 'Permanently delete all visitor records from a specific country (GDPR right-to-erasure).', 'ipquery' ); ?></p>
<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<?php wp_nonce_field( 'ipquery_delete_by_country' ); ?>
<input type="hidden" name="action" value="ipquery_delete_by_country">
<label>
<?php esc_html_e( 'Country:', 'ipquery' ); ?>
<select name="country_code" required>
<option value=""><?php esc_html_e( 'β€” Select a country β€”', 'ipquery' ); ?></option>
<?php
// Fetch distinct countries that actually have records.
$ipquery_countries = IpQuery_DB::get_distinct_countries();
foreach ( $ipquery_countries as $ipquery_country ) :
if ( empty( $ipquery_country['country_code'] ) ) {
continue;
}
?>
<option value="<?php echo esc_attr( $ipquery_country['country_code'] ); ?>">
<?php echo esc_html( $ipquery_country['country'] . ' (' . $ipquery_country['country_code'] . ')' ); ?>
</option>
<?php endforeach; ?>
</select>
</label>
<?php
submit_button(
__( 'Delete Records', 'ipquery' ),
'secondary',
'delete_country_btn',
false,
array(
'onclick' => "return confirm('" . esc_js( __( 'Are you sure? This will permanently delete ALL visitor records from the selected country. This cannot be undone.', 'ipquery' ) ) . "')",
)
);
?>
</form>
</div>


</div>
5 changes: 5 additions & 0 deletions assets/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,8 @@
flex-direction: column;
}
}


.ipquery-panel--spaced {
margin-top: 24px;
}
51 changes: 51 additions & 0 deletions includes/class-ipquery-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_assets' ) );
add_action( 'admin_init', array( self::class, 'handle_settings_save' ) );
add_action( 'admin_post_ipquery_delete_ip', array( self::class, 'handle_delete_ip' ) );
add_action( 'admin_post_ipquery_delete_by_country', array( self::class, 'handle_delete_by_country' ) );
add_action( 'admin_post_ipquery_purge', array( self::class, 'handle_purge' ) );
add_action( 'admin_post_ipquery_lookup', array( self::class, 'handle_manual_lookup' ) );
add_action( 'wp_ajax_ipquery_chart_data', array( self::class, 'ajax_chart_data' ) );
Expand Down Expand Up @@ -216,6 +217,56 @@
exit;
}

/**
* Handles the delete-by-country form submission.
*
* @return void
*/
public static function handle_delete_by_country(): void {

Check warning on line 225 in includes/class-ipquery-admin.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "handle_delete_by_country" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=guibranco_ipquery-wordpress&issues=AZ4Chmjfjr-6ff9XG9aj&open=AZ4Chmjfjr-6ff9XG9aj&pullRequest=50
check_admin_referer( 'ipquery_delete_by_country' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Not allowed.', 'ipquery' ) );
}

$country_code = strtoupper(
Comment thread
guibranco marked this conversation as resolved.
sanitize_text_field( wp_unslash( $_POST['country_code'] ?? '' ) )
);

if ( empty( $country_code ) ) {
wp_safe_redirect( admin_url( 'admin.php?page=ipquery-visitors' ) );
exit;
}
Comment thread
guibranco marked this conversation as resolved.

$deleted = IpQuery_DB::delete_by_country( $country_code );

if ( false === $deleted ) {
wp_safe_redirect(
admin_url( 'admin.php?page=ipquery-visitors&country_delete_error=1' )
);
exit;
}

IpQuery_DB::log_action(
sprintf(
'Admin "%s" deleted %d visitor record(s) for country: %s',
wp_get_current_user()->user_login,
(int) $deleted,
$country_code
)
Comment thread
guibranco marked this conversation as resolved.
);

$deleted_param = (string) (int) $deleted;

wp_safe_redirect(
admin_url(
'admin.php?page=ipquery-visitors'
. '&country_deleted=' . $deleted_param
. '&country_code=' . rawurlencode( $country_code )
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
);
exit;
}

/**
* Handles the bulk-purge form submission.
*
Expand Down
41 changes: 41 additions & 0 deletions includes/class-ipquery-db.php
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,45 @@
$table = $wpdb->prefix . IPQUERY_TABLE;
$wpdb->delete( $table, array( 'ip' => $ip ), array( '%s' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
}

/**
* Writes an audit message to the WordPress debug log when WP_DEBUG_LOG is enabled.
*
* @param string $message The message to log.
* @return void
*/
public static function log_action( string $message ): void {

Check warning on line 315 in includes/class-ipquery-db.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "log_action" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=guibranco_ipquery-wordpress&issues=AZ4Chmhdjr-6ff9XG9ag&open=AZ4Chmhdjr-6ff9XG9ag&pullRequest=50
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
error_log( '[IpQuery] ' . $message );
}
}

/**
* Delete all visitor records for a given country code.
*
* @param string $country_code Two-letter ISO country code (e.g. 'DE').
* @return int|false Number of rows deleted, or false on error.
*/
public static function delete_by_country( string $country_code ): int|false {

Check warning on line 328 in includes/class-ipquery-db.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "delete_by_country" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=guibranco_ipquery-wordpress&issues=AZ4Chmhdjr-6ff9XG9ah&open=AZ4Chmhdjr-6ff9XG9ah&pullRequest=50
global $wpdb;
$table = $wpdb->prefix . IPQUERY_TABLE;
return $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$table,
array( 'country_code' => sanitize_text_field( $country_code ) ),
array( '%s' )
);
}

/**
* Returns distinct countries that have visitor records, for the deletion dropdown.
*
* @return array<int,array{country:string,country_code:string}>
*/
public static function get_distinct_countries(): array {

Check warning on line 343 in includes/class-ipquery-db.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename function "get_distinct_countries" to match the regular expression ^[a-z][a-zA-Z0-9]*$.

See more on https://sonarcloud.io/project/issues?id=guibranco_ipquery-wordpress&issues=AZ4Chmhdjr-6ff9XG9ai&open=AZ4Chmhdjr-6ff9XG9ai&pullRequest=50
global $wpdb;
$table = $wpdb->prefix . IPQUERY_TABLE;
$result = $wpdb->get_results( "SELECT DISTINCT country, country_code FROM {$table} WHERE country_code IS NOT NULL AND country_code != '' ORDER BY country ASC", ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
return is_array( $result ) ? $result : array();
}
}
Loading