'https://api.ip2location.io/?ip=%s', 'ipinfo.io' => 'https://ipinfo.io/%s/json' ); private static $hash_ip_prefix = '#atk#'; public static function geolocate_ip( $ip_address = '', $allow_remote = true ){ $ip = sanitize_text_field( wp_unslash( $ip_address ) ); if( empty($ip) || !self::clean_ip($ip) ) { return; } if ( ! $allow_remote ) { $cached = get_transient( 'atkai_geoiploc_' . $ip ); return is_array( $cached ) ? $cached : ''; } return self::geolocate_via_api( $ip ); } public static function readableIP( $ip = '' ) { // 1) Reverse any slashes and sanitize $ip = sanitize_text_field( wp_unslash( $ip ) ); // 2) Handle loopback names first if ( in_array( $ip, [ 'localhost', '127.0.0.1', '::1' ], true ) ) { return 'localhost'; } // 3) If this isn’t a valid IP *or* your “hashed” placeholder, bail if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) && ! self::is_hash_ip( $ip ) ) { return ''; // or return 'INVALID'; } // 4) IPv6? if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) { return 'IPV6'; } // 5) IPv4? if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) { return 'IPV4'; } // 6) “Hash” format? if ( self::is_hash_ip( $ip ) ) { return 'HASH'; } // 7) Fallback — shouldn’t happen, but just in case return $ip; } private static function isValidIP( $ip ) { // 1) Remove slashes and strip any unexpected characters $ip = sanitize_text_field( wp_unslash( $ip ) ); // 2) Validate as IPv4 only, excluding private & reserved ranges $flags = FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE; if ( false === filter_var( $ip, FILTER_VALIDATE_IP, [ 'flags' => $flags ] ) ) { return false; } // 3) Exclude localhost loopbacks if ( in_array( $ip, [ '127.0.0.1', '::1' ], true ) ) { return false; } return true; } private static function geolocate_via_api( $ip_address ) { $country_info = get_transient( 'atkai_geoiploc_' . $ip_address ); if ( false === $country_info ) { $country_info = self::get_ip_location( $ip_address ); if ( empty($country_info) || false === $country_info || is_null($country_info) ) { $geoip_services = array_filter( apply_filters( 'atkai_geolocation_geoip_api', self::$geoip_apis ), static function( $endpoint ) { return is_string( $endpoint ) && 0 === stripos( $endpoint, 'https://' ); } ); if ( empty( $geoip_services ) ) { return ''; } $geoip_services_keys = array_keys( $geoip_services ); shuffle( $geoip_services_keys ); $country_code = ''; $country_name = ''; foreach ( $geoip_services_keys as $service_name ) { $country_code = ''; $country_name = ''; $service_endpoint = $geoip_services[ $service_name ]; $response = wp_safe_remote_get( sprintf( $service_endpoint, $ip_address ), array( 'timeout' => 2, 'user-agent' => 'WordPress/' . get_bloginfo( 'version' ), 'atkai_internal_request' => true, ) ); if ( ! is_wp_error( $response ) && $response['body'] ) { switch ( $service_name ) { case 'ip2location.io': $data = json_decode( $response['body'] ); $country_code = isset( $data->country_code ) ? $data->country_code : ''; $country_name = isset( $data->country_name ) ? $data->country_name : ''; break; case 'ipinfo.io': $data = json_decode( $response['body'] ); $country_code = isset( $data->country ) ? $data->country : ''; $country_name = isset( $data->country ) ? $data->country : ''; break; case 'ip-api.com': $data = json_decode( $response['body'] ); $country_code = isset( $data->countryCode ) ? $data->countryCode : ''; $country_name = isset( $data->country ) ? $data->country : ''; break; default: $country_info = apply_filters( 'atkai_geolocation_geoip_response_' . $service_name, '', $response['body'] ); break; } $country_code = sanitize_text_field( strtolower( $country_code ) ); $country_name = sanitize_text_field( strtoupper( $country_name ) ); if ( $country_code ) { break; } } } $country_info = array( 'country' => $country_name, 'country_code' => $country_code ); set_transient( 'atkai_geoiploc_' . $ip_address, $country_info, $country_code ? WEEK_IN_SECONDS : 5 * MINUTE_IN_SECONDS ); } } return $country_info; } private static function clean_ip( $ip ) { // 1) Reverse any slashes and strip unwanted characters $ip = sanitize_text_field( wp_unslash( $ip ) ); // 2) Remove any “unless” suffix (some servers append extra info) if ( false !== stripos( $ip, ' unless ' ) ) { $parts = preg_split( '/\s+unless\s+/i', $ip ); $ip = $parts[0]; } $ip = strtolower( trim( $ip ) ); // 3) Strip off port from IPv4 addresses (e.g. “1.2.3.4:5678”) if ( preg_match( '/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/', $ip, $m ) ) { $ip = $m[1]; } // 4) Strip brackets/port from IPv6 (e.g. “[::1]:8080”) if ( preg_match( '/^\[([0-9a-f:]+)\](?::\d+)?$/i', $ip, $m ) ) { $ip = $m[1]; } // 5) Convert IPv4‐in‐IPv6 format (::ffff:1.2.3.4) if ( preg_match( '/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i', $ip, $m ) ) { $ip = $m[1]; } // 6) If this is a “hashed” placeholder-style IP, skip it if ( self::is_hash_ip( $ip ) ) { return false; } // 7) Final validation: must be a real IP (v4 or v6) return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 ) ? $ip : false; } private static function get_ip_location( $ip ) { global $wpdb; // 1) Clean up & validate $ip = sanitize_text_field( wp_unslash( $ip ) ); if ( ! self::isValidIP( $ip ) ) { return null; } // 2) Build a cache key & try to read from object cache $group = 'atk_ip_geolocate'; $cache_key = 'loc_' . md5( $ip ); $cached = wp_cache_get( $cache_key, $group ); if ( false !== $cached ) { return $cached; // already decoded array } // 3) Table name $table = esc_sql( $wpdb->prefix . 'atk_ai_logs' ); // 4) Direct DB lookup (necessary for custom table) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $row = $wpdb->get_row( $wpdb->prepare( "SELECT ip_location FROM %i WHERE user_ip_address = %s AND ip_location <> '' ORDER BY log_id DESC LIMIT 1", $table, $ip ) ); // 5) Decode, cache, and return if ( $row && ! empty( $row->ip_location ) ) { $json = wp_unslash( $row->ip_location ); $data = json_decode( $json, true ); // cache for 1 hour (or choose your TTL) wp_cache_set( $cache_key, $data, $group, HOUR_IN_SECONDS ); return $data; } // 6) Nothing found return null; } private static function is_hash_ip($ip) { // Check if the IP starts with the hash prefix return (substr($ip, 0, strlen(self::$hash_ip_prefix)) == self::$hash_ip_prefix); } }