✓ ' . sprintf( esc_html__( 'IP %s unblocked.', 'guardforge' ), '' . esc_html( $guardforge_ip ) . '' ) . ''; } $guardforge_prefix = '_transient_' . GUARDFORGE_Bruteforce::ATTEMPTS_TRANSIENT_PREFIX; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- lists this plugin's own lockout transients by their full-slug prefix; WordPress has no API that enumerates transients, and the pattern goes through prepare and esc_like. $guardforge_locked = $wpdb->get_results( $wpdb->prepare( "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $guardforge_prefix ) . '%' ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $guardforge_log_table = $wpdb->prefix . 'guardforge_login_log'; // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $guardforge_log_table is a prefixed table name, not user input. // One row per IP, so locks in the same second tie on last_time, and LIMIT cuts // the tie wherever the engine puts it. The IP is the group's own column. $guardforge_recent = $wpdb->get_results( "SELECT ip, COUNT(*) as cnt, MAX(event_time) as last_time FROM {$guardforge_log_table} WHERE result = 'locked' GROUP BY ip ORDER BY last_time DESC, ip ASC LIMIT 20" ); // phpcs:enable PluginCheck.Security.DirectDB.UnescapedDBParameter,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared ?>

option_name ); ?>
option_value; ?>

ip ); ?> cnt ); ?> last_time ); ?>