desbloquear_ip(). * @param string $ip The IP address to unblock. */ private function unblock_ip_autonomo( $ip ) { $block_types = [ 'geoblock', '404', '403', 'login', 'manual', 'honeypot', 'user_agent' ]; $unblocked_from = []; foreach ( $block_types as $type ) { delete_transient( 'advaipbl_bloqueo_' . $type . '_' . md5( $ip ) ); if ( in_array( $type, [ '404', '403', 'login' ], true ) ) { delete_transient( 'advaipbl_errores_' . $type . '_' . md5( $ip ) ); delete_transient( 'advaipbl_detail_' . $type . '_' . md5( $ip ) ); } $option_key = 'advaipbl_ips_bloqueadas_' . $type; $list = get_option( $option_key, [] ); if ( is_array( $list ) && array_key_exists( $ip, $list ) ) { unset( $list[ $ip ] ); update_option( $option_key, $list ); $unblocked_from[] = $type; } } if ( ! empty( $unblocked_from ) ) { /* translators: 1: IP address, 2: List of block types (e.g., "404, manual"). */ $this->log_event_autonomo( sprintf( __( 'IP %1$s unblocked from lists: %2$s. Action via WP-CLI.', 'advanced-ip-blocker' ), $ip, implode( ', ', $unblocked_from ) ), 'info', $ip ); } } /** * Standalone replication of ADVAIPBL_Main->log_event(). * @param string $message Log message. * @param string $level Level (info, warning, etc.). * @param string $ip IP associated with the event. */ private function log_event_autonomo( $message, $level = 'info', $ip = '127.0.0.1' ) { $options = get_option( 'advaipbl_settings', [] ); if ( empty( $options['enable_logging'] ) ) return; global $wpdb; $table_name = $wpdb->prefix . 'advaipbl_logs'; @$wpdb->insert( $table_name, [ 'timestamp' => current_time( 'mysql', 1 ), 'ip' => $ip, 'log_type' => 'general', 'level' => $level, 'message' => $message, 'details' => wp_json_encode( [ 'source' => 'WP-CLI' ] ) ] ); } // --- PUBLIC COMMANDS --- /** * Manually blocks an IP address (adds to permanent blacklist). * * ## OPTIONS * * * : The IP address to block. * * [--reason=] * : An optional reason for the block. * --- * default: Blocked via WP-CLI * --- * * ## EXAMPLES * * $ wp advaipbl block 1.2.3.4 * Success: IP 1.2.3.4 blocked successfully. * * $ wp advaipbl block 8.8.8.8 --reason="Suspicious activity" * Success: IP 8.8.8.8 blocked successfully. */ public function block( $args, $assoc_args ) { if ( empty( $args[0] ) ) { WP_CLI::error( 'Usage: wp advaipbl block [--reason=]' ); return; } $ip = $args[0]; $reason = $assoc_args['reason'] ?? 'Blocked via WP-CLI'; if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { WP_CLI::error( "'{$ip}' is not a valid IP address." ); return; } $option_key = 'advaipbl_ips_bloqueadas_manual'; $list = get_option( $option_key, [] ); if ( array_key_exists( $ip, $list ) ) { WP_CLI::warning( "IP {$ip} was already in the manual block list." ); return; } $list[ $ip ] = [ 'timestamp' => time(), 'detail' => $reason ]; update_option( $option_key, $list ); set_transient( 'advaipbl_bloqueo_manual_' . md5( $ip ), true, 10 * YEAR_IN_SECONDS ); /* translators: 1: The IP address, 2: The reason for blocking. */ $this->log_event_autonomo( sprintf( __( 'IP %1$s manually blocked via WP-CLI. Reason: %2$s', 'advanced-ip-blocker' ), $ip, $reason ), 'warning', $ip ); WP_CLI::success( "IP {$ip} blocked successfully." ); } /** * Unblocks an IP address from all blocklists. * * This command removes the specified IP from all temporary and permanent * blocklists, effectively whitelisting it for future requests. * * ## OPTIONS * * * : The IP address to unblock. * * ## EXAMPLES * * $ wp advaipbl unblock 1.2.3.4 * Success: Attempted to unblock 1.2.3.4 from all lists. */ public function unblock( $args ) { if ( empty( $args[0] ) ) { WP_CLI::error( 'Usage: wp advaipbl unblock ' ); return; } $ip = $args[0]; if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { WP_CLI::error( "'{$ip}' is not a valid IP address." ); return; } $this->unblock_ip_autonomo( $ip ); WP_CLI::success( "Attempted to unblock {$ip} from all lists." ); } /** * Displays a list of currently blocked IPs. * * ## OPTIONS * * [--type=] * : Filter the list by a specific block type. * --- * default: all * options: * - all * - geoblock * - manual * - honeypot * - user_agent * - 404 * - 403 * - login * --- * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - json * - csv * - yaml * --- * * ## EXAMPLES * * # List all blocked IPs * $ wp advaipbl blocked * * # List only IPs blocked for 404 errors in JSON format * $ wp advaipbl blocked --type=404 --format=json */ public function blocked( $args, $assoc_args ) { $all_blocked = []; $block_types = [ 'geoblock' => 'Geoblock', 'honeypot' => 'Honeypot', 'user_agent' => 'User-Agent', 'manual' => 'Manual', '404' => '404 Error', '403' => '403 Error', 'login' => 'Login Failure' ]; $filter_type = \WP_CLI\Utils\get_flag_value( $assoc_args, 'type', 'all' ); $types_to_check = ( 'all' === $filter_type ) ? array_keys($block_types) : [ $filter_type ]; foreach ( $types_to_check as $type ) { if ( ! isset( $block_types[$type] ) ) continue; $label = $block_types[$type]; $ips = get_option( 'advaipbl_ips_bloqueadas_' . $type, [] ); if ( ! is_array($ips) ) continue; foreach ( $ips as $ip => $block_data ) { $timestamp = is_array($block_data) ? ($block_data['timestamp'] ?? 0) : $block_data; $detail = is_array($block_data) ? ($block_data['detail'] ?? '') : ''; $all_blocked[] = [ 'ip' => $ip, 'type_label' => $label, 'timestamp' => ADVAIPBL_Main::get_formatted_datetime($timestamp), 'detail' => $detail ]; } } if ( empty( $all_blocked ) ) { WP_CLI::line( 'No blocked IPs found for the selected filter.' ); return; } usort($all_blocked, function($a, $b) { return strtotime($b['timestamp']) <=> strtotime($a['timestamp']); }); WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', array_values( $all_blocked ), [ 'ip', 'type_label', 'timestamp', 'detail' ] ); } /** * Manages active user sessions. * * ## SYNOPSIS * * wp advaipbl session [--=] * * ## SUBCOMMANDS * * list * : Lists all currently active user sessions. * * terminate * : Terminates user sessions based on a condition. * * ## EXAMPLES * * # List all active sessions * $ wp advaipbl session list * * # Terminate all sessions for the user with ID 2 * $ wp advaipbl session terminate --user-id=2 * * # Terminate all sessions for all users * $ wp advaipbl session terminate --all * * @subcommand session */ public function session( $args, $assoc_args ) { $subcommand = array_shift( $args ); if ( ! in_array( $subcommand, ['list', 'terminate'], true ) ) { WP_CLI::error( "Invalid subcommand. Use 'list' or 'terminate'." ); return; } if ( 'list' === $subcommand ) { if ( ! class_exists('ADVAIPBL_User_Session_Manager') ) { require_once( dirname(__FILE__) . '/class-advaipbl-session-manager.php' ); } $session_manager = new ADVAIPBL_User_Session_Manager(null); $sessions = $session_manager->get_active_sessions(); $role_filter = \WP_CLI\Utils\get_flag_value( $assoc_args, 'role' ); if ( $role_filter ) { $sessions = array_filter($sessions, function($session) use ($role_filter) { return in_array($role_filter, explode(', ', $session['role']), true); }); } if (empty($sessions)) { WP_CLI::line('No active user sessions found.'); return; } WP_CLI\Utils\format_items($assoc_args['format'] ?? 'table', $sessions, ['user_id', 'username', 'role', 'ip', 'last_activity']); } elseif ( 'terminate' === $subcommand ) { $user_id = \WP_CLI\Utils\get_flag_value( $assoc_args, 'user-id' ); $user_login = \WP_CLI\Utils\get_flag_value( $assoc_args, 'user-login' ); $role = \WP_CLI\Utils\get_flag_value( $assoc_args, 'role' ); $all = \WP_CLI\Utils\get_flag_value( $assoc_args, 'all', false ); if ( ! $user_id && ! $user_login && ! $role && ! $all ) { WP_CLI::error('Please specify a condition: --user-id, --user-login, --role, or --all.'); return; } $user_ids_to_logout = []; $log_message = ''; if ( $user_id ) { $user_ids_to_logout[] = absint($user_id); /* translators: %s: User ID. */ $log_message = sprintf( __( 'Sessions for user ID %s terminated via WP-CLI.', 'advanced-ip-blocker' ), $user_id ); } elseif ( $user_login ) { $user = get_user_by('login', $user_login); if ($user) { $user_ids_to_logout[] = $user->ID; /* translators: %s: User login name. */ $log_message = sprintf( __( 'Sessions for user \'%s\' terminated via WP-CLI.', 'advanced-ip-blocker' ), $user_login ); } } elseif ( $role ) { $user_ids_to_logout = get_users(['role' => $role, 'fields' => 'ID']); /* translators: %s: User role. */ $log_message = sprintf( __( 'All sessions for role \'%s\' terminated via WP-CLI.', 'advanced-ip-blocker' ), $role ); } elseif ( $all ) { $user_ids_to_logout = get_users(['fields' => 'ID']); $log_message = __( 'All user sessions terminated via WP-CLI.', 'advanced-ip-blocker' ); } if (empty($user_ids_to_logout)) { WP_CLI::warning('No users found matching the criteria.'); return; } WP_CLI::confirm(sprintf('This will terminate sessions for %d user(s). Are you sure?', count($user_ids_to_logout)), $assoc_args); $terminated_count = 0; foreach ($user_ids_to_logout as $uid) { WP_Session_Tokens::get_instance($uid)->destroy_all(); $terminated_count++; } $this->log_event_autonomo($log_message, 'warning'); WP_CLI::success(sprintf('Successfully terminated sessions for %d user(s).', $terminated_count)); } } /** * Manages the IP whitelist. * * The whitelist ensures that an IP address will never be blocked by any * automatic rule. Adding an IP to the whitelist also removes it from * all existing blocklists. * * ## OPTIONS * * * : The action to perform. * --- * options: * - add * - remove * - list * --- * * [] * : The IP address to add or remove. Required for 'add' and 'remove' actions. * * [--format=] * : Render output in a particular format. Only applies to the 'list' action. * --- * default: table * options: * - table * - json * - csv * - yaml * --- * * ## EXAMPLES * * # List all IPs currently in the whitelist * $ wp advaipbl whitelist list * * # Add a new IP to the whitelist * $ wp advaipbl whitelist add 192.168.1.100 * * # Remove an IP from the whitelist * $ wp advaipbl whitelist remove 192.168.1.100 * * @subcommand whitelist */ public function whitelist( $args, $assoc_args ) { $subcommand = array_shift( $args ); if ( ! in_array( $subcommand, [ 'add', 'remove', 'list' ], true ) ) { WP_CLI::error( 'Invalid subcommand. Use: add, remove, or list.' ); } if ( in_array( $subcommand, [ 'add', 'remove' ], true ) ) { if ( empty( $args[0] ) ) { WP_CLI::error( 'Please provide an IP address.' ); return; } $ip = $args[0]; if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { WP_CLI::error( 'Invalid IP address.' ); return; } } $option_key = 'advaipbl_ips_whitelist'; $list = get_option( $option_key, [] ); switch ( $subcommand ) { case 'add': if ( array_key_exists( $ip, $list ) ) { WP_CLI::warning( "IP {$ip} is already in the whitelist." ); return; } $list[ $ip ] = [ 'timestamp' => time(), 'detail' => 'Added via WP-CLI' ]; update_option( $option_key, $list ); $this->unblock_ip_autonomo( $ip ); /* translators: %s: IP address. */ $this->log_event_autonomo( sprintf( __( 'IP %s added to whitelist via WP-CLI.', 'advanced-ip-blocker' ), $ip ), 'info', $ip ); WP_CLI::success( "IP {$ip} added to whitelist and unblocked from all lists." ); break; case 'remove': if ( ! array_key_exists( $ip, $list ) ) { WP_CLI::warning( "IP {$ip} was not in the whitelist." ); return; } unset( $list[ $ip ] ); update_option( $option_key, $list ); /* translators: %s: IP address. */ $this->log_event_autonomo( sprintf( __( 'IP %s removed from whitelist via WP-CLI.', 'advanced-ip-blocker' ), $ip ), 'info', $ip ); WP_CLI::success( "IP {$ip} removed from whitelist." ); break; case 'list': if ( empty( $list ) ) { WP_CLI::line( 'The whitelist is empty.' ); return; } $items = []; foreach ( $list as $ip_addr => $data ) { $timestamp = 0; $detail = 'N/A'; if ( is_array($data) && isset($data['timestamp']) ) { $timestamp = $data['timestamp']; $detail = $data['detail'] ?? ''; } elseif ( is_numeric($data) ) { $timestamp = $data; $detail = 'Migrated from old format'; } $items[] = [ 'ip' => $ip_addr, 'detail' => $detail, 'added_on' => ADVAIPBL_Main::get_formatted_datetime( $timestamp ) ]; } WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', $items, [ 'ip', 'detail', 'added_on' ] ); break; } } /** * Manages the list of blocked countries. * * ## EXAMPLES * * # List all currently blocked country codes * $ wp advaipbl geoblock list * * # Add China to the block list * $ wp advaipbl geoblock add CN * * # Remove Germany from the block list * $ wp advaipbl geoblock remove DE * * @subcommand geoblock */ public function geoblock( $args, $assoc_args ) { $subcommand = array_shift( $args ); if ( ! in_array( $subcommand, [ 'add', 'remove', 'list' ], true ) ) { WP_CLI::error( 'Invalid subcommand. Use: add, remove, or list.' ); } $settings = get_option('advaipbl_settings', []); $blocked_countries = $settings['geoblock_countries'] ?? []; switch ( $subcommand ) { case 'add': if ( count($args) < 1 ) { WP_CLI::error( 'Please provide a 2-letter country code. Usage: wp advaipbl geoblock add ' ); return; } $country_code = strtoupper($args[0]); if ( strlen($country_code) !== 2 ) { WP_CLI::error( 'Country code must be 2 letters (e.g., US, DE, CN).' ); return; } if ( in_array($country_code, $blocked_countries, true) ) { WP_CLI::warning( "Country {$country_code} is already in the block list." ); return; } $blocked_countries[] = $country_code; $settings['geoblock_countries'] = array_unique($blocked_countries); update_option('advaipbl_settings', $settings); /* translators: %s: Two-letter country code. */ $this->log_event_autonomo( sprintf( __( 'Country %s added to geoblock list via WP-CLI.', 'advanced-ip-blocker' ), strtoupper($country_code) ), 'warning' ); WP_CLI::success( "Country {$country_code} added to the block list." ); break; case 'remove': if ( count($args) < 1 ) { WP_CLI::error( 'Please provide a country code to remove.' ); return; } $country_code = strtoupper($args[0]); $key = array_search($country_code, $blocked_countries, true); if ( false === $key ) { WP_CLI::warning( "Country {$country_code} was not found in the block list." ); return; } unset($blocked_countries[$key]); $settings['geoblock_countries'] = array_values($blocked_countries); update_option('advaipbl_settings', $settings); /* translators: %s: Two-letter country code. */ $this->log_event_autonomo( sprintf( __( 'Country %s removed from geoblock list via WP-CLI.', 'advanced-ip-blocker' ), strtoupper($country_code) ), 'info' ); WP_CLI::success( "Country {$country_code} removed from the block list." ); break; case 'list': if ( empty($blocked_countries) ) { WP_CLI::line('The country block list is empty.'); return; } WP_CLI::line('Blocked country codes:'); foreach ($blocked_countries as $code) { WP_CLI::line("- {$code}"); } break; } } /** * Manages the plugin's security and event logs. * * ## EXAMPLES * * # List the last 20 general log entries * $ wp advaipbl log list * * # List the last 50 logs of 404 errors * $ wp advaipbl log list --type=404 --count=50 * * # Clear all 403 error logs after confirmation * $ wp advaipbl log clear --type=403 * * # Clear all logs of all types without asking for confirmation * $ wp advaipbl log clear --force * * @subcommand log */ public function log( $args, $assoc_args ) { $subcommand = array_shift( $args ); switch ( $subcommand ) { case 'list': $this->log_list( $args, $assoc_args ); break; case 'clear': /* translators: %s: Log type (e.g., "404" or "all"). */ $this->log_event_autonomo( sprintf( __( 'Clearing logs of type \'%s\' via WP-CLI.', 'advanced-ip-blocker' ), $type ), 'warning' ); break; default: WP_CLI::error( "Please specify a subcommand for 'log': list or clear. Use 'wp help advaipbl log' for more information." ); } } /** * Lists log entries. * * ## OPTIONS * * [--type=] * : The type of log entries to display. * --- * default: general * options: * - all * - general * - 404 * - 403 * - login * - honeypot * - user_agent * --- * * [--count=] * : The number of entries to list. * --- * default: 20 * --- * * [--format=] * : Render output in a particular format. * --- * default: table * options: * - table * - json * - csv * - yaml * --- */ private function log_list( $args, $assoc_args ) { global $wpdb; $table_name = $wpdb->prefix . 'advaipbl_logs'; $type = $assoc_args['type'] ?? 'general'; // Cambiado a 'general' por defecto para ser más claro. $count = $assoc_args['count'] ?? 20; $where_clause = '1=1'; if ( 'all' !== $type ) { $where_clause = $wpdb->prepare( 'log_type = %s', $type ); } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $logs = $wpdb->get_results( $wpdb->prepare( "SELECT timestamp, level, ip, message, details FROM $table_name WHERE $where_clause ORDER BY timestamp DESC LIMIT %d", $count ), ARRAY_A ); if ( empty( $logs ) ) { WP_CLI::line( 'No log entries found for the specified type.' ); return; } WP_CLI\Utils\format_items( $assoc_args['format'] ?? 'table', $logs, [ 'timestamp', 'level', 'ip', 'message' ] ); } /** * Clears log entries. * * ## OPTIONS * * [--type=] * : The type of log entries to clear. If omitted, all logs will be cleared. * --- * default: all * options: * - all * - general * - 404 * - 403 * - login * - honeypot * - user_agent * --- * * [--force] * : Skip the confirmation prompt. */ private function log_clear( $args, $assoc_args ) { $type = $assoc_args['type'] ?? 'all'; if ( ! isset( $assoc_args['force'] ) ) { $confirm_message = ( 'all' === $type ) ? 'Are you sure you want to clear ALL logs? This action cannot be undone.' : "Are you sure you want to clear all '{$type}' logs?"; WP_CLI::confirm( $confirm_message ); } global $wpdb; $table_name = $wpdb->prefix . 'advaipbl_logs'; $this->log_event_autonomo( "WP-CLI: Clearing logs of type '{$type}'.", 'warning' ); if ( 'all' !== $type ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $deleted = $wpdb->delete( $table_name, [ 'log_type' => $type ], [ '%s' ] ); WP_CLI::success( "{$deleted} logs of type '{$type}' have been cleared." ); } else { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( "TRUNCATE TABLE $table_name" ); WP_CLI::success( 'All logs have been cleared.' ); } } /** * Gets or sets plugin configuration options. * * ## EXAMPLES * * # Get all configuration options * $ wp advaipbl config get * * # Get the value of a single option * $ wp advaipbl config get login_threshold * * # Set the value of an option * $ wp advaipbl config set login_threshold 5 * * @subcommand config */ public function config( $args, $assoc_args ) { $subcommand = array_shift( $args ); switch ( $subcommand ) { case 'get': $options = get_option( 'advaipbl_settings', [] ); $key = array_shift( $args ); if ( $key ) { if ( isset( $options[ $key ] ) ) { WP_CLI::line( is_array($options[$key]) ? wp_json_encode($options[$key]) : $options[ $key ] ); } else { WP_CLI::error( "Option '{$key}' not found." ); } } else { $display_items = []; foreach ($options as $option_key => $option_value) { $display_items[] = [ 'key' => $option_key, 'value' => is_array($option_value) ? wp_json_encode($option_value) : $option_value, ]; } WP_CLI\Utils\format_items( 'table', $display_items, [ 'key', 'value' ] ); } break; case 'set': if ( count( $args ) < 2 ) { WP_CLI::error( 'Usage: wp advaipbl config set ' ); return; } $options = get_option( 'advaipbl_settings', [] ); list($key, $value) = $args; if ( ! array_key_exists( $key, $options ) ) { WP_CLI::error( "Option '{$key}' is not a valid option key." ); return; } $sanitized_value = sanitize_text_field( $value ); $options[ $key ] = $sanitized_value; update_option( 'advaipbl_settings', $options ); /* translators: 1: Option key, 2: New value. */ $this->log_event_autonomo( sprintf( __( 'Option \'%1$s\' updated to \'%2$s\' via WP-CLI.', 'advanced-ip-blocker' ), $key, $sanitized_value ), 'info' ); WP_CLI::success( "Option '{$key}' updated to '{$sanitized_value}'." ); break; default: WP_CLI::error( "Please specify a subcommand for 'config': get or set." ); } } }