You are here

ip2country.module in IP-based Determination of a Visitor's Country 7

Same filename and directory in other branches
  1. 8 ip2country.module
  2. 6 ip2country.module

Determination of user's Country based on IP.

This module uses the IP Address that a user is connected from to deduce the Country where the user is located. This method is not foolproof, because a user may connect through an anonymizing proxy, or may be in an unusual case, such as getting service from a neighboring country, or using an IP block leased from a company in another country. Additionally, users accessing a server on a local network may be using an IP that is not assigned to any country (e.g. 192.168.x.x).

Country determination occurs upon user login. If a country can be determined from the IP address, the ISO 3166 2-character country code is stored in the Drupal $user object as $user->country_iso_code_2. If no country can be determined, this member is left unset.

The database used is maintained by ARIN, the American Registry for Internet Numbers (http://www.arin.net/about_us/index.html), which is one of the 5 official Regional Internet Registries (RIR) responsible for assigning IP addresses. The claim is the database is 98% accurate, with most of the problems coming from users in less-developed countries. Regardless, there's no more-authoritative source of this information.

@author Tim Rohaly. <http://drupal.org/user/202830>

File

ip2country.module
View source
<?php

/**
 * @file
 * Determination of user's Country based on IP.
 *
 * This module uses the IP Address that a user is connected from to deduce
 * the Country where the user is located. This method is not foolproof,
 * because a user may connect through an anonymizing proxy, or may be in
 * an unusual case, such as getting service from a neighboring country,
 * or using an IP block leased from a company in another country.
 * Additionally, users accessing a server on a local network may be using
 * an IP that is not assigned to any country (e.g. 192.168.x.x).
 *
 * Country determination occurs upon user login. If a country can be
 * determined from the IP address, the ISO 3166 2-character country code
 * is stored in the Drupal $user object as $user->country_iso_code_2.
 * If no country can be determined, this member is left unset.
 *
 * The database used is maintained by ARIN, the American Registry for
 * Internet Numbers (http://www.arin.net/about_us/index.html), which is
 * one of the 5 official Regional Internet Registries (RIR) responsible
 * for assigning IP addresses. The claim is the database is 98% accurate,
 * with most of the problems coming from users in less-developed countries.
 * Regardless, there's no more-authoritative source of this information.
 *
 * @author Tim Rohaly.    <http://drupal.org/user/202830>
 */

/**
 * Implements hook_help().
 */
function ip2country_help($path = 'admin/help#ip2country', $arg = NULL) {
  switch ($path) {
    case 'admin/help#ip2country':
    case 'admin/help/ip2country':

      //module_load_include('inc', 'ip2country', 'ip2country.help');
      return t('Determines the Country where the user is located based on the IP address used.');
    case 'admin/config/people/ip2country':
      return t('Configuration settings for the ip2country module.');
  }
}

/**
 * Implements hook_permission().
 */
function ip2country_permission() {
  return array(
    'administer ip2country' => array(
      'title' => t('Administer IP lookup settings'),
      'description' => t('Configure IP database and module test settings.'),
    ),
  );
}

/**
 * Implements hook_cron().
 *
 * Updates the IP to Country database automatically on a periodic
 * basis. Default period is 1 week.
 */
function ip2country_cron() {

  // Utility functions for fetching and loading IP/Country DB from RIR.
  module_load_include('inc', 'ip2country');

  // Automatic database updates are disabled when $update_interval == 0.
  $update_interval = variable_get('ip2country_update_interval', 604800);
  if ($update_interval && variable_get('ip2country_last_update', 0) <= REQUEST_TIME - $update_interval) {
    $status = ip2country_update_database(variable_get('ip2country_rir', 'arin'));

    // Log to watchdog if requested.
    if (variable_get('ip2country_watchdog', TRUE)) {
      if ($status != FALSE) {

        // Success.
        watchdog('ip2country', 'Database updated from @registry server. Table contains @rows rows.', array(
          '@registry' => drupal_strtoupper(variable_get('ip2country_rir', 'arin')),
          '@rows' => $status,
        ), WATCHDOG_NOTICE);
      }
      else {

        // Failure.
        watchdog('ip2country', 'Database update from @registry server FAILED.', array(
          '@registry' => drupal_strtoupper(variable_get('ip2country_rir', 'arin')),
        ), WATCHDOG_WARNING);
      }
    }
  }
}

/**
 * Implements hook_menu().
 */
function ip2country_menu() {
  $items = array();
  $items['admin/config/people/ip2country'] = array(
    'title' => 'User location',
    'description' => 'Settings for determining user location from IP address.',
    'access arguments' => array(
      'administer ip2country',
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'ip2country_admin_settings',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'ip2country.admin.inc',
  );
  $items['admin/config/people/ip2country/update'] = array(
    'title' => 'Update database',
    'access arguments' => array(
      'administer ip2country',
    ),
    'page callback' => '_ip2country_update',
    'type' => MENU_CALLBACK,
    'file' => 'ip2country.admin.inc',
  );
  $items['admin/config/people/ip2country/lookup'] = array(
    'title' => 'Lookup IP address in database',
    'access arguments' => array(
      'administer ip2country',
    ),
    'page callback' => '_ip2country_lookup',
    'type' => MENU_CALLBACK,
    'file' => 'ip2country.admin.inc',
  );
  return $items;
}

/**
 * Implements hook_date_formats().
 */
function ip2country_date_formats() {

  // Implementation is kept separate because it only needs to be loaded
  // when module is enabled.
  module_load_include('inc', 'ip2country', 'ip2country.formats');
  return ip2country_get_date_formats();
}

/**
 * Implements hook_date_format_types().
 */
function ip2country_date_format_types() {
  return array(
    'ip2country_date' => t('Date only (ip2country)'),
    'ip2country_time' => t('Time only (ip2country)'),
  );
}

/**
 * Implements hook_user_login().
 *
 * Detects IP and determines country upon user login.
 */
function ip2country_user_login(&$edit, &$account) {

  // Successful login. First determine user's country based on IP.
  $ip = ip_address();
  $country_code = ip2country_get_country($ip);

  // Now check to see if this user has "administer ip2country" permission
  // and if debug mode set. If both are TRUE, use debug information
  // instead of real information.
  if (user_access('administer ip2country') && variable_get('ip2country_debug', FALSE)) {
    $type = variable_get('ip2country_test_type', 0);
    if ($type == 0) {

      // Debug Country entered.
      $country_code = variable_get('ip2country_test_country', 'US');
    }
    else {

      // Debug IP entered.
      $ip = variable_get('ip2country_test_ip_address', $ip);
      $country_code = ip2country_get_country($ip);
    }
    drupal_set_message(t('Using DEBUG value for Country - @country', array(
      '@country' => $country_code,
    )));
  }

  // Finally, save country, if it has been determined.
  if ($country_code) {

    // Store the ISO country code in the $user object.
    user_save($account, array(
      'country_iso_code_2' => $country_code,
    ));
  }
}

/**
 * Implements hook_user_presave().
 *
 * Takes care of storing country data into {users}.data.
 */
function ip2country_user_presave(&$edit, $account, $category) {
  if (isset($edit['country_iso_code_2'])) {
    $edit['data']['country_iso_code_2'] = $edit['country_iso_code_2'];
  }
}

/**
 * Implements hook_user_load().
 *
 * Takes care of restoring country data from {users}.data.
 */
function ip2country_user_load($users) {
  foreach ($users as $user) {
    if (isset($user->data['country_iso_code_2'])) {
      $users[$user->uid]->country_iso_code_2 = $user->data['country_iso_code_2'];
    }
  }
}

/**
 * Gets the ISO 3166 2-character country code from the IP address.
 *
 * @param string|int $ip_address
 *   IP address either as a dotted quad string (e.g. "127.0.0.1") or
 *   as a 32-bit unsigned long integer.
 *
 * @return string|false
 *   ISO 3166-1 2-character country code for this IP address, or
 *   FALSE if the lookup failed to find a country.
 */
function ip2country_get_country($ip_address) {
  $ipl = ip2long($ip_address);
  if (is_int($ip_address)) {
    $ipl = $ip_address;
  }

  // Locate IP within range.
  $sql = "SELECT country FROM {ip2country}\n             WHERE (:start >= ip_range_first AND :end <= ip_range_last) LIMIT 1";
  $result = db_query($sql, array(
    ':start' => $ipl,
    ':end' => $ipl,
  ))
    ->fetchField();
  return $result;
}

/**
 * Gets the total count of IP ranges in database.
 */
function ip2country_get_count() {
  $sql = "SELECT COUNT(*) FROM {ip2country}";
  $count = db_query($sql)
    ->fetchField();
  return (int) $count;
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters Ubercart's uc_cart_checkout_form() to use ip2country's country
 * determination as the default billing and delivery country. If the user's
 * country hasn't been determined, the store country will be used instead.
 */
function ip2country_form_uc_cart_checkout_form_alter(&$form, &$form_state) {
  global $user;
  if (isset($user->data['country_iso_code_2'])) {
    if (module_exists('uc_store')) {
      $country_id = db_query('SELECT country_id from {uc_countries} WHERE country_iso_code_2 = :iso2', array(
        ':iso2' => $user->data['country_iso_code_2'],
      ))
        ->fetchField();
      if (!$country_id) {
        $country_id = uc_store_default_country();
      }
      $form['panes']['billing']['billing_country']['#default_value'] = $form['panes']['delivery']['delivery_country']['#default_value'] = $country_id;
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alters Ubercart's uc_cart_pane_quotes() form to use ip2country's country
 * determination as the default billing and delivery country. If the user's
 * country hasn't been determined, the store country will be used instead.
 */
function ip2country_form_uc_cart_pane_quotes_alter(&$form, &$form_state) {
  global $user;
  if (isset($user->data['country_iso_code_2'])) {
    if (module_exists('uc_store')) {
      $country_id = db_query('SELECT country_id from {uc_countries} WHERE country_iso_code_2 = :iso2', array(
        ':iso2' => $user->data['country_iso_code_2'],
      ))
        ->fetchField();
      if (!$country_id) {
        $country_id = uc_store_default_country();
      }
      $form['delivery_country']['#default_value'] = $country_id;
    }
  }
}