Show the Distance in Miles for US based Visitors and in Kilometers for Everyone Else

If your store locations are spread around the world, and you’d like US-based visitors to see the distance in miles instead of kilometers, follow the directions in this article.

All code used throughout this article needs to go in the functions.php inside your active theme folder.

  1. Determine the IP of the current visitor
  2. Find the country the IP is located in
  3. Set the correct distance unit based on the user’s country

Determine the IP of the current visitor

The first part of the code looks up the IP of the current visitor. It’s needed to find the country the visitor is located in through an IP geolocation API.

/**
* Get the IP of the current visitor.
*
* @return string The IP, or an empty string if no valid IP is found.
*/
function custom_get_user_ip() {
$headers = [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' ];
foreach ( $headers as $header ) {
if ( empty( $_SERVER[ $header ] ) ) {
continue;
}
// X-Forwarded-For can hold a list of IPs, the first one is the visitor.
$ips = array_map( 'trim', explode( ',', wp_unslash( $_SERVER[ $header ] ) ) );
if ( filter_var( $ips[0], FILTER_VALIDATE_IP ) ) {
return $ips[0];
}
}
return '';
}
view rawwpsl-user-ip.php hosted with ❤ by GitHub

Find the country the IP is located in

In the next part the IP is sent to a service called ipstack to find the country it’s from. To use this service you need an API key. The free plan is meant for testing, so check their plans for a live site. Add your key between the quotes of $api_key = ''; in the code below.

If your site runs behind Cloudflare with IP geolocation enabled, you don’t need an API key: Cloudflare adds the visitor’s country to every request, and the code uses that instead.

Caching

To prevent making lots of requests for the same IP, the returned country code is cached for 30 minutes. To change this, change the $cache_expires value. It’s in seconds, so 1800 is 30 minutes. A failed lookup is cached for 5 minutes, so the API isn’t called on every page load when it’s down.

/**
* Get the country code of the current visitor.
*
* @return string The country code, for example 'US', or an empty string.
*/
function custom_get_user_country() {
// Cloudflare can add the visitor's country itself, then no API request is needed.
if ( ! empty( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) {
return strtoupper( sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_IPCOUNTRY'] ) ) );
}
// The API key from https://ipstack.com/.
$api_key = '';
// How long the country is cached, in seconds ( 1800 = 30 minutes ).
$cache_expires = 1800;
$user_ip = custom_get_user_ip();
// Uncomment the line below to test it with a US based IP.
// $user_ip = '54.239.17.6';
if ( ! $user_ip || ! $api_key ) {
return '';
}
$transient_name = 'custom_user_country_' . md5( $user_ip );
$user_country = get_transient( $transient_name );
if ( false === $user_country ) {
$user_country = '';
$api_url = add_query_arg(
[
'access_key' => rawurlencode( $api_key ),
'fields' => 'country_code',
],
'http://api.ipstack.com/' . rawurlencode( $user_ip )
);
$response = wp_remote_get( $api_url );
if ( ! is_wp_error( $response ) ) {
$user_location = json_decode( wp_remote_retrieve_body( $response ) );
if ( ! empty( $user_location->country_code ) ) {
$user_country = $user_location->country_code;
}
}
// Cache a failed lookup for 5 minutes, so the API isn't called on every page load.
set_transient( $transient_name, $user_country, $user_country ? $cache_expires : 300 );
}
return $user_country;
}

Set the correct distance unit based on the user’s country

The last part uses miles for visitors from the US, and kilometers for everyone else. When the country can’t be found, the distance unit from the settings page is used. The unit is used in the search radius dropdown, the search results, and the calculation of the distance between the searched location and the stores.

add_filter( 'wpsl_distance_unit', 'custom_distance_unit' );
/**
* Use miles for visitors from the US, and kilometers for everyone else.
*
* @param string $distance_unit The distance unit from the settings.
* @return string Either 'mi' or 'km'.
*/
function custom_distance_unit( $distance_unit ) {
$user_country = custom_get_user_country();
// Keep the setting when the country is unknown.
if ( ! $user_country ) {
return $distance_unit;
}
return ( 'US' === $user_country ) ? 'mi' : 'km';
}

The filter also overrides the distance_unit attribute on the wpsl shortcode.

If you use a caching plugin, or your host caches full pages, every visitor gets the page with the distance unit of the visitor who was first after the cache was cleared. Exclude the store locator page from the page cache, so each visitor gets their own distance unit.