Hello!

We recently had a requirement from a client to generate content on a page specifically based on the geolocation coordinates of the visitor’s IP address. Now this sort of mechanism isn’t totally new, however we decided to develop a WordPress plugin called Shift8 GeoIP that would obtain this information and set it into an encrypted cookie.

The reason why we wanted a plugin to set your coordinates in a cookie was because the mechanism to obtain the geolocation coordinates would never need to change. We could then develop the custom content generation (i.e. finding a “store” nearest to your location) could be done directly in the WordPress theme for the particular page in question.

Furthermore, we decided to ensure that the cookie data was encrypted using OpenSSL in PHP to ensure that this geolocation data could not be obtained by third parties by simply reading the cookie data.

Below I’ll go into some of the interesting components of the Shift8 GeoIP plugin into further detail to demonstrate how it works.

Use PHP to get a visitor’s IP address regardless of your environment

Due to the variety of hosting environments, obtaining the visitor’s true public IP address may require reading different host headers. There are approximately 7 headers that may contain this information : HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR, HTTP_X_CLUSTER_CLIENT_IP, HTTP_FORWARDED_FOR, HTTP_FORWARDED and REMOTE_ADDR.

The trick is to check for those headers in the above order. Once a value is obtained along the order of headers, it can be stored or returned. In our case we wrote a PHP function to process this information :

function shift8_geoip_get_ip() { $ip_keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'); foreach ($ip_keys as $key) { if (array_key_exists($key, $_SERVER) === true) { foreach (explode(',', $_SERVER[$key]) as $ip) { // trim for safety measures $ip = trim($ip); // attempt to validate IP if (shift8_geoip_validate_ip($ip)) { return $ip; } } } } return false; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function shift8_geoip_get_ip ( ) { $ ip_keys = array ( 'HTTP_CLIENT_IP' , 'HTTP_X_FORWARDED_FOR' , 'HTTP_X_FORWARDED' , 'HTTP_X_CLUSTER_CLIENT_IP' , 'HTTP_FORWARDED_FOR' , 'HTTP_FORWARDED' , 'REMOTE_ADDR' ) ; foreach ( $ ip_keys as $ key ) { if ( array_key_exists ( $ key , $ _SERVER ) === true ) { foreach ( explode ( ',' , $ _SERVER [ $ key ] ) as $ ip ) { // trim for safety measures $ ip = trim ( $ ip ) ; // attempt to validate IP if ( shift8_geoip_validate_ip ( $ ip ) ) { return $ ip ; } } } } return false ; }

You can see that we assign the header names to an array and then just loop through the array, checking if a value exists. If it does then we validate the IP first before returning it, ending the foreach loop.

How to check if an IP address is valid in PHP

As mentioned above, we want to validate the IP address before returning it in the shift8_geoip_get_ip function. This is because for some of those host headers, an IP might actually be present but the IP might be a local address of a load balancer, reverse proxy or cache server (such as Varnish). We want to leverage PHP’s filter_var function. We will wrap that function in another function that will essentially return true/false if its a valid IP address :

function shift8_geoip_validate_ip($ip) { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) { return false; } return true; } 1 2 3 4 5 6 function shift8_geoip_validate_ip ( $ ip ) { if ( filter_var ( $ ip , FILTER_VALIDATE_IP , FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) === false ) { return false ; } return true ; }

As you can see from the above snippet, we are running the IP address through filter_var and checking to make sure its a valid IPv4 IP, not within the range of private IP addresses or reserved IP addresses. This will increase the likelihood that the IP address we have is indeed a public IP of the end-user.

Use IP-API to pull the geolocation coordinates from an IP address in PHP

Now to the actual interesting part, right? In our Shift8 GeoIP plugin, we created a class called “SHIFT8_GEOIP_IPAPI” to interact with the IP-API service. This service is free (up to a threshold) and returns a JSON response based on the IP address provided to it. For the remote CURL query, we are using the built-in WordPress function wp_remote_get.

class SHIFT8_GEOIP_IPAPI { static $fields = 65535; // refer to http://ip-api.com/docs/api:returned_values#field_generator static $api = "http://ip-api.com/php/"; public $status, $country, $countryCode, $region, $regionName, $city, $zip, $lat, $lon, $timezone, $isp, $org, $as, $reverse, $query, $message; public static function query($q) { try { $data = self::communicate($q); $result = new static; foreach($data as $key => $val) { $result->$key = $val; } return $result; } catch (Exception $e) { return null; } } private function communicate($q) { $result_array = wp_remote_get( self::$api.$q.'?fields='.self::$fields, array( 'httpversion' => '1.1', 'timeout' => 30, ) ); if (is_array($result_array)) { return unserialize($result_array['body']); } else { return null; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class SHIFT8_GEOIP_IPAPI { static $ fields = 65535 ; // refer to http://ip-api.com/docs/api:returned_values#field_generator static $ api = "http://ip-api.com/php/" ; public $ status , $ country , $ countryCode , $ region , $ regionName , $ city , $ zip , $ lat , $ lon , $ timezone , $ isp , $ org , $ as , $ reverse , $ query , $ message ; public static function query ( $ q ) { try { $ data = self :: communicate ( $ q ) ; $ result = new static ; foreach ( $ data as $ key =& gt ; $ val ) { $ result - & gt ; $ key = $ val ; } return $ result ; } catch ( Exception $ e ) { return null ; } } private function communicate ( $ q ) { $ result_array = wp_remote_get ( self :: $ api . $ q . '?fields=' . self :: $ fields , array ( 'httpversion' =& gt ; '1.1' , 'timeout' =& gt ; 30 , ) ) ; if ( is_array ( $ result_array ) ) { return unserialize ( $ result_array [ 'body' ] ) ; } else { return null ; } } }

You can see in the above class we are simply submitting the IP address as a query and parsing the results that are returned. We can then interact with the class with the following code :

if ($ip_address) { $query = SHIFT8_GEOIP_IPAPI::query($ip_address); } 1 2 3 if ( $ ip_address ) { $ query = SHIFT8_GEOIP_IPAPI :: query ( $ ip_address ) ; }

Encrypt your geolocation data in a cookie to use elsewhere in WordPress

The final part of this process is for us to store the geolocation coordinates in an encrypted cookie. We will be using openssl to encrypt the data :

$encryption_key = wp_salt('auth'); $cookie_data = shift8_geoip_encrypt($encryption_key, $ip_address . '_' . $query->lat . '_' . $query->lon . '_' . $query->countryCode); 1 2 $ encryption_key = wp_salt ( 'auth' ) ; $ cookie_data = shift8_geoip_encrypt ( $ encryption_key , $ ip _ address . '_' . $ query - & gt ; lat . '_' . $ query - & gt ; lon . '_' . $ query - & gt ; countryCode ) ;

It should be noted that we are using the salt thats defined in your wp-config.php installation as the key. This means that it would be relatively straightforward to decrypt the data elsewhere in your WordPress installation (such as in your theme’s functions.php) without having to worry about setting and/or re-defining a key elsewhere.The above snippet triggers the function shift8_geoip_encrypt, which you can see below :

function shift8_geoip_encrypt($key, $payload) { if (!empty($key) && !empty($payload)) { $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc')); $encrypted = openssl_encrypt($payload, 'aes-256-cbc', $key, 0, $iv); return base64_encode($encrypted . '::' . $iv); } else { return false; } } 1 2 3 4 5 6 7 8 9 function shift8_geoip_encrypt ( $ key , $ payload ) { if ( ! empty ( $ key ) & amp ; & amp ; ! empty ( $ payload ) ) { $ iv = openssl_random_pseudo_bytes ( openssl_cipher_iv_length ( 'aes-256-cbc' ) ) ; $ encrypted = openssl_encrypt ( $ payload , 'aes-256-cbc' , $ key , 0 , $ iv ) ; return base64_encode ( $ encrypted . '::' . $ iv ) ; } else { return false ; } }

This is all you would need to encrypt the geolocation data in your cookie. Now all you have to do is actually set the cookie with the data prepared :

setcookie('shift8_geoip', $cookie_data, strtotime('+1 day'), '/'); 1 setcookie ( 'shift8_geoip' , $ cookie_data , strtotime ( '+1 day' ) , '/' ) ;

If you wanted to pull the cookie data and use it elsewhere in your WordPress installation , you could use something similar to the snippet below :

$cookie_data = explode('_', shift8_geoip_decrypt(wp_salt('auth'), $_COOKIE['shift8_geoip'])); 1 $ cookie_data = explode ( '_' , shift8_geoip_decrypt ( wp_salt ( 'auth' ) , $ _COOKIE [ 'shift8_geoip' ] ) ) ;

That’s about it! Hopefully you find it useful to generate geolocation coordinates from your user ip addresses 🙂