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.moduleView 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
Name | 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. |