You are here

uc_ip2country.module in IP-based Determination of a Visitor's Country 5

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. Additionaly, 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-authoritive source of this information.

@author Tim Rohaly.

File

uc_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.
 * Additionaly, 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-authoritive source of this information.
 *
 * @author Tim Rohaly.
 */

/** Utility functions for loading IP/Country DB from external sources */
include_once drupal_get_path('module', 'uc_ip2country') . '/uc_ip2country.inc';

/******************************************************************************
 * Drupal Hooks                                                               *
 ******************************************************************************/

/**
 * Implementation of hook_help().
 */
function uc_ip2country_help($section = 'admin/help#uc_ip2country') {
  switch ($section) {
    case 'admin/help#uc_ip2country':
      return t('Detects Country of user based on IP address.');
      break;
    case 'admin/settings/ip2country':
      return t('Configuration settings for the ip2country module.');
      break;
  }
}

/**
 * Implementation of hook_perm().
 */
function uc_ip2country_perm() {
  return array(
    'administer ip2country',
  );
}

/**
 * Implementation of hook_cron().
 *
 * Updates the IP to Country database automatically on a periodic
 * basis.  Default period is 1 week.
 */
function uc_ip2country_cron() {
  if (variable_get('ip2country_last_update', 0) <= time() - variable_get('ip2country_update_interval', 604800)) {
    ip2country_update_database(variable_get('ip2country_rir', 'arin'));
    variable_set('ip2country_last_update', time());
    if (variable_get('ip2country_watchdog', 1)) {
      watchdog('ip2country', t('Database updated from @registry server.', array(
        '@registry' => drupal_strtoupper(variable_get('ip2country_rir', 'arin')),
      )), WATCHDOG_NOTICE);
    }
  }
}

/**
 * Implementation of hook_menu().
 *
 * Called when Drupal is building menus.  Cache parameter lets module know
 * if Drupal intends to cache menu or not - different results may be
 * returned for either case.
 *
 * @param $may_cache
 *   TRUE when Drupal is building menus it will cache
 *
 * @return
 *   An array with the menu path, callback, and parameters.
 */
function uc_ip2country_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'admin/settings/ip2country',
      'title' => t('IP to Country Settings'),
      'description' => t('Configure the IP/Country settings'),
      'access' => user_access('administer ip2country'),
      'callback' => 'drupal_get_form',
      'callback arguments' => 'uc_ip2country_admin_settings',
      'type' => MENU_NORMAL_ITEM,
    );
    $items[] = array(
      'path' => 'admin/settings/ip2country/update',
      'title' => t('Update Database'),
      'access' => user_access('administer ip2country'),
      'callback' => '_ip2country_update',
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'admin/settings/ip2country/lookup',
      'title' => t('Lookup IP Address in Database'),
      'access' => user_access('administer ip2country'),
      'callback' => '_ip2country_lookup',
      'type' => MENU_CALLBACK,
    );
  }
  return $items;
}

/**
 * Implementation of hook_user().
 *
 * Detects IP and determines country upon user login.
 */
function uc_ip2country_user($op, &$edit, &$account, $category = NULL) {
  switch ($op) {
    case 'login':

      // Successful login. First determine user's country based on IP
      $ip = $_SERVER['REMOTE_ADDR'];
      $country_code = uc_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 = db_result(db_query("SELECT country_iso_code_2 FROM {uc_countries} WHERE country_id = %d", variable_get('ip2country_test_country', 'US')));
        }
        else {

          // Debug IP entered
          $ip = variable_get('ip2country_test_ip', $ip);
          $country_code = uc_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,
        ));
      }
      break;
  }
}

/******************************************************************************
 * Menu Callbacks                                                             *
 ******************************************************************************/

/**
 * AJAX callback to update the IP to Country database.
 *
 * @param $rir
 *   String with name of IP registry.  One of afrinic, arin, lacnic, ripe
 *
 * @return
 *   JSON object for display by jQuery script
 */
function _ip2country_update($rir) {
  ip2country_update_database($rir);
  if (variable_get('ip2country_watchdog', 1)) {
    watchdog('ip2country', t('Database updated from @registry server.', array(
      '@registry' => drupal_strtoupper($rir),
    )), WATCHDOG_NOTICE);
  }
  print drupal_to_js(array(
    'count' => t('@rows rows affected.', array(
      '@rows' => uc_ip2country_get_count(),
    )),
    'server' => $rir,
    'message' => t('The IP to Country database has been updated from @server.', array(
      '@server' => drupal_strtoupper($rir),
    )),
  ));
  exit;
}

/**
 * AJAX callback to lookup an IP address in the database.
 *
 * @param $arg
 *   String with IP address
 *
 * @return
 *   JSON object for display by jQuery script
 */
function _ip2country_lookup($arg) {

  // Return results of manual lookup
  $country = uc_ip2country_get_country($arg);
  if ($country) {
    print drupal_to_js(array(
      'message' => t('IP Address @ip is assigned to @country.', array(
        '@ip' => $arg,
        '@country' => $country,
      )),
    ));
  }
  else {
    print drupal_to_js(array(
      'message' => t('IP Address @ip is not assigned to a country.', array(
        '@ip' => $arg,
      )),
    ));
  }
  exit;
}

/**
 * Default IP to Country administration settings.
 *
 * @return
 *   Forms for store administrator to set configuration options.
 */
function uc_ip2country_admin_settings() {
  uc_add_js(drupal_get_path('module', 'uc_ip2country') . '/uc_ip2country.js');
  drupal_add_css(drupal_get_path('module', 'uc_ip2country') . '/uc_ip2country.css');

  /* Container for database update preference forms */
  $form['ip2country_database_update'] = array(
    '#type' => 'fieldset',
    '#title' => t('Database Updates'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );

  /* Form to enable watchdog logging of updates */
  $form['ip2country_database_update']['ip2country_watchdog'] = array(
    '#type' => 'checkbox',
    '#title' => t('Log database updates to watchdog'),
    '#default_value' => variable_get('ip2country_watchdog', 1),
  );

  /* Form to choose RIR */
  $form['ip2country_database_update']['ip2country_rir'] = array(
    '#type' => 'select',
    '#title' => t('Regional Internet Registry to use'),
    '#options' => array(
      'afrinic' => 'AFRINIC',
      'apnic' => 'APNIC',
      'arin' => 'ARIN',
      'lacnic' => 'LACNIC',
      'ripe' => 'RIPE',
    ),
    '#default_value' => variable_get('ip2country_rir', 'arin'),
    '#description' => t('You may find that the regional server nearest you has the best response time, but note that AFRINIC provides only its own subset of registration data.'),
  );
  $period = drupal_map_assoc(array(
    86400,
    302400,
    604800,
    1209600,
    2419200,
  ), 'format_interval');

  /* Form to set automatic update interval */
  $form['ip2country_database_update']['ip2country_update_interval'] = array(
    '#type' => 'select',
    '#title' => t('Database update frequency'),
    '#default_value' => variable_get('ip2country_update_interval', 604800),
    '#options' => $period,
    '#description' => t('Database will be automatically updated via cron.php.  Cron must be enabled for this to work.  Default period is 1 week (604800 seconds).'),
  );
  $update_time = variable_get('ip2country_last_update', 0);

  /* Form for manual updating of the IP-Country database */
  $form['ip2country_database_update']['ip2country_update_database'] = array(
    '#type' => 'button',
    '#value' => t('Update'),
    '#prefix' => '<div>' . t('The IP to Country Database may be updated manually by pressing the Update button below.  Note, this may take several minutes.') . '</div>',
    '#suffix' => '<span id="dbthrobber" class="message">' . t('Database last updated on ') . format_date($update_time, 'custom', 'n/j/Y') . ' at ' . format_date($update_time, 'custom', 'H:i:s T') . '</span>',
  );

  /* Container for manual lookup */
  $form['ip2country_manual_lookup'] = array(
    '#type' => 'fieldset',
    '#title' => t('Manual Lookup'),
    '#description' => t('Examine database values'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );

  /* Form for manual lookups */
  $form['ip2country_manual_lookup']['ip2country_lookup'] = array(
    '#type' => 'textfield',
    '#title' => t('Manual Lookup'),
    '#description' => t('Enter IP address'),
  );

  /* Form for manual updating of the IP-Country database */
  $form['ip2country_manual_lookup']['ip2country_lookup_button'] = array(
    '#type' => 'button',
    '#value' => t('Lookup'),
    '#prefix' => '<div>' . t('An IP address may be looked up in the database by entering the address above then pressing the Lookup button below.') . '</div>',
    '#suffix' => '<span id="lookup-message" class="message"></span>',
  );

  /* Container for debugging preference forms */
  $form['ip2country_debug_preferences'] = array(
    '#type' => 'fieldset',
    '#title' => t('Debug Preferences'),
    '#description' => t('Set debugging values'),
    '#collapsible' => FALSE,
    '#collapsed' => FALSE,
  );

  /* Form to turn on debugging */
  $form['ip2country_debug_preferences']['ip2country_debug'] = array(
    '#type' => 'checkbox',
    '#title' => t('Admin Debug'),
    '#default_value' => variable_get('ip2country_debug', FALSE),
    '#description' => t('Allows admin to specify an IP Address or Country to use for location-based currency.'),
  );

  /* Form to select Dummy Country or Dummy IP Address for testing */
  $form['ip2country_debug_preferences']['ip2country_test_type'] = array(
    '#type' => 'radios',
    '#title' => t('Bypass IP/Country check for administrator and manually enter'),
    '#default_value' => variable_get('ip2country_test_type', 0),
    '#options' => array(
      t('Country'),
      t('IP Address'),
    ),
  );
  $dd = variable_get('ip2country_test_country', uc_ip2country_get_country($_SERVER['REMOTE_ADDR']));
  $form['ip2country_debug_preferences']['ip2country_test_country'] = uc_country_select(t('Dummy Country to use for testing'), $dd, NULL, 'name', FALSE);
  $form['ip2country_debug_preferences']['ip2country_test_ip_address'] = array(
    '#type' => 'textfield',
    '#title' => t('Dummy IP to use for testing'),
    '#default_value' => variable_get('ip2country_test_ip_address', $_SERVER['REMOTE_ADDR']),
  );
  return system_settings_form($form);
}

/**
 * Process Forms submitted by IP to Country administration page
 */
function uc_ip2country_admin_settings_submit($form_id, $form_values) {
  global $user;

  // Check to see if debug set
  if ($form_values['ip2country_debug']) {

    // Debug on
    if ($form_values['ip2country_test_type']) {

      // Dummy IP Address
      $ip = $form_values['ip2country_test_ip_address'];
      $country_code = uc_ip2country_get_country($ip);
    }
    else {

      // Dummy Country
      $country_code = db_result(db_query("SELECT country_iso_code_2 FROM {uc_countries} WHERE country_id = %d", $form_values['ip2country_test_country']));
    }
    drupal_set_message(t('Using DEBUG value for Country - @country', array(
      '@country' => $country_code,
    )));
  }
  else {

    // Debug off - make sure we set/reset IP/Country to their real values
    $ip = $_SERVER['REMOTE_ADDR'];
    $country_code = uc_ip2country_get_country($ip);
    drupal_set_message(t('Using ACTUAL 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($user, array(
      'country_iso_code_2' => $country_code,
    ));
  }
  system_settings_form_submit($form_id, $form_values);
}

/******************************************************************************
 * Module Functions                                                           *
 ******************************************************************************/

/**
 * Get the Country Code from the IP address
 *
 * @return
 *   FALSE if the lookup failed to find a country for this IP
 */
function uc_ip2country_get_country($ip_address) {
  $ipl = ip2long($ip_address);
  if (is_long($ip_address)) {
    $ipl = ip_address;
  }

  // Locate IP within ranges
  $sql = "SELECT country FROM {ip2country}\n             WHERE (%d >= `ip_range_first` AND %d <= `ip_range_last`) LIMIT 1";
  $result = db_result(db_query($sql, $ipl, $ipl));
  return $result;
}

/**
 * Get the total count of IP ranges in database
 */
function uc_ip2country_get_count() {
  $sql = "SELECT COUNT(*) FROM {ip2country}";
  $count = db_result(db_query($sql));
  return (int) $count;
}

Functions

Namesort descending Description
uc_ip2country_admin_settings Default IP to Country administration settings.
uc_ip2country_admin_settings_submit Process Forms submitted by IP to Country administration page
uc_ip2country_cron Implementation of hook_cron().
uc_ip2country_get_count Get the total count of IP ranges in database
uc_ip2country_get_country Get the Country Code from the IP address
uc_ip2country_help Implementation of hook_help().
uc_ip2country_menu Implementation of hook_menu().
uc_ip2country_perm Implementation of hook_perm().
uc_ip2country_user Implementation of hook_user().
_ip2country_lookup AJAX callback to lookup an IP address in the database.
_ip2country_update AJAX callback to update the IP to Country database.