Issue #948164 by catch: Fixed Unused session variable.
[project/i18n.git] / i18n.module
index dd3dd2a..a64b9dd 100644 (file)
 <?php
-// $Id$
 
 /**
- * Internationalization (i18n) module
+ * @file
+ * Internationalization (i18n) module.
  *
- * @author Jose A. Reyero, 2004
+ * This module extends multilingual support being the base module for the i18n package.
+ * - Multilingual variables
+ * - Extended languages for nodes
+ * - Extended language API
  *
+ * @author Jose A. Reyero, 2004
  */
 
+// Some constants. Language support modes for content
+define('LANGUAGE_SUPPORT_NONE', 0);
+define('LANGUAGE_SUPPORT_NORMAL', 1);
+define('LANGUAGE_SUPPORT_EXTENDED', 2);
+define('LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED', 3);
+
 /**
- * Module initialization
+ * Implementation of hook_boot()
  * 
- * Get language from path if exists and Initialize i18n system
- * May do a redirect from home page for not to get wrong versions in cache
+ * Initialize variables, that will be used to decide on site_frontpage
  */
+function i18n_boot() {
+  drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
+  _i18n_init();  
+}
 
 /**
- * Implementation of hook_init
- */ 
-function i18n_init(){
-  global $i18n_langpath;
-
-  $path = _i18n_get_original_path();
-  $i18n_langpath = i18n_get_lang_prefix($path);
-  $lang = _i18n_get_lang();
-  
-  if ($path == '') { // Main page
-    // Check for update script
-    if (isset($_GET['op'])) {
-      return ;
-    }
-    if( variable_get('cache',0) && $lang != i18n_default_language() ) {
-      // Redirect to main page in $lang
-      _i18n_goto($lang);
-    } elseif (variable_get('i18n_frontpage',0)){
-      $_GET['q'] = i18n_frontpage();
-    } 
-  } 
-  elseif ($lang == $path) { // When path is only language code
-    $_GET['q'] =  variable_get('i18n_frontpage',0) ? i18n_frontpage() : variable_get('site_frontpage','node'); 
+ * Implementation of hook_init().
+ *
+ * Special fix for site_frontpage, that may have been used before the language variables are loaded.
+ */
+function i18n_init() {
+  // If not in bootstrap, variable init. Otherwise we are serving a cached page so we don't need anything else.
+  if (!_i18n_is_bootstrap()) {
+    _i18n_init(TRUE);
+    _i18n_init_mode();
   }
-  elseif ($i18n_langpath) {
-    //search alias with and without lang and remove lang
-    $_GET['q'] = i18n_get_normal_path($path);
-  } 
+}
 
-  // Multi table, for backwards compatibility and experimentation
-  if (variable_get('i18n_multi' , 0)) {
-    _i18n_set_db_prefix(_i18n_get_lang());
+/**
+ * Initialize multilingual variables and use them for site_frontpage
+ * 
+ * Special fix for site_frontpage, that may have been used before the language variables are loaded.
+ */
+function _i18n_init($check_frontpage = FALSE) {
+  static $done, $default_frontpage;
+  
+  // Prevent this function from running twice;
+  if (!isset($done)) {
+    $done = TRUE;
+    $default_frontpage = variable_get('site_frontpage', 'node');
+    i18n_variable_init();
   }
-
-  // If not in bootstrap, include hooks
-  if(!_i18n_is_bootstrap()){
-    include 'modules/i18n/i18n.inc';
-    i18n_variable_init();    
+  // We do aditional frontpage check if this has run after first bootstrap phase.
+  // But if this runs in hook_boot we should be ok
+  if ($check_frontpage && $default_frontpage != variable_get('site_frontpage', 'node') && $_GET['q'] == drupal_get_normal_path($default_frontpage)) {
+    $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'));
   }
-}   
+}
 
 /**
+ * Initialize selection mode
+ */
+function _i18n_init_mode() {
+  if (i18n_selection_mode() != 'off') {
+    // Node language when loading specific nodes or creating translations.
+    if (arg(0) == 'node' ) {
+      // We shouldn't call menu_get_object() because it may end up calling i18n_selection_mode(), see #614548
+      // Also we better don't do the full node loading here because there may be missing modules (init)
+      if (is_numeric(arg(1)) && (arg(2) == 'edit') && ($lang = i18n_node_get_lang(arg(1)))) {
+        i18n_selection_mode('node', $lang);
+      }
+      elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language'])) {
+        i18n_selection_mode('translation', db_escape_string($_GET['language']));
+      }
+    }
+    elseif (arg(0) == 'admin') {
+      // There are some exceptions for admin pages.
+      if (arg(1) == 'content' && user_access('administer all languages')) {
+        // No restrictions for administration pages.
+        i18n_selection_mode('off');
+      }
+      elseif (arg(1) == 'build' && (arg(2) == 'menu-customize' || arg(2) == 'menu')) {
+        // All nodes available when editing custom menu items.
+        i18n_selection_mode('off');
+      }
+    }
+  }  
+}
+/**
  * Implementation of hook_help().
  */
-function i18n_help($section = 'admin/help#i18n' ) {
-  switch ($section) {
+function i18n_help($path = 'admin/help#i18n', $arg) {
+  switch ($path) {
     case 'admin/help#i18n' :
-      $output = t('
-        <p>This module provides support for internationalization of Drupal sites:</p>
-        <ul>
-        <li>Translation of the user interface for anonymous users (combined with locale)</li>
-        <li>Multi-language for content. Adds a language field for nodes and taxonomy vocabularies and terms</li>
-        <li>Basic translation management</li>
-        <li>Browser language detection</li>
-        <li>Keeps the language setting accross consecutive requests using URL rewriting</li>
-        <li>Provides a block for language selection and two theme functions: <i>i18n_flags</i> and <i>i18n_links</i></li>
-        <li>Support for long locale names</li>
-        </ul>
-        <p><small>Module developed by Jose A. Reyero, <a href="http://www.reyero.net">www.reyero.net</a></small></p>' );
-      break;
-  case 'admin/modules#description' :
-    $output = t('Enables multilingual content. <b>Requires locale module for interface translation</b>' );
-    break;
+      $output = '<p>'. t('This module improves support for multilingual content in Drupal sites:') .'</p>';
+      $output .= '<ul>';
+      $output .= '<li>'. t('Shows content depending on page language.') .'</li>';
+      $output .= '<li>'. t('Handles multilingual variables.') .'</li>';
+      $output .= '<li>'. t('Extended language option for chosen content types. For these content types transations will be allowed for all defined languages, not only for enabled ones.') .'</li>';
+      $output .= '<li>'. t('Provides a block for language selection and two theme functions: <em>i18n_flags</em> and <em>i18n_links</em>.') .'</li>';
+      $output .= '</ul>';
+      $output .= '<p>'. t('This is the base module for several others adding different features:') .'</p>';
+      $output .= '<ul>';
+      $output .= '<li>'. t('Multilingual menu items.') .'</li>';
+      $output .= '<li>'. t('Multilingual taxonomy adds a language field for taxonomy vocabularies and terms.') .'</li>';
+      $output .= '</ul>';
+      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@i18n">Internationalization module</a>.', array('@i18n' => 'http://drupal.org/node/133977')) .'</p>';
+      return $output;
+
+    case 'admin/settings/language/i18n':
+      $output = '<ul>';
+      $output .= '<li>'. t('To enable multilingual support for specific content types go to <a href="@configure_content_types">configure content types</a>.', array('@configure_content_types' => url('admin/content/types'))) .'</li>';
+      $output .= '</ul>';
+      return $output;
   }
-  return $output;
 }
 
 /**
- * Implementation of hook_settings().
- * 
- * Some options have been removed from previous versions:
- * - Languages are now taken from locale module unless defined in settings file
- * - Language dependent tables are authomatically used if defined in settings file
+ * Implementation of hook_menu().
  */
-function i18n_settings() {
-  
-  $form['i18n_browser'] = array(
-    '#type' => 'radios',
-    '#title' => t('Browser language detection'),
-    '#default_value' => variable_get('i18n_browser', 0),
-    '#options' => array(t('Disabled'), t('Enabled' )),
-    '#description' => t('A description of this setting.'),
-  );
-  
-  $form['i18n_frontpage'] = array(
-    '#type' => 'radios',
-    '#title' => t('Front page'),
-    '#default_value' => variable_get('i18n_frontpage', 0),
-    '#options' => array(t('Default'), t('Language dependent')),
-    '#description' => t("If 'language dependent' is selected, default front page will be prepended with language code, i.e. 'en/node'"),
+function i18n_menu() {
+  $items['admin/settings/language/i18n'] = array(
+    'title' => 'Multilingual system',
+    'description' => 'Configure extended options for multilingual content and translations.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('i18n_admin_settings'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'i18n.admin.inc',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10,
   );
-  
-  // Language icons
-  $form['icons'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Language icons settings'),
-    '#collapsible' => TRUE,
-    '#collapsed' => FALSE,
+  $items['admin/settings/language/i18n/configure'] = array(
+    'title' => 'Options',
+    'description' => 'Configure extended options for multilingual content and translations.',
+    //'page callback' => 'drupal_get_form',
+    //'page arguments' => array('i18n_admin_settings'),
+    //'access arguments' => array('administer site configuration'),
+    'file' => 'i18n.admin.inc',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
   );
-  $form['icons']['i18n_icon_path'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Language icons path'),
-    '#default_value' => variable_get('i18n_icon_path', 'modules/i18n/flags/*.png'),
-    '#size' => 70,
-    '#maxlength' => 180,
-    '#description' => t('Path for language icons, relative to Drupal installation. \'*\' is a placeholder for language code.'),
+  $items['admin/settings/language/i18n/variables'] = array(
+    'title' => 'Variables',
+    'description' => 'Multilingual variables.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('i18n_admin_variables_form'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'i18n.admin.inc',
+    'type' => MENU_LOCAL_TASK,
   );
-  $form['icons']['i18n_icon_size'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Language icons size'),
-    '#default_value' => variable_get('i18n_icon_size', '16x12'),
-    '#size' => 10,
-    '#maxlength' => 10,
-    '#description' => t('Image size for language icons, in the form "width x height".'),
+  // Autocomplete callback for nodes
+  $items['i18n/node/autocomplete'] = array(
+    'title' => 'Node title autocomplete',
+    'page callback' => 'i18n_node_autocomplete',
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+    'file' => 'i18n.pages.inc',
   );
+  return $items;
+}
 
-  // Advanced options
-  $form['advanced'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Advanced settings'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-  $form['advanced']['i18n_selection_mode'] = array(
-    '#type' => 'radios',
-    '#title' => t('Content selection mode'),
-    '#default_value' => variable_get('i18n_selection_mode', 'simple'),
-    '#options' => _i18n_selection_mode(),
-    '#description' => t('Determines which content to show depending on language.'),
-  );
-    
-  return $form;
+/**
+ * Implementation of hook_menu_alter().
+ *
+ * Take over the node translation page.
+ */
+function i18n_menu_alter(&$items) {
+  $items['node/%node/translate']['page callback'] = 'i18n_translation_node_overview';
+  $items['node/%node/translate']['file'] = 'i18n.pages.inc';
+  $items['node/%node/translate']['module'] = 'i18n';
 }
 
 /**
- * Simple i18n API
+ * Implementation of hook_nodeapi().
  */
+function i18n_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+  global $language;
+
+  if (variable_get('language_content_type_' . $node->type, 0)) {
+    // Set current language for new nodes if option enabled
+    if ($op == 'prepare' && empty($node->nid) && empty($node->language) && variable_get('i18n_newnode_current_' . $node->type, 0)) {
+      $node->language = $language->language;
+    }
+  }
+}
 
 /**
- * Get list of supported languages
+ * Implementation of hook_alter_translation_link().
+ *
+ * Handles links for extended language. The links will have current language.
  */
-function i18n_supported_languages() {
-  global $i18n_languages;  
-  static $languages;
-  if ($languages) {
-    return $languages;
+function i18n_translation_link_alter(&$links, $path) {
+  global $language;
+
+  // Check for a node related path, and for its translations.
+  if ((preg_match("!^node/([0-9]+)(/.+|)$!", $path, $matches)) && ($node = node_load((int)$matches[1])) && !empty($node->tnid)) {
+    // make sure language support is set to LANGUAGE_SUPPORT_EXTENDED, so links
+    // dont get added for LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED
+    if (variable_get('i18n_node_'. $node->type, LANGUAGE_SUPPORT_NORMAL) == LANGUAGE_SUPPORT_EXTENDED) {
+      $languages = language_list();
+      $extended = array();
+      foreach (translation_node_get_translations($node->tnid) as $langcode => $translation_node) {
+        if (!isset($links[$langcode]) && isset($languages[$langcode])) {
+          $extended[$langcode] = array(
+            'href' => 'node/'. $translation_node->nid . $matches[2],
+            'language' => $language,
+            'language_icon' => $languages[$langcode],
+            'title' => $languages[$langcode]->native,
+            'attributes' => array('class' => 'language-link'),
+          );
+        }
+      }
+      // This will run after languageicon module, so we add icon in case that one is enabled.
+      if ($extended && function_exists('languageicons_translation_link_alter')) {
+        languageicons_translation_link_alter($extended, $path);
+      }
+      $links = array_merge($links, $extended);
+    }
   }
-  elseif(is_array($i18n_languages)) {
-    return $languages = $i18n_languages;
-  }   
-  elseif ($languages = _i18n_locale_supported_languages()) {
-    return $languages;
-  } 
-  else {
-    return array();
+}
+
+/**
+ * Implementation of hook_link_alter().
+ *
+ * Handles links for extended languages. Sets current interface language.
+ */
+function i18n_link_alter(&$links, $node) {
+  global $language;
+
+  $language_support = variable_get('i18n_node_'. $node->type, LANGUAGE_SUPPORT_NORMAL);
+
+  // Hide node translation links.
+  if (variable_get('i18n_hide_translation_links', 0) == 1) {
+    foreach ($links as $module => $link) {
+      if (strpos($module, 'node_translation') === 0) {
+        unset($links[$module]);
+      }
+    }
   }
-} 
+
+  if (!empty($node->tnid)) {
+    foreach (array_keys(i18n_language_list('extended')) as $langcode) {
+      $index = 'node_translation_'. $langcode;
+      if (!empty($links[$index])) {
+        if ($language_support != LANGUAGE_SUPPORT_EXTENDED && $links[$index]['language']->enabled == 0) {
+          unset($links[$index]);
+        }
+        else {
+          $links[$index]['language'] = $language;
+        }
+      }
+    }
+  }
+}
 
 /**
- * Returns default language
+ * Implementation of hook_user().
+ *
+ * Switch to user's language after login.
  */
-function i18n_default_language(){
-  return key(i18n_supported_languages());
+function i18n_user($op, &$edit, &$account, $category = NULL) {
+  if ($op == 'login' && $account->language) {
+    i18n_get_lang($account->language);
+  }
 }
 
 /**
- * Get language from browser settings, but only if it is in the $i18n_languages array
+ * Implementation of hook_elements().
+ *
+ * Add a process callback for textfields.
  */
-function i18n_get_browser_lang() {
-  $languages = i18n_supported_languages();
-  $accept=explode(',',array_shift( explode(";",$_SERVER["HTTP_ACCEPT_LANGUAGE"])));
-  foreach ($accept as $lang) {
-    $lang=substr($lang,0,2);
-    if ( !empty($lang) && array_key_exists($lang,$languages)) {
-      return $lang;
+function i18n_elements() {
+  $type = array();
+  $type['textfield'] = array('#process' => array('i18n_textfield_process'));
+  return $type;
+}
+
+/**
+ * Process callback for textfield elements.
+ *
+ * When editing or translating a node, set Javascript to rewrite autocomplete
+ * paths to use the node language prefix rather than the current content one.
+ */
+function i18n_textfield_process($element) {
+  global $language;
+  static $sent = FALSE;
+
+  // Ensure we send the Javascript only once.
+  if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
+    // Add a JS file for node forms.
+    // Determine if we are either editing or translating an existing node.
+    // We can't act on regular node creation because we don't have a specified
+    // node language.
+    $node_edit = $node = menu_get_object() && arg(2) == 'edit' && isset($node->language) && !empty($node->language);
+    $node_translate = arg(0) == 'node' && arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language']);
+    if ($node_edit || $node_translate) {
+      $node_language = $node_edit ? $node->language : $_GET['language'];
+      // Only needed if the node language is different from the interface one.
+      if ($node_language != $language->language) {
+        $languages = language_list();
+        if (isset($languages[$node_language])) {
+          drupal_add_js(drupal_get_path('module', 'i18n') . '/i18n.js');
+          // Pass the interface and content language base paths.
+          // Remove any trailing forward slash. Doing so prevents a mismatch
+          // that occurs when a language has no prefix and hence gets a path
+          // with a trailing forward slash.
+          $interface = rtrim(url('', array('absolute' => TRUE)), '/');
+          $content = rtrim(url('', array('absolute' => TRUE, 'language' => $languages[$node_language])), '/');
+          $data = array('interface_path' => $interface, 'content_path' => $content);
+          drupal_add_js(array('i18n' => $data), 'setting');
+        }
+      }
     }
+    $sent = TRUE;
   }
+  return $element;
+}
+
+/**
+ * Simple i18n API
+ */
+
+/**
+ * Get language properties.
+ *
+ * @param $code
+ *   Language code.
+ * @param $property
+ *   It may be 'name', 'native', 'ltr'...
+ */
+function i18n_language_property($code, $property) {
+  $languages = language_list();
+  return isset($languages[$code]->$property) ? $languages[$code]->$property : NULL;
+}
+
+/**
+ * Get node language.
+ */
+function i18n_node_get_lang($nid, $default = '') {
+  $lang = db_result(db_query('SELECT language FROM {node} WHERE nid = %d', $nid));
+  return $lang ? $lang : $default ;
 }
 
 /**
- * Get language code from path.
+ * Get allowed languages for node.
+ *
+ * This allows node types to define its own language list implementing hook 'language_list'.
  *
- * @param $path
- * @param $trim true to remove language code from $path
- */
-function i18n_get_lang_prefix(&$path, $trim = FALSE) {
-  $maybelang = array_shift(explode('/',$path));
-  $languages = i18n_supported_languages();
-  if(array_key_exists($maybelang, $languages)){
-    if($trim) {
-      $path = trim(substr($path, strlen($maybelang)),'/');
+ * @param $node
+ *   Node to retrieve language list for.
+ * @param $translate
+ *   Only languages available for translation. Filter out existing translations.
+ */
+function i18n_node_language_list($node, $translate = FALSE) {
+  // Check if the node module manages its own language list.
+  $languages = node_invoke($node, 'language_list', $translate);
+
+  if (!$languages) {
+    if (variable_get('i18n_node_'. $node->type, 0) >= LANGUAGE_SUPPORT_EXTENDED) {
+      $languages = locale_language_list('name', TRUE); // All defined languages
+    }
+    else {
+      $languages = locale_language_list(); // All enabled languages
+    }
+    if ($translate && isset($node->tnid) && $node->tnid && ($translations = translation_node_get_translations($node->tnid))) {
+      unset($translations[$node->language]);
+      foreach (array_keys($translations) as $langcode) {
+        unset($languages[$langcode]);
+      }
+    }
+    // Language may be locked for this node type, restrict options to current one
+    if (variable_get('i18n_lock_node_' . $node->type, 0) && !empty($node->language) && !empty($languages[$node->language])) {
+      $languages = array($node->language => $languages[$node->language]);
+    }
+    // Check language required for this type (no language neutral)
+    elseif (!variable_get('i18n_required_node_' . $node->type, 0)) {
+      $languages = array('' => t('Language neutral')) + $languages;
     }
-    return $maybelang;
   }
-  /*
-  if (preg_match("/^\w\w($|\/.*)/", $path)) {
-    return substr($path, 0, 2);
+
+  return $languages;
+}
+
+/**
+ * Selection mode for content.
+ *
+ * Warning: when used with params they need to be escaped, as some values are thrown directly in queries.
+ *
+ * Allows several modes for query rewriting and to change them programatically.
+ *  off = No language conditions inserted.
+ *  simple = Only current language and no language.
+ *  mixed = Only current and default languages.
+ *  strict = Only current language.
+ *  default = Only default language.
+ *  user = User defined, in the module's settings page.
+ *  params = Gets the stored params.
+ *  reset = Returns to previous.
+ *  custom = add custom where clause, like "%alias.language = 'en'".
+ */
+function i18n_selection_mode($mode = NULL, $params = NULL) {
+  static $current_mode;
+  static $current_value = '';
+  static $store = array();
+
+  // Initialization, first time this runs
+  if (!isset($current_mode)) {
+    $current_mode = variable_get('i18n_selection_mode', 'simple');
+  }
+
+  if (!$mode) {
+    return $current_mode;
+  }
+  elseif ($mode == 'params') {
+    return $current_value;
+  }
+  elseif ($mode == 'reset') {
+    list($current_mode, $current_value) = array_pop($store);
+  }
+  else {
+    array_push($store, array($current_mode, $current_value));
+    $current_mode = $mode;
+    $current_value = $params;
   }
-  */
 }
 
 /**
- * Language dependent front page
+ * Implementation of hook_db_rewrite_sql().
+ *
+ * Rewrite node queries so language selection options are enforced.
  */
-function i18n_frontpage() {
-  $path = _i18n_get_lang().'/'.variable_get('site_frontpage','node');
-  return i18n_get_normal_path($path);
+function i18n_db_rewrite_sql($query, $primary_table, $primary_key, $args = array()) {
+  // If mode is 'off' = no rewrite, we cannot return any empty 'where' string so check here.
+  $mode = i18n_selection_mode();
+  if ($mode == 'off') return;
+
+  // Disable language conditions for views.
+  if (array_key_exists('view', $args)) return;
+
+  switch ($primary_table) {
+    case 'n':
+    case 'node':
+      // No rewrite for queries with subselect ? (views count queries).
+      // @ TO DO Actually these queries look un-rewrittable, check with other developers.
+      if (preg_match("/FROM \(SELECT/", $query)) return;
+      // No rewrite for translation module queries.
+      if (preg_match("/.*FROM {node} $primary_table WHERE.*$primary_table\.tnid/", $query)) return;
+      // When loading specific nodes, language conditions shouldn't apply.
+      if (preg_match("/WHERE.*\s$primary_table.nid\s*=\s*(\d|%d)/", $query)) return;
+      // If language conditions already there, get out.
+      if (preg_match("/i18n/", $query)) return;
+
+
+      // Mixed mode is a bit more complex, we need to join in one more table
+      // and add some more conditions, but only if language is not default.
+      if ($mode == 'mixed') {
+        $result['where'] = i18n_db_rewrite_where($primary_table, 'node', 'simple');
+        if (i18n_get_lang() != i18n_default_language()) {
+          $result['join'] = "LEFT JOIN {node} i18n ON $primary_table.tnid > 0 AND $primary_table.tnid = i18n.tnid AND i18n.language = '". i18n_get_lang() ."'";
+          // So we show also nodes that have default language.
+          $result['where'] .= " OR $primary_table.language = '". i18n_default_language() ."' AND i18n.nid IS NULL";
+        }
+      }
+      else {
+        $result['where'] = i18n_db_rewrite_where($primary_table, 'node', $mode);
+      }
+      return $result;
+  }
 }
 
 /**
- * This function is similar to drupal_get_normal_path, but language-aware
- * Also removes language from path
+ * Rewrites queries depending on rewriting mode.
  */
-function i18n_get_normal_path($path) {
-  // First, check alias with lang, then without
-  if ($path != ($alias = drupal_lookup_path('alias', $path))) {
-    return $alias;
-  } elseif(i18n_get_lang_prefix($path, TRUE)){
-    $alias = drupal_lookup_path('alias', $path);
-    if( $alias && $path != $alias) {
-      return $alias;
-    }
-  } 
-  // We only get here when no alias is defined, with or without lang
-  return $path;
+function i18n_db_rewrite_where($alias, $type, $mode = NULL) {
+  if (!$mode) {
+    // Some exceptions for query rewrites.
+    $mode = i18n_selection_mode();
+  }
+
+  // Get languages to simplify query building.
+  $current = i18n_get_lang();
+  $default = i18n_default_language();
+
+  if ($mode == 'strict' && $type != 'node') {
+    // Special case. Selection mode is 'strict' but this should be only for node queries.
+    $mode = 'simple';
+  }
+  elseif ($mode == 'mixed' && $current == $default) {
+    // If mode is mixed but current = default, is the same as 'simple'.
+    $mode = 'simple';
+  }
+
+  switch ($mode) {
+    case 'off':
+      return '';
+
+    case 'simple':
+      return "$alias.language ='$current' OR $alias.language ='' OR $alias.language IS NULL" ;
+
+    case 'mixed':
+      return "$alias.language ='$current' OR $alias.language ='$default' OR $alias.language ='' OR $alias.language IS NULL" ;
+
+    case 'strict':
+      return "$alias.language ='$current'" ;
+
+    case 'node':
+    case 'translation':
+      return "$alias.language ='". i18n_selection_mode('params') ."' OR $alias.language ='' OR $alias.language IS NULL" ;
+
+    case 'default':
+      return "$alias.language ='$default' OR $alias.language ='' OR $alias.language IS NULL" ;
+
+    case 'custom':
+      return str_replace('%alias', $alias, i18n_selection_mode('params'));
+  }
 }
 
 /**
- *     Gets language, checking in order:
+ * Implementation of hook_preprocess_page().
  *
- *     1. Path language
- *     2. User language
- *     3. Browser language
- *     4. Default language
+ * Add the language code to the classes for the <body> tag. Unfortunately, some
+ * themes will not respect the variable we're modifying to achieve this - in
+ * particular, Garland and Minelli do not.
  */
+function i18n_preprocess_page(&$variables) {
+  if (isset($variables['body_classes'])) {
+    global $language;
+    $variables['body_classes'] .= ' i18n-' . $language->language;
+  }
+}
 
-function _i18n_get_lang() {
-  global $user, $i18n_langpath;
-  static $i18n_lang;
-  
-  //see if the language is already set.
-  if ($i18n_lang) {
-    return $i18n_lang;
+/**
+ * Implementation of hook_exit().
+ */
+function i18n_exit() {
+  _i18n_variable_exit();
+}
+
+/**
+ * Implementation of hook_form_alter();
+ *
+ * This is the place to add language fields to all forms.
+ */
+function i18n_form_alter(&$form, $form_state, $form_id) {
+  global $language;
+
+  switch ($form_id) {
+    case 'node_type_form':
+      $disabled = !variable_get('language_content_type_'. $form['#node_type']->type, 0);
+      $form['i18n'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Multilanguage options'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#description' => t('Extended multilingual options provided by Internationalization module.'),
+        '#disabled' => $disabled,
+      );
+      // Add disabled message
+      if ($disabled) {
+        $form['i18n']['#description'] .= ' <em>' . t('These will be available only when you enable Multilingual support in Workflow settings above.') . '</em>';
+      }
+      // Some settings about node languages
+      $form['i18n']['options'] = array(
+        '#title' => t('Options for node language'),
+        '#type' => 'fieldset',
+        '#disabled' => $disabled,
+      );
+      $form['i18n']['options']['i18n_newnode_current'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Set current language as default for new content.'),
+        '#default_value' => variable_get('i18n_newnode_current_' . $form['#node_type']->type, 0),
+        '#disabled' => $disabled,
+      );
+      $form['i18n']['options']['i18n_required_node'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Require language (Do not allow Language Neutral).'),
+        '#default_value' => variable_get('i18n_required_node_' . $form['#node_type']->type, 0),
+        '#disabled' => $disabled,
+      );
+      $form['i18n']['options']['i18n_lock_node'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Lock language (Cannot be changed).'),
+        '#default_value' => variable_get('i18n_lock_node_' . $form['#node_type']->type, 0),
+        '#disabled' => $disabled,
+      );
+      // Add extended language support option to content type form.
+      $form['i18n']['i18n_node'] = array(
+        '#type' => 'radios',
+        '#title' => t('Extended language support'),
+        '#default_value' => variable_get('i18n_node_'. $form['#node_type']->type, LANGUAGE_SUPPORT_NORMAL),
+        '#options' => _i18n_content_language_options(),
+        '#description' => t('If enabled, all defined languages will be allowed for this content type in addition to only enabled ones. This is useful to have more languages for content than for the interface.'),
+        '#disabled' => $disabled,
+      );
+      break;
+
+    default:
+      // Extensions for node edit forms
+      if (isset($form['#id']) && $form['#id'] == 'node-form') {
+        if (isset($form['#node']->type)) {
+          if (variable_get('language_content_type_'. $form['#node']->type, 0)) {
+            if (!empty($form['language']['#options'])) {
+              $form['language']['#options'] = i18n_node_language_list($form['#node'], TRUE);
+            }
+          }
+          elseif (!isset($form['#node']->nid)) {
+            // Set language to empty for not multilingual nodes when creating
+            $form['language'] = array('#type' => 'value', '#value' => '');
+          }
+        }
+      }
+
+      // Multilingual variables in settings form.
+      if (isset($form['#theme']) && $form['#theme'] == 'system_settings_form' && $variables = i18n_variable()) {
+        if ($i18n_variables = i18n_form_alter_settings($form, $variables)) {
+          array_unshift($form['#submit'], 'i18n_variable_form_submit');
+          $form['#i18n_variables'] = $i18n_variables;
+        }
+      }
   }
+}
+
+/**
+ * Implementation of hook_perm().
+ *
+ * Permissions defined
+ * - administer all languages
+ *   Disables language conditions for administration pages, so the user can view objects for all languages at the same time.
+ *   This applies for: menu items, taxonomy
+ * - administer translations
+ *   Will allow to add/remove existing nodes to/from translation sets.
+ */
+function i18n_perm() {
+  return array('administer all languages', 'administer translations');
+}
 
-  $languages = i18n_supported_languages();
-   
-  if ($i18n_langpath && array_key_exists($i18n_langpath,$languages)) {
-    $i18n_lang = $i18n_langpath;
+/**
+ * Implementation of hook_theme().
+ */
+function i18n_theme() {
+  return array(
+    'i18n_node_select_translation' => array(
+      'arguments' => array('element' => NULL),
+      'file' => 'i18n.pages.inc',
+    ),
+  );
+}
+
+/**
+ * Process menu and menu item add/edit form submissions.
+ */
+function i18n_menu_edit_item_form_submit($form, &$form_state) {
+  $mid = menu_edit_item_save($form_state['values']);
+  db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", $form_state['values']['language'], $mid);
+  return 'admin/build/menu';
+}
+
+/**
+ * Check for multilingual variables in form.
+ */
+function i18n_form_alter_settings(&$form, &$variables) {
+  $result = array();
+  foreach (element_children($form) as $field) {
+    if (count(element_children($form[$field])) && empty($form[$field]['#tree'])) {
+      $result += i18n_form_alter_settings($form[$field], $variables);
+    }
+    elseif (in_array($field, $variables)) {
+      // Add form field class: i18n-variable
+      $form[$field]['#attributes']['class'] = !empty($form[$field]['#attributes']['class']) ? $form[$field]['#attributes']['class'] . ' i18n-variable' : 'i18n-variable';
+      $form[$field]['#description'] = !empty($form[$field]['#description']) ? $form[$field]['#description'] : '';
+      $form[$field]['#description'] .= ' <strong>'. t('This is a multilingual variable.') .'</strong>';
+      // Addd field => name to result
+      $result[$field] = !empty($form[$field]['#title']) ? $form[$field]['#title'] : $field;
+    }
   }
-  elseif ($user->uid && $user->language && array_key_exists($user->language,$languages)) {
-    $i18n_lang = $user->language;
+  return $result;
+}
+
+/**
+ * Save multilingual variables and remove them from form.
+ */
+function i18n_variable_form_submit($form, &$form_state) {
+  $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
+  $variables = i18n_variable();
+  $language = i18n_get_lang();
+  $is_default = $language == language_default('language');
+  foreach ($form_state['values'] as $key => $value) {
+    if (i18n_variable($key)) {
+      if ($op == t('Reset to defaults')) {
+        i18n_variable_del($key, $language);
+      }
+      else {
+        if (is_array($value) && isset($form_state['values']['array_filter'])) {
+          $value = array_keys(array_filter($value));
+        }
+        i18n_variable_set($key, $value, $language);
+      }
+      // If current is default language, we allow global (without language) variables to be set too
+      if (!$is_default) {
+        unset($form_state['values'][$key]);
+      }
+    }
   }
-  elseif (variable_get("i18n_browser",0) && $lang=i18n_get_browser_lang()) {
-    $i18n_lang=$lang;
+  // The form will go now through system_settings_form_submit()
+}
+
+/**
+ * Initialization of multilingual variables.
+ *
+ * @param $langcode
+ *   Language to retrieve variables. Defaults to current language.
+ */
+function i18n_variable_init($langcode = NULL) {
+  global $conf;
+
+  $langcode = $langcode ? $langcode : i18n_get_lang();
+  if ($variables = _i18n_variable_init($langcode)) {
+    $conf = array_merge($conf, $variables);
   }
-  else {
-    $i18n_lang=key($languages);
+}
+
+/**
+ * Get language from context.
+ */
+function _i18n_get_context_lang() {
+  // Node language when loading specific nodes or creating translations.
+  if (arg(0) == 'node' ) {
+    if (($node = menu_get_object('node')) && $node->language) {
+      return $node->language;
+    }
+    elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language'])) {
+      return $_GET['language'];
+    }
+  }
+}
+
+/**
+ * Helper function to create language selector.
+ */
+function _i18n_language_select($value ='', $description ='', $weight = -20, $languages = NULL) {
+  $languages = $languages ? $languages : locale_language_list();
+  return array(
+    '#type' => 'select',
+    '#title' => t('Language'),
+    '#default_value' => $value,
+    '#options' => array_merge(array('' => ''), $languages),
+    '#description' => $description,
+    '#weight' => $weight,
+  );
+}
+
+/**
+ * Load language variables into array.
+ */
+function _i18n_variable_init($langcode) {
+  global $i18n_conf;
+
+  if (!isset($i18n_conf[$langcode])) {
+    $cacheid = 'variables:'. $langcode;
+    if ($cached = cache_get($cacheid)) {
+      $i18n_conf[$langcode] = $cached->data;
+    }
+    else {
+      $result = db_query("SELECT * FROM {i18n_variable} WHERE language = '%s'", $langcode);
+      $i18n_conf[$langcode] = array();
+      while ($variable = db_fetch_object($result)) {
+        $i18n_conf[$langcode][$variable->name] = unserialize($variable->value);
+      }
+      cache_set($cacheid, $i18n_conf[$langcode]);
+    }
+  }
+
+  return $i18n_conf[$langcode];
+}
+
+/**
+ * Save multilingual variables that may have been changed by other methods than settings pages.
+ */
+function _i18n_variable_exit() {
+  global $conf, $i18n_conf;
+
+  $langcode = i18n_get_lang();
+  if (isset($i18n_conf[$langcode])) {  
+    $refresh = FALSE;
+    // Rewritten because array_diff_assoc may fail with array variables.
+    foreach (i18n_variable() as $name) {
+      if (isset($conf[$name]) && isset($i18n_conf[$langcode][$name]) && $conf[$name] != $i18n_conf[$langcode][$name]) {  
+        $refresh = TRUE;
+        $i18n_conf[$langcode][$name] = $conf[$name];
+        db_query("DELETE FROM {i18n_variable} WHERE name='%s' AND language='%s'", $name, $langcode);
+        db_query("INSERT INTO {i18n_variable} (language, name, value) VALUES('%s', '%s', '%s')", $langcode, $name, serialize($conf[$name]));
+      }
+    }
+    if ($refresh) {
+      cache_set('variables:'. $langcode, $i18n_conf[$langcode]);
+    }
   }
-  
-  return $i18n_lang;
 }
 
 /**
- * Check whether we are in bootstrap mode
- */  
-function _i18n_is_bootstrap(){
+ * Check whether we are in bootstrap mode.
+ */
+function _i18n_is_bootstrap() {
   return !function_exists('drupal_get_headers');
-}    
+}
+
+/**
+ * Drupal 6, backwards compatibility layer.
+ *
+ * @ TO DO Fully upgrade all the modules and remove
+ */
 
 /**
- * Sets db_prefix to given language
+ * This one expects to be called first from common.inc
  */
-function _i18n_set_db_prefix($lang) {
-  global $db_prefix, $db_prefix_i18n;
-  if (is_array($db_prefix_i18n)) {
-    $db_prefix = array_merge($db_prefix, str_replace('**', $lang, $db_prefix_i18n));
+function i18n_get_lang() {
+  global $language;
+  return $language->language;
+}
+
+/**
+ * @defgroup i18n_api Extended language API
+ * @{
+ * This is an extended language API to be used by modules in i18n package.
+ */
+
+/**
+ * Returns language lists.
+ */
+function i18n_language_list($type = 'enabled', $field = 'name') {
+  switch ($type) {
+    case 'enabled':
+      return locale_language_list($field);
+
+    case 'extended':
+      $enabled = locale_language_list($field);
+      $defined = locale_language_list($field, TRUE);
+      return array_diff_assoc($defined, $enabled);
   }
 }
 
 /**
- * To get the original path. 
- * Cannot use $_GET["q"] cause it may have been already changed
+ * Returns default language code.
  */
-function _i18n_get_original_path() {
-  return isset($_REQUEST["q"]) ? trim($_REQUEST["q"],"/") : '';
+function i18n_default_language() {
+  return language_default('language');
 }
 
 /**
- * Returns list of enabled languages from locale module
+ * Get list of supported languages, native name.
  *
- * Some code borrowed from locale module.
- * And yes, if locale enabled, languages are cached twice. But better twice than never ;-)
- */
-function _i18n_locale_supported_languages() {
-  if(function_exists('locale_supported_languages')){
-    $languages = locale_supported_languages();
-    return $languages['name'];
-  } else {
-    $result = db_query('SELECT locale, name FROM {locales_meta} WHERE enabled = 1 ORDER BY isdefault DESC, name ASC');
-    while ($row = db_fetch_object($result)) {
-      $enabled[$row->locale] = $row->name;
-    }
-    return $enabled;
+ * @param $all
+ *   TRUE to get all defined languages.
+ */
+function i18n_supported_languages($all = FALSE) {
+  return locale_language_list('native', $all);
+}
+
+/**
+ * Get list of multilingual variables or check whether a variable is multilingual
+ */
+function i18n_variable($name = NULL) {
+  $variables = variable_get('i18n_variables', array());
+  return $name ? in_array($name, $variables) : $variables;
+}
+
+/**
+ * Set a persistent language dependent variable.
+ *
+ * @param $name
+ *   The name of the variable to set.
+ * @param $value
+ *   The value to set. This can be any PHP data type; these functions take care
+ *   of serialization as necessary.
+ * @param $langcode
+ *   Language code.
+ */
+function i18n_variable_set($name, $value, $langcode) {
+  global $conf, $i18n_conf;
+
+  $serialized_value = serialize($value);
+  db_query("UPDATE {i18n_variable} SET value = '%s' WHERE name = '%s' AND language = '%s'", $serialized_value, $name, $langcode);
+  if (!db_affected_rows()) {
+    @db_query("INSERT INTO {i18n_variable} (name, language, value) VALUES ('%s', '%s', '%s')", $name, $langcode, $serialized_value);
+  }
+  cache_clear_all('variables:'. $langcode, 'cache');
+  $i18n_conf[$langcode][$name] = $value;
+  if ($langcode == i18n_get_lang()) {
+    $conf[$name] = $value;
   }
 }
 
 /**
- * Emulates drupal_goto, it may not be loaded yet
+ * Get single multilingual variable
  */
-function _i18n_goto($lang){
-  if(!function_exists('drupal_goto')){
-    require_once './includes/common.inc';    
+function i18n_variable_get($name, $langcode, $default = NULL) {
+  if ($variables = _i18n_variable_init($langcode)) {
+    return isset($variables[$name]) ? $variables[$name] : $default;
+  }
+  else {
+    return $default;
+  }
+}
+
+/**
+ * Unset a persistent multilingual variable.
+ *
+ * @param $name
+ *   The name of the variable to undefine.
+ * @param $langcode
+ *   Optional language code. If not set it will delete the variable for all languages.
+ */
+function i18n_variable_del($name, $langcode = NULL) {
+  global $conf, $i18n_conf;
+
+  if ($langcode) {
+    db_query("DELETE FROM {i18n_variable} WHERE name = '%s' AND language='%s'", $name, $langcode);
+    cache_clear_all('variables:'. $langcode, 'cache');
+    unset($i18n_conf[$langcode][$name]);
+    // If current language, unset also global conf
+    if ($langcode == i18n_get_lang()) {
+      unset($conf[$name]);
+    }
+  }
+  else {
+    db_query("DELETE FROM {i18n_variable} WHERE name = '%s'", $name);
+    if (db_affected_rows()) {
+      cache_clear_all('variables:', 'cache', TRUE);
+      if (is_array($i18n_conf)) {
+        foreach (array_keys($i18n_conf) as $lang) {
+          unset($i18n_conf[$lang][$name]);
+        }
+      }
+    }
   }
-  drupal_goto($lang);
 }
 
 /**
- * i18n_selection_mode
- * Allows several modes for query rewriting and to change them programatically
- *     off = No language conditions inserted
- *     simple = Only current language and no language
- *     mixed = Only current and default languages
- *  strict = Only current language
- *  default = Only default language
- *  user = User defined, in the module's settings page
+ * Utility. Get part of array variable.
  */
-function i18n_selection_mode($mode=NULL){
-  static $current;
-  if($mode) {
-    $current = ($mode == 'user') ? variable_get('i18n_selection_mode', 'simple') : $mode;
-  } elseif($current) {
-    return $current;
-  } else {
-    return $current = variable_get('i18n_selection_mode', 'simple');
+function i18n_array_variable_get($name, $element, $default = NULL) {
+  if (($values = variable_get($name, array())) && isset($values[$element])) {
+    return $values[$element];
+  }
+  else {
+    return $default;
   }
 }
 
-function _i18n_selection_mode(){
+/**
+ * Utility. Set part of array variable.
+ */
+function i18n_array_variable_set($name, $element, $value) {
+  $values = variable_get($name, array());
+  $values[$element] = $value;
+  variable_set($name, $values);
+}
+
+/**
+ * @} End of "defgroup i18n_api".
+ */
+
+/**
+ * List of language support modes for content.
+ */
+function _i18n_content_language_options() {
   return array(
-    'simple' => t('Only current language and no language'),
-    'mixed' => t('Only current and default languages and no language'),
-    'default' => t('Only default language and no language'),    
-    'strict' => t('Only current language'),
-    'off' => t('All content. No language conditions apply'),
-    
+    LANGUAGE_SUPPORT_NORMAL => t('Normal - All enabled languages will be allowed.'),
+    LANGUAGE_SUPPORT_EXTENDED => t('Extended - All defined languages will be allowed.'),
+    LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED => t('Extended, but not displayed - All defined languages will be allowed for input, but not displayed in links.'),
   );
 }
-?>
\ No newline at end of file