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 ) ) { $this->log_event_autonomo( sprintf( 'IP %s unblocked from lists: %s.', $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'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching @$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 ) { // 1. Validación de argumentos. if ( empty( $args[0] ) ) { WP_CLI::error( 'Usage: wp advaipbl block [--reason=]' ); return; } $ip = $args[0]; if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) { WP_CLI::error( "'{$ip}' is not a valid IP address." ); return; } $reason = $assoc_args['reason'] ?? 'Blocked via WP-CLI'; // 2. Lógica de bloqueo (replicando la del panel de admin). $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 ); // 3. Registrar el evento y notificar al usuario. $this->log_event_autonomo( "IP {$ip} manually blocked via WP-CLI.", '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 = $assoc_args['type'] ?? 'all'; $types_to_check = ( 'all' === $filter_type ) ? array_keys($block_types) : [ $filter_type ]; foreach ( $types_to_check as $type ) { $label = $block_types[$type] ?? ucfirst($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 'editor_user' * $ wp advaipbl session terminate --user-login=editor_user * * @subcommand session */ public function session( $args, $assoc_args ) { if ( ! class_exists('ADVAIPBL_User_Session_Manager') ) { require_once( dirname(__FILE__) . '/class-advaipbl-session-manager.php' ); } $session_manager = new ADVAIPBL_User_Session_Manager(null); $subcommand = array_shift( $args ); switch ( $subcommand ) { case 'list': $sessions = $session_manager->get_active_sessions(); $role_filter = $assoc_args['role'] ?? null; 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']); break; case 'terminate': $user_ids_to_logout = []; $log_message = ''; if ( isset( $assoc_args['user-id'] ) ) { $user_id = absint($assoc_args['user-id']); if ( get_userdata($user_id) ) { $user_ids_to_logout[] = $user_id; $log_message = "WP-CLI: Sessions for user ID {$user_id} terminated."; } else { WP_CLI::error("User with ID {$user_id} not found."); } } elseif ( isset( $assoc_args['user-login'] ) ) { $user = get_user_by('login', $assoc_args['user-login']); if ($user) { $user_ids_to_logout[] = $user->ID; $log_message = "WP-CLI: Sessions for user '{$assoc_args['user-login']}' terminated."; } else { WP_CLI::error("User with login '{$assoc_args['user-login']}' not found."); } } elseif ( isset( $assoc_args['role'] ) ) { $role = sanitize_text_field($assoc_args['role']); $user_ids_to_logout = get_users(['role' => $role, 'fields' => 'ID']); $log_message = "WP-CLI: All sessions for role '{$role}' terminated."; } elseif ( isset( $assoc_args['all'] ) ) { $user_ids_to_logout = get_users(['fields' => 'ID']); $log_message = 'WP-CLI: All sessions for all users terminated.'; } else { WP_CLI::error('Please specify a condition to terminate sessions (e.g., --user-id, --role, --all).'); } if (empty($user_ids_to_logout)) { WP_CLI::warning('No users found matching the criteria. No sessions terminated.'); 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 $user_id) { WP_Session_Tokens::get_instance($user_id)->destroy_all(); $terminated_count++; } $this->log_event_autonomo($log_message, 'warning'); WP_CLI::success(sprintf('Successfully terminated sessions for %d user(s).', $terminated_count)); break; default: WP_CLI::error( "Please specify a subcommand for 'session': list or terminate." ); } } /** * 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 ); $this->log_event_autonomo( "IP {$ip} added to whitelist via WP-CLI.", '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 ); $this->log_event_autonomo( "IP {$ip} removed from whitelist via WP-CLI.", '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); 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); 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': $this->log_clear( $args, $assoc_args ); 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; } $options[ $key ] = sanitize_text_field( $value ); update_option( 'advaipbl_settings', $options ); $this->log_event_autonomo( "WP-CLI: Option '{$key}' updated.", 'info' ); WP_CLI::success( "Option '{$key}' updated to '{$value}'." ); break; default: WP_CLI::error( "Please specify a subcommand for 'config': get or set." ); } } }