[lat_min, lat_max, lng_min, lng_max] Se pueden agregar más países desde el admin sin tocar el código. ─────────────────────────────────────────────────────────────── */ function ab_get_country_boxes() { return [ 'US' => [ [ 24.5, 49.5, -125.0, -66.9 ], // Contiguous USA [ 51.2, 71.5, -180.0, -129.9 ], // Alaska [ 18.9, 22.2, -160.2, -154.8 ], // Hawaii ], 'CO' => [[ 1.5, 13.4, -79.0, -66.8 ]], 'MX' => [[ 14.5, 32.7, -118.4, -86.7 ]], 'CA' => [[ 41.7, 83.1, -141.0, -52.6 ]], 'GB' => [[ 49.9, 60.9, -8.2, 1.8 ]], 'ES' => [[ 35.9, 43.8, -9.3, 4.3 ]], 'AR' => [[-55.0, -21.8, -73.6, -53.6 ]], 'BR' => [[-33.7, 5.3, -73.9, -34.8 ]], 'CL' => [[-55.9, -17.5, -75.6, -66.9 ]], 'PE' => [[-18.4, -0.0, -81.3, -68.7 ]], 'EC' => [[ -5.0, 1.5, -81.0, -75.2 ]], 'VE' => [[ 0.6, 12.2, -73.3, -59.8 ]], ]; } /* ────────────────────────────────────────────────────────────── HELPER — detecta el país de unas coordenadas Retorna el código ISO 2 del país si está en las boxes, o null si no coincide con ningún país configurado. ─────────────────────────────────────────────────────────────── */ function ab_get_country_from_coords( $lat, $lng ) { $lat = (float) $lat; $lng = (float) $lng; if ( ! $lat || ! $lng ) return null; foreach ( ab_get_country_boxes() as $code => $boxes ) { foreach ( $boxes as $box ) { if ( $lat >= $box[0] && $lat <= $box[1] && $lng >= $box[2] && $lng <= $box[3] ) { return $code; } } } return null; } /* ────────────────────────────────────────────────────────────── HELPER — verifica si un país está en la lista de permitidos ─────────────────────────────────────────────────────────────── */ function ab_is_country_allowed( $country_code ) { if ( ! $country_code ) return false; $allowed = get_option( 'ab_allowed_countries', [ 'US' ] ); if ( is_string( $allowed ) ) $allowed = json_decode( $allowed, true ) ?: [ 'US' ]; return in_array( strtoupper( $country_code ), array_map( 'strtoupper', (array) $allowed ), true ); } /* ────────────────────────────────────────────────────────────── IP GEOLOCATION — verifica país por IP como backup del GPS ─────────────────────────────────────────────────────────────── */ function ab_get_country_from_ip( $ip ) { if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) return null; $cache_key = 'ab_geo_ip_' . md5( $ip ); $cached = get_transient( $cache_key ); if ( $cached !== false ) return $cached ?: null; $response = wp_remote_get( 'https://ip-api.com/json/' . rawurlencode( $ip ) . '?fields=countryCode,status', ['timeout'=>5] ); if ( is_wp_error( $response ) ) { set_transient( $cache_key, '', 3600 ); return null; } $body = json_decode( wp_remote_retrieve_body( $response ), true ); $code = ( ! empty( $body['status'] ) && $body['status'] === 'success' ) ? strtoupper( $body['countryCode'] ?? '' ) : ''; set_transient( $cache_key, $code, 86400 ); return $code ?: null; } function ab_get_request_ip() { if ( function_exists( 'abs_get_ip' ) ) return 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 ); } /* ────────────────────────────────────────────────────────────── ENDPOINT PÚBLICO — GET /autobooking/v1/geo/check?lat=&lng= El JS lo llama antes de cualquier acción crítica. Retorna si el país está permitido Y cuál es el país detectado. ─────────────────────────────────────────────────────────────── */ add_action( 'rest_api_init', function () { // Check de coordenadas register_rest_route( 'autobooking/v1', '/geo/check', [ 'methods' => 'GET', 'callback' => function ( WP_REST_Request $req ) { $lat = (float) $req->get_param( 'lat' ); $lng = (float) $req->get_param( 'lng' ); $country = ab_get_country_from_coords( $lat, $lng ); $allowed = ab_is_country_allowed( $country ); if ( $allowed && $country ) { $ip_country = ab_get_country_from_ip( ab_get_request_ip() ); if ( $ip_country && $ip_country !== $country ) { return rest_ensure_response(['allowed'=>false,'country'=>$country,'message'=>'Ubicación inconsistente. Verifica tu conexión.']); } } return rest_ensure_response( [ 'allowed' => $allowed, 'country' => $country, 'message' => $allowed ? '' : get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ), ] ); }, 'permission_callback' => '__return_true', 'args' => [ 'lat' => [ 'required' => true, 'type' => 'number' ], 'lng' => [ 'required' => true, 'type' => 'number' ], ], ] ); // Leer configuración geo (para el admin dashboard) register_rest_route( 'autobooking/v1', '/admin/geo-settings', [ 'methods' => 'GET', 'callback' => function () { $allowed = get_option( 'ab_allowed_countries', [ 'US' ] ); if ( is_string( $allowed ) ) $allowed = json_decode( $allowed, true ) ?: [ 'US' ]; return rest_ensure_response( [ 'allowed_countries' => array_values( $allowed ), 'available_countries' => array_keys( ab_get_country_boxes() ), 'blocked_message' => get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ), ] ); }, 'permission_callback' => function () { return current_user_can( 'manage_autobooking' ); }, ] ); // Guardar configuración geo (desde el admin dashboard) register_rest_route( 'autobooking/v1', '/admin/geo-settings/save', [ 'methods' => 'POST', 'callback' => function ( WP_REST_Request $req ) { $body = $req->get_json_params(); $allowed = array_map( 'strtoupper', array_filter( (array) ( $body['allowed_countries'] ?? [] ), 'is_string' ) ); $msg = sanitize_textarea_field( $body['blocked_message'] ?? '' ); if ( empty( $allowed ) ) { return new WP_Error( 'invalid', 'Debe haber al menos un país permitido.', [ 'status' => 400 ] ); } update_option( 'ab_allowed_countries', array_values( $allowed ) ); if ( $msg ) update_option( 'ab_geo_blocked_msg', $msg ); return rest_ensure_response( [ 'ok' => true, 'allowed_countries' => $allowed ] ); }, 'permission_callback' => function () { return current_user_can( 'manage_autobooking' ); }, ] ); } ); /* ────────────────────────────────────────────────────────────── INTERCEPTOR REST — bloquea endpoints sensibles fuera de países permitidos ─────────────────────────────────────────────────────────────── */ add_filter( 'rest_dispatch_request', function ( $result, WP_REST_Request $request, $route, $handler ) { if ( $request->get_method() !== 'POST' ) return $result; $body = $request->get_json_params() ?: []; $lat = 0; $lng = 0; /* Conductor se pone online */ if ( preg_match( '#^/autobooking/v1/driver/status$#', $route ) ) { if ( ! empty( $body['online'] ) ) { $lat = floatval( $body['lat'] ?? 0 ); $lng = floatval( $body['lng'] ?? 0 ); if ( $lat && $lng ) { $country = ab_get_country_from_coords( $lat, $lng ); if ( ! ab_is_country_allowed( $country ) ) { return new WP_Error( 'geo_restricted', get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ), [ 'status' => 403, 'country' => $country ] ); } } } return $result; } /* Pasajero solicita viaje */ if ( preg_match( '#^/autobooking/v1/passenger/(request|book|ride)$#', $route ) ) { $lat = floatval( $body['origin_lat'] ?? $body['pickup_lat'] ?? 0 ); $lng = floatval( $body['origin_lng'] ?? $body['pickup_lng'] ?? 0 ); if ( $lat && $lng ) { $country = ab_get_country_from_coords( $lat, $lng ); if ( ! ab_is_country_allowed( $country ) ) { return new WP_Error( 'geo_restricted', get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ), [ 'status' => 403, 'country' => $country ] ); } } return $result; } /* Corporate reserva viaje */ if ( preg_match( '#^/autobooking/v1/corporate/(book|trip|request)$#', $route ) ) { $lat = floatval( $body['origin_lat'] ?? 0 ); $lng = floatval( $body['origin_lng'] ?? 0 ); if ( $lat && $lng ) { $country = ab_get_country_from_coords( $lat, $lng ); if ( ! ab_is_country_allowed( $country ) ) { return new WP_Error( 'geo_restricted', get_option( 'ab_geo_blocked_msg', 'AutoBooking is not available in your current location.' ), [ 'status' => 403, 'country' => $country ] ); } } return $result; } return $result; }, 10, 4 );