243 lines
12 KiB
PHP
243 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: AutoBooking Geo Restrict
|
|
* Description: Permite operar solo en países habilitados por el admin. Cada usuario solo
|
|
* puede usar la app dentro del país donde está físicamente (GPS). Si el país
|
|
* no está en la lista de permitidos, acceso bloqueado.
|
|
* Version: 1.1.0
|
|
* Author: AutoBooking
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) exit;
|
|
|
|
/* ──────────────────────────────────────────────────────────────
|
|
BOUNDING BOXES por país
|
|
Formato: 'CODE' => [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 );
|