get_charset_collate(); require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_rate_limits ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip VARCHAR(45) NOT NULL DEFAULT '', endpoint VARCHAR(80) NOT NULL DEFAULT '', hits INT UNSIGNED NOT NULL DEFAULT 1, window_start DATETIME NOT NULL, PRIMARY KEY (id), KEY ip_ep_win (ip, endpoint, window_start) ) $charset;" ); dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_blocked_ips ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, ip VARCHAR(45) NOT NULL DEFAULT '', reason VARCHAR(120) NOT NULL DEFAULT '', blocked_at DATETIME NOT NULL, expires_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY ip (ip), KEY ip_exp (ip, expires_at) ) $charset;" ); dbDelta( "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}ab_driver_documents ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, driver_id BIGINT UNSIGNED NOT NULL, doc_type ENUM('license','insurance','vehicle_photo') NOT NULL, file_url VARCHAR(512) NOT NULL DEFAULT '', expiry_date DATE NULL, status ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending', reviewed_by BIGINT UNSIGNED NULL, reviewed_at DATETIME NULL, created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY driver_doc (driver_id, doc_type), KEY status (status) ) $charset;" ); } /* ── HELPERS ──────────────────────────────────────────────── */ function abs_get_ip() { $ip = ''; if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { $parts = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ); $ip = trim( $parts[0] ); } if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) { $ip = $_SERVER['REMOTE_ADDR'] ?? ''; } return sanitize_text_field( $ip ); } function abs_is_whitelisted( $ip ) { $raw = get_option( 'ab_sec_whitelist', '' ); if ( ! $raw ) return false; return in_array( $ip, array_map( 'trim', explode( "\n", $raw ) ), true ); } function abs_is_blocked( $ip ) { global $wpdb; return (bool) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$wpdb->prefix}ab_blocked_ips WHERE ip = %s AND expires_at > %s", $ip, current_time( 'mysql' ) ) ); } function abs_block_ip( $ip, $reason, $hours ) { global $wpdb; $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->prefix}ab_blocked_ips (ip, reason, blocked_at, expires_at) VALUES (%s,%s,%s,%s) ON DUPLICATE KEY UPDATE reason=VALUES(reason), blocked_at=VALUES(blocked_at), expires_at=VALUES(expires_at)", $ip, $reason, current_time('mysql'), date('Y-m-d H:i:s', time() + $hours * 3600) ) ); } function abs_check_rate( $ip, $endpoint, $limit, $window_sec, $block_hours = 0 ) { if ( ! $ip || abs_is_whitelisted( $ip ) ) return true; if ( abs_is_blocked( $ip ) ) return false; global $wpdb; $table = $wpdb->prefix . 'ab_rate_limits'; $window = date( 'Y-m-d H:i:s', time() - $window_sec ); $hits = (int) $wpdb->get_var( $wpdb->prepare( "SELECT SUM(hits) FROM $table WHERE ip=%s AND endpoint=%s AND window_start>=%s", $ip, $endpoint, $window ) ); if ( $hits >= $limit ) { if ( $block_hours > 0 ) abs_block_ip( $ip, "Rate limit: $endpoint", $block_hours ); return false; } $minute = date( 'Y-m-d H:i:00' ); $exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table WHERE ip=%s AND endpoint=%s AND window_start=%s", $ip, $endpoint, $minute ) ); if ( $exists ) { $wpdb->query( $wpdb->prepare( "UPDATE $table SET hits=hits+1 WHERE ip=%s AND endpoint=%s AND window_start=%s", $ip, $endpoint, $minute ) ); } else { $wpdb->insert( $table, ['ip'=>$ip,'endpoint'=>$endpoint,'hits'=>1,'window_start'=>$minute] ); } return true; } /* ── RATE LIMITING: login ─────────────────────────────────── */ add_action( 'wp_login_failed', function( $u ) { abs_check_rate( abs_get_ip(), 'login', 10, 900, 2 ); } ); add_action( 'login_init', function() { if ( abs_is_blocked( abs_get_ip() ) ) wp_die( 'Tu IP fue bloqueada temporalmente por demasiados intentos fallidos.', 'Bloqueado', ['response'=>429] ); } ); /* ── RATE LIMITING: registro ──────────────────────────────── */ add_filter( 'registration_errors', function( $errors, $login, $email ) { if ( ! abs_check_rate( abs_get_ip(), 'register', 5, 3600, 24 ) ) $errors->add( 'rate_limit', 'Error: Demasiados intentos desde tu IP. Intenta más tarde.' ); return $errors; }, 10, 3 ); /* ── RATE LIMITING: REST ──────────────────────────────────── */ add_filter( 'rest_dispatch_request', function( $result, $request, $route, $handler ) { if ( strpos( $route, '/autobooking/v1/' ) === false ) return $result; if ( $request->get_method() === 'GET' ) return $result; if ( ! abs_check_rate( abs_get_ip(), 'rest_post', 120, 60, 0 ) ) return new WP_Error( 'rate_limited', 'Demasiadas solicitudes.', ['status'=>429] ); return $result; }, 10, 4 ); /* ── LIMPIEZA DIARIA ──────────────────────────────────────── */ add_action( 'abs_daily_cleanup', function() { global $wpdb; $wpdb->query("DELETE FROM {$wpdb->prefix}ab_rate_limits WHERE window_start < DATE_SUB(NOW(), INTERVAL 2 DAY)"); $wpdb->query("DELETE FROM {$wpdb->prefix}ab_blocked_ips WHERE expires_at < NOW()"); } ); if ( ! wp_next_scheduled('abs_daily_cleanup') ) wp_schedule_event( time(), 'daily', 'abs_daily_cleanup' ); /* ── HCAPTCHA ─────────────────────────────────────────────── */ add_action( 'register_form', function() { $key = get_option('ab_hcaptcha_site_key',''); if ( ! $key ) return; echo '
'; echo ''; } ); add_filter( 'registration_errors', function( $errors, $login, $email ) { $secret = get_option('ab_hcaptcha_secret',''); if ( ! $secret ) return $errors; $token = sanitize_text_field( $_POST['h-captcha-response'] ?? '' ); if ( ! $token ) { $errors->add('captcha_missing','Error: Completa el captcha.'); return $errors; } $res = wp_remote_post('https://api.hcaptcha.com/siteverify', [ 'body' => ['secret'=>$secret,'response'=>$token,'remoteip'=>abs_get_ip()], 'timeout' => 10, ]); if ( ! is_wp_error($res) ) { $body = json_decode(wp_remote_retrieve_body($res), true); if ( empty($body['success']) ) $errors->add('captcha_invalid','Error: Captcha inválido.'); } return $errors; }, 20, 3 ); /* ── EMAIL CONFIRMATION ───────────────────────────────────── */ add_action( 'user_register', function( $uid ) { $user = get_userdata($uid); if ( ! $user || ! in_array('driver_pending',(array)$user->roles,true) ) return; $token = bin2hex(random_bytes(32)); update_user_meta($uid,'ab_email_token',$token); update_user_meta($uid,'ab_email_token_expires',date('Y-m-d H:i:s',time()+86400)); update_user_meta($uid,'ab_email_confirmed',0); $url = add_query_arg('token',$token,get_rest_url(null,'autobooking/v1/confirm-email')); wp_mail($user->user_email,'Confirma tu email — AutoBooking', "Hola {$user->display_name},\n\nConfirma tu email (válido 24h):\n\n$url\n\nAutoBooking"); } ); add_action( 'template_redirect', function() { if ( ! is_page('driver-dashboard') && ! is_page('pending-driver') ) return; if ( ! is_user_logged_in() ) return; $user = wp_get_current_user(); $roles = (array)$user->roles; if ( ! in_array('driver_pending',$roles,true) && ! in_array('driver',$roles,true) ) return; if ( (int)get_user_meta($user->ID,'ab_email_confirmed',true) ) return; $resend = esc_url(get_rest_url(null,'autobooking/v1/resend-confirmation')); wp_die( 'Confirma tu email
' .'Revisa tu bandeja de entrada y haz clic en el enlace de confirmación.
' .'