You are here

geoip_language.module in GeoIP API 6

Same filename and directory in other branches
  1. 7 geoip_language/geoip_language.module

Language negotiation based on GeoIP detection.

File

geoip_language/geoip_language.module
View source
<?php

/**
 * @file
 * Language negotiation based on GeoIP detection.
 */

/**
 * Language negotiation option for GeoIP detection.
 */
define('GEOIP_LANGUAGE_NEGOTIATION_PATH', 8);

/**
 * Implementation of hook_help().
 */
function geoip_language_help($path, $arg) {
  switch ($path) {
    case 'admin/settings/language/geoip':
      $help = t('<p>This page provides an overview of your site\'s IP detection and language negotiation settings. You may configure which language is chosen when each country is detected from the user\'s IP, using the form below. All detectable languages are listed in the <em>Detected country</em> drop-down list, and all installed languages are listed in t the <em>Language</em> drop-down list. If a country is detected but doesn\'t have an entry in this list, the default language will be used.</p>');
      return $help;
  }
}

/**
 * Implementation of hook_menu().
 */
function geoip_language_menu() {
  $items = array();
  $items['admin/settings/language/geoip'] = array(
    'title' => 'GeoIP',
    'type' => MENU_LOCAL_TASK,
    'weight' => 5,
    'page callback' => 'geoip_language_settings_overview',
    'access arguments' => array(
      'administer languages',
    ),
    'file' => 'geoip_language.admin.inc',
  );
  $items['admin/settings/language/geoip/delete/%'] = array(
    'title' => 'Delete GeoIP mapping',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'geoip_admin_delete_mapping',
      5,
    ),
    'access arguments' => array(
      'administer languages',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'geoip_language.admin.inc',
  );
  return $items;
}

/**
 * Implementation of hook_init().
 */
function geoip_language_init() {

  // Configured presentation language mode.
  $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
  $drush = function_exists('drush_verify_cli') && drush_verify_cli();

  // Only take action if language negotiation is set to GeoIP and there is more
  // than 1 enabled language.
  if (!$drush && $mode == GEOIP_LANGUAGE_NEGOTIATION_PATH && (int) variable_get('language_count', 1) > 1) {
    geoip_language_negotiation();
  }
}

/**
 * Determine the appropriate language based on path prefix, with fallbacks for
 * user preferred language and GeoIP mapped language.
 */
function geoip_language_negotiation() {

  // Get a list of enabled languages.
  $languages = language_list('enabled');
  $languages = $languages[1];

  // Get the original q, as it is before path un-aliasing.  This is important
  // because a path alias which is the same as a language prefix will have been
  // improperly un-aliased and the language prefix will be lost.
  $query = array();
  parse_str($_SERVER['QUERY_STRING'], $query);
  $q = $query['q'];

  // Mostly copied from language_initialize(). Do a basic path prefix lookup to
  // determine the language.
  $args = explode('/', $q);
  $prefix = array_shift($args);

  // Search prefix within enabled languages.
  foreach ($languages as $language) {
    if (!empty($language->prefix) && $language->prefix == $prefix) {

      // Set the global language object.
      $GLOBALS['language'] = $language;

      // Rebuild $_GET['q'] with the language removed.
      $_GET['q'] = implode('/', $args);

      // Re-initialize the path.
      drupal_init_path();

      // Make PressFlow happy by only storing the language code if there's an
      // existing session.
      if (count($_SESSION)) {
        $_SESSION['geoip_language'] = $language->language;
      }
      return;
    }
  }

  // Begin fallbacks. At this point, we know that there is not a valid path
  // prefix, so we must first determine a language, and then do a redirect to
  // the current path, in that language.
  global $user;

  // First check to see if a language was previously determined.
  if (isset($_SESSION['geoip_language']) && isset($languages[$_SESSION['geoip_language']])) {
    $language = $languages[$_SESSION['geoip_language']];
  }
  elseif ($user->uid && isset($languages[$user->language])) {
    $language = $languages[$user->language];
  }
  else {
    $language = geoip_language_detect_language();
  }

  // Set the global language object.
  $GLOBALS['language'] = $language;
  global $base_path;

  // Abort the redirect if bootstrap happened outside of index.php or if the
  // requested path is inside the files directory
  if ($_SERVER['SCRIPT_NAME'] != $base_path . 'index.php' || strpos($_GET['q'], file_directory_path()) === 0 || $_SERVER['REQUEST_METHOD'] == 'POST') {
    return;
  }

  // Now that the language is detected, do an absolute redirect to avoid page
  // caching in the wrong language.
  $url = url($_GET['q'], array(
    'language' => $language,
    'absolute' => TRUE,
    'query' => drupal_query_string_encode($_GET, array(
      'q',
    )),
  ));
  drupal_goto($url, NULL, NULL, 301);
  exit;
}

/**
 * Implementation of custom_url_rewrite_outbound().
 * language_url_rewrite() doesn't add the path prefix for the default language,
 * so we are doing it here.  This lessens the amount of redirection that
 * would occur because of a missing path prefix.
 */
if (!function_exists('custom_url_rewrite_outbound')) {
  function custom_url_rewrite_outbound(&$path, &$options, $original_path) {
    global $language;
    if (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == GEOIP_LANGUAGE_NEGOTIATION_PATH && (int) variable_get('language_count', 1) > 1) {

      // Only modify relative (insite) URLs.
      if (!$options['external']) {

        // Allow links to bypass the language prefixing.  Helpful for simpletest.
        // Breaking forms as of 2009-06-22
        //        if (isset($options['geoip_language_no_prefix']) && ($options['geoip_language_no_prefix'] === TRUE)) {
        //          return;
        //        }
        // Language can be passed as an option, or we go for current language.
        if (!isset($options['language'])) {
          $options['language'] = $language;
        }
        if (!empty($options['language']->prefix)) {
          $options['prefix'] = $options['language']->prefix . '/';
        }
      }
    }
  }
}

/**
 * Implementation of hook_form_locale_languages_configure_form_alter().
 */
function geoip_language_form_locale_languages_configure_form_alter(&$form, $form_state) {
  $form['language_negotiation']['#options'][GEOIP_LANGUAGE_NEGOTIATION_PATH] = t('Path prefix with GeoIP detection fallback.');
}

/**
 * API function to create a new mapping.
 */
function geoip_language_mapping_create($country, $language) {
  $data = array(
    'country' => $country,
    'language' => $language,
  );
  drupal_write_record('geoip_language', $data);
  geoip_language_mappings(TRUE);
  $countries = geoip_country_values();
  watchdog('geoip_language', 'GeoIP mapping created for %country', array(
    '%country' => $countries[$country],
  ));
  return $data;
}

/**
 * API function to delete a mapping.
 */
function geoip_language_mapping_delete($country) {
  db_query('DELETE FROM {geoip_language} WHERE country="%s"', $country);
  geoip_language_mappings(TRUE);
  $countries = geoip_country_values();
  watchdog('geoip_language', 'GeoIP mapping deleted for %country', array(
    '%country' => $countries[$country],
  ));
}

/**
 * API function to return the country-language mapping.
 *
 * TODO: Serializing to a text file is insane. This should go into the cache
 * table instead.
 */
function geoip_language_mappings($reset = FALSE) {
  static $mapping = NULL;
  if ($reset || !isset($mapping)) {
    $file = file_directory_path() . '/geoip_language.txt';
    if (file_exists($file)) {
      $data = unserialize(file_get_contents($file));
      $mapping = isset($data['geoip']) ? $data['geoip'] : NULL;
    }

    // Build the mapping array and cache it to the filesystem.
    if ($reset || !$mapping) {
      $mapping = array();
      $data = array(
        'geoip' => array(),
        'default' => language_default('prefix'),
      );
      $result = db_query('SELECT g.*, l.prefix FROM {geoip_language} g INNER JOIN {languages} l on g.language = l.language ORDER BY g.country ASC');
      while ($row = db_fetch_object($result)) {
        $mapping[$row->country] = $row->language;
        $data['geoip'][$row->country] = $row;
      }

      // Add default
      $file = file_save_data(serialize($data), 'geoip_language.txt', FILE_EXISTS_REPLACE);
    }
  }
  return $mapping;
}

/**
 * Return the language object mapped to the current GeoIP detected country.
 */
function geoip_language_detect_language($reset = FALSE) {
  static $geoip_language = NULL;
  if ($reset || !isset($geoip_language)) {

    // Get a list of enabled languages.
    $languages = language_list('enabled');
    $languages = $languages[1];

    // Get a list of country->language mappings.
    $mappings = geoip_language_mappings();

    // GeoIP detect the current country.
    $country_code = geoip_country_code();

    // Make sure country_code, the mapping for that country_code, and the enabled
    // language for that mapping, all exist.
    if ($country_code && $mappings[$country_code] && $languages[$mappings[$country_code]->language]) {
      $geoip_language = $languages[$mappings[$country_code]->language];
    }
    else {
      $geoip_language = language_default();
    }
  }
  return $geoip_language;
}

/**
 * Implementation of hook_flush_caches().
 */
function geoip_language_flush_caches() {

  // Reload the geoip cache file.
  geoip_language_mappings(TRUE);
  return array();
}

Functions

Namesort descending Description
geoip_language_detect_language Return the language object mapped to the current GeoIP detected country.
geoip_language_flush_caches Implementation of hook_flush_caches().
geoip_language_form_locale_languages_configure_form_alter Implementation of hook_form_locale_languages_configure_form_alter().
geoip_language_help Implementation of hook_help().
geoip_language_init Implementation of hook_init().
geoip_language_mappings API function to return the country-language mapping.
geoip_language_mapping_create API function to create a new mapping.
geoip_language_mapping_delete API function to delete a mapping.
geoip_language_menu Implementation of hook_menu().
geoip_language_negotiation Determine the appropriate language based on path prefix, with fallbacks for user preferred language and GeoIP mapped language.

Constants

Namesort descending Description
GEOIP_LANGUAGE_NEGOTIATION_PATH Language negotiation option for GeoIP detection.