Skip to content
i18n.module 32.5 KiB
Newer Older
<?php
// $Id$
 * Internationalization (i18n) module
 *
 * 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);
Alexander Hass's avatar
Alexander Hass committed
 * Implementation of hook_init().
Alexander Hass's avatar
Alexander Hass committed
 * Will initialize language dependent variables.
 * Modify rewriting conditions when viewing specific nodes.
Alexander Hass's avatar
Alexander Hass committed
 * Special fix for site_frontpage, that may have been used before the language variables are loaded.
Alexander Hass's avatar
Alexander Hass committed
function i18n_init() {
Alexander Hass's avatar
Alexander Hass committed
  // If not in bootstrap, variable init.
  if (!_i18n_is_bootstrap()) {
    $default_frontpage = variable_get('site_frontpage', 'node');
Alexander Hass's avatar
Alexander Hass committed
    // Now we check whether this is the frontpage and it should be a different one.
    if ($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'));
    }
/**
 * Implementation of hook_help().
 */
function i18n_help($path = 'admin/help#i18n', $arg) {
  switch ($path) {
Jose Antonio Reyero del Prado's avatar
Jose Antonio Reyero del Prado committed
    case 'admin/help#i18n' :
Alexander Hass's avatar
Alexander Hass committed
      $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>';
Alexander Hass's avatar
Alexander Hass committed
      $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>';
Alexander Hass's avatar
Alexander Hass committed
      $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>';
Alexander Hass's avatar
Alexander Hass committed
      $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>';
Alexander Hass's avatar
Alexander Hass committed
      $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;
/**
 * Implementation of hook_menu().
 */
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',
    'weight' => 10,
  );  
  $items['admin/settings/language/i18n/configure'] = 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_DEFAULT_LOCAL_TASK,
  // 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;
}
/**
 * Implementation of hook_menu_alter().
Alexander Hass's avatar
Alexander Hass committed
 * 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';
}

 * 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;
    }
  }
}

function i18n_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
  if (variable_get("i18n_node_$node->type", 0)) {
Alexander Hass's avatar
Alexander Hass committed
    switch ($op) {
      case 'load':
        return db_fetch_array(db_query("SELECT trid, language, status AS i18n_status FROM {i18n_node} WHERE nid=%d", $node->nid));
Alexander Hass's avatar
Alexander Hass committed

      case 'insert':
      case 'update':
        db_query("DELETE FROM {i18n_node} WHERE nid=%d",$node->nid);
        if ($node->language){
          // Assign a trid from the beginning
          db_query("INSERT INTO {i18n_node} (nid, trid, language, status) VALUES(%d, '%d', '%s', '%d')", $node->nid, $node->trid, $node->language, $node->i18n_status);
        }
        // Handle menu items. Fixes duplication issue and language for menu items which happens when editing nodes in languages other than current.
        if (isset($node->menu) && !$node->menu['delete'] && $node->menu['title']) {
          $item = $node->menu;
          $item['path'] = ($item['path']) ? $item['path'] : "node/$node->nid";
          $item['type'] = $item['type'] | MENU_MODIFIED_BY_ADMIN;
          if ($item['mid']) {
Alexander Hass's avatar
Alexander Hass committed
            // Update menu item.
            db_query("UPDATE {menu} SET pid = %d, path = '%s', title = '%s', description = '%s', weight = %d, type = %d, language = '%s' WHERE mid = %d", $item['pid'], $item['path'], $item['title'], $item['description'], $item['weight'], $item['type'], $node->language, $item['mid']);
            drupal_set_message(t('The menu item %title has been updated with node language.', array('%title' => $item['title'])));
Alexander Hass's avatar
Alexander Hass committed
          }
          elseif (SAVED_NEW == menu_save_item($item)) {
            // Creating new menu item with node language.
            db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", $node->language, $item['mid']);
            drupal_set_message(t('The menu item %title has been added with node language.',  array('%title' => $item['title'])));
          }
          menu_rebuild();
Alexander Hass's avatar
Alexander Hass committed
          unset($node->menu); // Avoid further processing by menu module.
Alexander Hass's avatar
Alexander Hass committed
        // Pathauto integration. Dynamic replacement of variables to allow different patterns per language.
        if (module_exists('path') && module_exists('pathauto')) {
Alexander Hass's avatar
Alexander Hass committed
          // Language for pathauto variables is either node language or default language.
          $language = $node->language ? $node->language : i18n_default_language();
          if ($language != i18n_get_lang()) {
            i18n_variable_init($language, 'pathauto_node');
          }
        }
Alexander Hass's avatar
Alexander Hass committed

      case 'delete':
        db_query('DELETE FROM {i18n_node} WHERE nid=%d', $node->nid);
        break;
Alexander Hass's avatar
Alexander Hass committed

      case 'prepare':
Alexander Hass's avatar
Alexander Hass committed
        // Book pages, set the right language nodes and outlines.
        if (arg(3) == 'parent' && is_numeric(arg(4)) && ($parent = node_load(arg(4))) && $parent->language) {
          $node->language = $parent->language;
          i18n_selection_mode('node', $parent->language);
Alexander Hass's avatar
Alexander Hass committed
}*/
/**
 * Implementation of hook_alter_translation_link().
 *
Alexander Hass's avatar
Alexander Hass committed
 * Handles links for extended language. The links will have current language.
 */
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);
    }
  }
}

/**
 * Implementation of hook_link_alter().
 *
Alexander Hass's avatar
Alexander Hass committed
 * 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]);
      }
    }
  }

    foreach (array_keys(i18n_language_list('extended')) as $langcode) {
Alexander Hass's avatar
Alexander Hass committed
      $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;
        }
Alexander Hass's avatar
Alexander Hass committed
 * Implementation of hook_user().
Alexander Hass's avatar
Alexander Hass committed
 * Switch to user's language after login.
 */
function i18n_user($op, &$edit, &$account, $category = NULL) {
Alexander Hass's avatar
Alexander Hass committed
  if ($op == 'login' && $account->language) {
    $_SESSION['language'] = $account->language;
    i18n_get_lang($account->language);
/**
 * Implementation of hook_elements().
 *
 * Add a process callback for textfields.
 */
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;
}

Alexander Hass's avatar
Alexander Hass committed
 * Get language properties.
 * @param $code
Alexander Hass's avatar
Alexander Hass committed
 *   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;
Alexander Hass's avatar
Alexander Hass committed
 * Get node language.
 */
function i18n_node_get_lang($nid, $default = '') {
Alexander Hass's avatar
Alexander Hass committed
  $lang = db_result(db_query('SELECT language FROM {node} WHERE nid = %d', $nid));
  return $lang ? $lang : $default ;
}

/**
Alexander Hass's avatar
Alexander Hass committed
 * Get allowed languages for node.
Alexander Hass's avatar
Alexander Hass committed
 * This allows node types to define its own language list implementing hook 'language_list'.
 * @param $node
Alexander Hass's avatar
Alexander Hass committed
 *   Node to retrieve language list for.
 * @param $translate
Alexander Hass's avatar
Alexander Hass committed
 *   Only languages available for translation. Filter out existing translations.
 */
function i18n_node_language_list($node, $translate = FALSE) {
Alexander Hass's avatar
Alexander Hass committed
  // 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
Alexander Hass's avatar
Alexander Hass committed
    }
    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 $languages;
Alexander Hass's avatar
Alexander Hass committed
 * Warning: when used with params they need to be escaped, as some values are thrown directly in queries.
Alexander Hass's avatar
Alexander Hass committed
 * 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_value = '';
  static $store = array();
Alexander Hass's avatar
Alexander Hass committed
  // Initialization, first time this runs with no explicit mode.
  if (!$current_mode && !$mode) {
    $current_mode = variable_get('i18n_selection_mode', 'simple');
Alexander Hass's avatar
Alexander Hass committed
      // Node language when loading specific nodes or creating translations.
        if (($node = menu_get_object('node')) && $node->language) {
          $current_mode = 'node';
          $current_value = $node->language;
Alexander Hass's avatar
Alexander Hass committed
        }
        elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language'])) {
          $current_mode = 'translation';
          $current_value = db_escape_string($_GET['language']);
Alexander Hass's avatar
Alexander Hass committed
      }
      elseif (arg(0) == 'admin') {
        // There are some exceptions for admin pages.
        if (arg(1) == 'content' && user_access('administer all languages')) {
Alexander Hass's avatar
Alexander Hass committed
          // No restrictions for administration pages.
Alexander Hass's avatar
Alexander Hass committed
        }
        elseif (arg(1) == 'build' && arg(2) == 'menu-customize') {
          // All nodes available when editing custom menu items.
Alexander Hass's avatar
Alexander Hass committed
  if (!$mode) {
    return $current_mode;
Alexander Hass's avatar
Alexander Hass committed
  }
  elseif ($mode == 'params') {
    return $current_value;
Alexander Hass's avatar
Alexander Hass committed
  }
  elseif ($mode == 'reset') {
    list($current_mode, $current_value) = array_pop($store);
Alexander Hass's avatar
Alexander Hass committed
  }
  else {
    array_push($store, array($current_mode, $current_value));
    $current_mode = $mode;
    $current_value = $params;
Alexander Hass's avatar
Alexander Hass committed
 * Implementation of hook_db_rewrite_sql().
Alexander Hass's avatar
Alexander Hass committed
 * Rewrite node queries so language selection options are enforced.
Alexander Hass's avatar
Alexander Hass committed
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;
Alexander Hass's avatar
Alexander Hass committed
  // Disable language conditions for views.
  if (array_key_exists('view', $args)) return;
  switch ($primary_table) {
    case 'n':
    case 'node':
Alexander Hass's avatar
Alexander Hass committed
      // No rewrite for queries with subselect ? (views count queries).
      // @ TO DO Actually these queries look un-rewrittable, check with other developers.
Alexander Hass's avatar
Alexander Hass committed
      // No rewrite for translation module queries.
      if (preg_match("/.*FROM {node} $primary_table WHERE.*$primary_table\.tnid/", $query)) return;
Alexander Hass's avatar
Alexander Hass committed
      // When loading specific nodes, language conditions shouldn't apply.
      if (preg_match("/WHERE.*\s$primary_table.nid\s*=\s*(\d|%d)/", $query)) return;
Alexander Hass's avatar
Alexander Hass committed
      // 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
Alexander Hass's avatar
Alexander Hass committed
      // and add some more conditions, but only if language is not default.
        $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";
Alexander Hass's avatar
Alexander Hass committed
      }
      else {
        $result['where'] = i18n_db_rewrite_where($primary_table, 'node', $mode);
      }
      return $result;
Alexander Hass's avatar
Alexander Hass committed
 * Rewrites queries depending on rewriting mode.
Alexander Hass's avatar
Alexander Hass committed
function i18n_db_rewrite_where($alias, $type, $mode = NULL) {
  if (!$mode) {
Alexander Hass's avatar
Alexander Hass committed
    // Some exceptions for query rewrites.
    $mode = i18n_selection_mode();
Alexander Hass's avatar
Alexander Hass committed
  // Get languages to simplify query building.
  $current = i18n_get_lang();
  $default = i18n_default_language();
Alexander Hass's avatar
Alexander Hass committed
    // Special case. Selection mode is 'strict' but this should be only for node queries.
  elseif ($mode == 'mixed' && $current == $default) {
Alexander Hass's avatar
Alexander Hass committed
    // If mode is mixed but current = default, is the same as 'simple'.
Alexander Hass's avatar
Alexander Hass committed
  switch ($mode) {
    case 'off':
      return '';
Alexander Hass's avatar
Alexander Hass committed

    case 'simple':
      return "$alias.language ='$current' OR $alias.language ='' OR $alias.language IS NULL" ;
Alexander Hass's avatar
Alexander Hass committed

    case 'mixed':
      return "$alias.language ='$current' OR $alias.language ='$default' OR $alias.language ='' OR $alias.language IS NULL" ;
Alexander Hass's avatar
Alexander Hass committed

    case 'strict':
Alexander Hass's avatar
Alexander Hass committed

    case 'node':
    case 'translation':
Alexander Hass's avatar
Alexander Hass committed
      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" ;
Alexander Hass's avatar
Alexander Hass committed

    case 'custom':
      return str_replace('%alias', $alias, i18n_selection_mode('params'));
Alexander Hass's avatar
Alexander Hass committed
 * Implementation of hook_exit().
Alexander Hass's avatar
Alexander Hass committed
function i18n_exit() {
  _i18n_variable_exit();
 * Implementation of hook_form_alter();
Alexander Hass's avatar
Alexander Hass committed
 * This is the place to add language fields to all forms.
function i18n_form_alter(&$form, $form_state, $form_id) {
Alexander Hass's avatar
Alexander Hass committed
  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,
      );
Alexander Hass's avatar
Alexander Hass committed
      // Add extended language support option to content type form.
      $form['i18n']['i18n_node'] = array(
Alexander Hass's avatar
Alexander Hass committed
        '#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,
Alexander Hass's avatar
Alexander Hass committed
      );
      break;
      if (isset($form['#id']) && $form['#id'] == 'node-form') {
          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);
            }
Alexander Hass's avatar
Alexander Hass committed
          }
          elseif (!isset($form['#node']->nid)) {
            // Set language to empty for not multilingual nodes when creating
            $form['language'] = array('#type' => 'value', '#value' => '');
Alexander Hass's avatar
Alexander Hass committed
      // Multilingual variables in settings form.
      if (isset($form['#theme']) && $form['#theme'] == 'system_settings_form' && $variables = variable_get('i18n_variables', 0)) {
        if (i18n_form_alter_settings($form, $variables)) {
          $form['#submit'][] = 'i18n_variable_form_submit';
/**
 * 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');
}

/**
 * Implementation of hook_theme()
 */
function i18n_theme() {
  return array(
    'i18n_node_select_translation' => array(
      'arguments' => array('element' => NULL),
      'file' => 'i18n.pages.inc',
    ),
  );
Alexander Hass's avatar
Alexander Hass committed

/**
 * 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';
}

Alexander Hass's avatar
Alexander Hass committed
 * Check for multilingual variables in form.
 */
function i18n_form_alter_settings(&$form, &$variables) {
  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)) {
Alexander Hass's avatar
Alexander Hass committed
      $form[$field]['#description'] .= ' <strong>'. t('This is a multilingual variable.') .'</strong>';
      $result++;
    }
  }
  return $result;
}

/**
Alexander Hass's avatar
Alexander Hass committed
 * 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 = variable_get('i18n_variables', array());
  $language = i18n_get_lang();
  foreach ($form_state['values'] as $key => $value) {
    if (in_array($key, $variables)) {
      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);
  }
  // Re-submit form
  // system_settings_form_submit($form_id, $form_values);
Alexander Hass's avatar
Alexander Hass committed
 * Initialization of multilingual variables.
 * @param $language
Alexander Hass's avatar
Alexander Hass committed
 *   Language to retrieve variables. Defaults to current language.
 * @param $prefix
Alexander Hass's avatar
Alexander Hass committed
 *   Variable name prefix to load just a selected group of variables.
Alexander Hass's avatar
Alexander Hass committed
function i18n_variable_init($langcode = NULL, $prefix = '') {
  global $conf;
  global $i18n_conf;
  $langcode = $langcode ? $langcode : i18n_get_lang();
Alexander Hass's avatar
Alexander Hass committed
  if ($i18n_variables = variable_get('i18n_variables', '')) {
    if (!$i18n_conf) {
      $i18n_conf = array();
    }
    $variables = _i18n_variable_init($langcode, $prefix);
Alexander Hass's avatar
Alexander Hass committed
    foreach ($i18n_variables as $name) {
      $i18n_conf[$name] = isset($variables[$name]) ? $variables[$name] : (isset($conf[$name]) ? $conf[$name] : '');
    }
    $conf = array_merge($conf, $i18n_conf);
  }
}

/**
 * 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'];
    }
  }
}

Alexander Hass's avatar
Alexander Hass committed
 * Helper function to create language selector.
Alexander Hass's avatar
Alexander Hass committed
function _i18n_language_select($value ='', $description ='', $weight = -20, $languages = NULL) {
  $languages = $languages ? $languages : locale_language_list();
  return array(
Alexander Hass's avatar
Alexander Hass committed
    '#type' => 'select',
    '#title' => t('Language'),
    '#default_value' => $value,
    '#options' => array_merge(array('' => ''), $languages),
    '#description' => $description,
    '#weight' => $weight,
Alexander Hass's avatar
Alexander Hass committed
 * Load language variables into array.
Alexander Hass's avatar
Alexander Hass committed
function _i18n_variable_init($language, $prefix = '') {
  $variables = array();
Alexander Hass's avatar
Alexander Hass committed
  $cacheid = 'variables:'. $language . ($prefix ? ':'. $prefix : '');
  if ($cached = cache_get($cacheid)) {
    $variables = $cached->data;
    $result = db_query("SELECT * FROM {i18n_variable} WHERE language='%s' AND name LIKE '%s%'", $language, $prefix);
    while ($variable = db_fetch_object($result)) {
      $variables[$variable->name] = unserialize($variable->value);
    }
    cache_set($cacheid, $variables);
  return $variables;
Alexander Hass's avatar
Alexander Hass committed
 * Save multilingual variables that may have been changed by other methods than settings pages.
Alexander Hass's avatar
Alexander Hass committed
function _i18n_variable_exit() {
  global $i18n_conf;
  global $conf;
Alexander Hass's avatar
Alexander Hass committed
  if ($i18n_conf) {
    $lang = i18n_get_lang();
    $refresh = FALSE;
Alexander Hass's avatar
Alexander Hass committed
    // Rewritten because array_diff_assoc may fail with array variables.
    foreach ($i18n_conf as $name => $value) {
      if ($value != $conf[$name]) {
        $refresh = TRUE;
        $i18n_conf[$name] = $conf[$name];
        db_query("DELETE FROM {i18n_variable} WHERE name='%s' AND language='%s'", $name, $lang );
        db_query("INSERT INTO {i18n_variable} (language, name, value) VALUES('%s', '%s', '%s')", $lang, $name, serialize($conf[$name]));
      }
    }
Alexander Hass's avatar
Alexander Hass committed
    if ($refresh) {
      cache_set('variables:'. $lang, $i18n_conf);

/**
 * Check whether we are in bootstrap mode
Alexander Hass's avatar
Alexander Hass committed
function _i18n_is_bootstrap() {
  return !function_exists('drupal_get_headers');

/**
 * Drupal 6, backwards compatibility layer
 * @ TO DO Fully upgrade all the modules and remove
 */
/**
 * This one expects to be called first from common.inc
 */
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);
Alexander Hass's avatar
Alexander Hass committed

    case 'extended':
      $enabled = locale_language_list($field);
      $defined = locale_language_list($field, TRUE);
      return array_diff_assoc($defined, $enabled);
Alexander Hass's avatar
Alexander Hass committed
 * Returns default language code.
Alexander Hass's avatar
Alexander Hass committed
function i18n_default_language() {
  return language_default('language');
Alexander Hass's avatar
Alexander Hass committed
 * Get list of supported languages, native name.
 * @param $all
Alexander Hass's avatar
Alexander Hass committed
 *   TRUE to get all defined languages.
 */
function i18n_supported_languages($all = FALSE) {
  return locale_language_list('native', $all);
}

/**
 * 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
Alexander Hass's avatar
Alexander Hass committed
 *   Language code.
 */
function i18n_variable_set($name, $value, $langcode) {
  global $conf, $i18n_conf;

  db_lock_table('i18n_variable');
  db_query("DELETE FROM {i18n_variable} WHERE name = '%s' AND language='%s'", $name, $langcode);
  db_query("INSERT INTO {i18n_variable} (name, language, value) VALUES ('%s', '%s', '%s')", $name, $langcode, serialize($value));
  db_unlock_tables();

Alexander Hass's avatar
Alexander Hass committed
  cache_clear_all('variables:'. $langcode, 'cache');

  $conf[$name] = $value;
  $i18n_conf[$name] = $value;
}

/**
 * Unset a persistent multilingual variable.
 *
 * @param $name
 *   The name of the variable to undefine.
 * @param $langcode
Alexander Hass's avatar
Alexander Hass committed
 *   Language code.
 */
function i18n_variable_del($name, $langcode) {
  global $conf, $i18n_conf;

  db_query("DELETE FROM {i18n_variable} WHERE name = '%s' AND language='%s'", $name, $langcode);
Alexander Hass's avatar
Alexander Hass committed
  cache_clear_all('variables:'. $langcode, 'cache');

  unset($conf[$name]);
  unset($i18n_conf[$name]);
}

/**
Alexander Hass's avatar
Alexander Hass committed
 * Utility. Get part of array variable.
 */
function i18n_array_variable_get($name, $element, $default = NULL) {
  if (($values = variable_get($name, array())) && isset($values[$element])) {
    return $values[$element];
Alexander Hass's avatar
Alexander Hass committed
  }
  else {
    return $default;
Alexander Hass's avatar
Alexander Hass committed
 * 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".
 */
Alexander Hass's avatar
Alexander Hass committed
/**
 * List of language support modes for content.
 */
function _i18n_content_language_options() {
  return array(
    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.'),