Index: i18nmenu/i18nmenu.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/i18n/i18nmenu/Attic/i18nmenu.install,v
retrieving revision 1.1.2.4
diff -u -p -r1.1.2.4 i18nmenu.install
--- i18nmenu/i18nmenu.install	18 Jan 2009 17:52:23 -0000	1.1.2.4
+++ i18nmenu/i18nmenu.install	1 Jul 2010 10:03:40 -0000
@@ -14,4 +14,29 @@
 function i18nmenu_enable() {
   drupal_load('module', 'i18nstrings');
   i18nmenu_locale_refresh();
-}
\ No newline at end of file
+  i18nmenu_node_item_translations_refresh_all();
+}
+
+/**
+ * Implementation of hook_uninstall().
+ *
+ * Delete all variables.
+ */
+function i18nmenu_uninstall() {
+  $result = db_query('SELECT name FROM {variable} WHERE name LIKE \'i18nmenu_node_translation_%%\'');
+  while ($row = db_fetch_object($result)) {
+    variable_del($row->name);
+  }
+  i18nmenu_node_item_translations_refresh_all();
+}
+
+/**
+ * Disable node translation by default for existing sites: administrator will
+ * have to restore defaults manually if they need so.
+ */
+function i18nmenu_update_6001() {
+  foreach (menu_get_menus() as $menu_name => $menu_title) {
+    variable_set("i18nmenu_node_translation_$menu_name", FALSE);
+  }
+  return array();
+}
Index: i18nmenu/i18nmenu.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/i18n/i18nmenu/i18nmenu.module,v
retrieving revision 1.2.2.21
diff -u -p -r1.2.2.21 i18nmenu.module
--- i18nmenu/i18nmenu.module	25 May 2010 13:57:20 -0000	1.2.2.21
+++ i18nmenu/i18nmenu.module	1 Jul 2010 10:18:42 -0000
@@ -24,6 +24,26 @@ function i18nmenu_locale($op = 'groups',
 }
 
 /**
+ * Implementation of hook_block().
+ */
+function i18nmenu_block($op = 'list', $delta = 0) {
+  switch ($op) {
+    case 'list':
+      $blocks = menu_block($op, $delta);
+      foreach ($blocks as $delta => $block) {
+        $blocks[$delta]['info'] = '['. t('i18n') .'] '. $blocks[$delta]['info'];
+      }
+      return $blocks;
+
+    case 'view':
+      i18n_selection_mode('off');
+      $result = menu_block($op, $delta);
+      i18n_selection_mode('reset');
+      return $result;
+  }
+}
+
+/**
  * Refresh locale strings.
  */
 function i18nmenu_locale_refresh() {
@@ -50,6 +70,12 @@ function i18nmenu_menu_link_alter(&$item
   elseif (isset($item['language'])) {
     unset($item['options']['langcode']);
   }
+
+  // Check if node translation is enabled.
+  $node_translation = !empty($item['i18nmenu_node_translation']) || (!isset($item['i18nmenu_node_translation']) && isset($item['options']['translations']));
+  // Remove translations: they will be rebuilt below, if necessary.
+  unset($item['options']['translations']);
+
   // If we are handling custom menu items of menu module and no language is set,
   // invoke translation via i18nstrings module.
   if (empty($item['language']) && $item['module'] == 'menu') {
@@ -58,10 +84,40 @@ function i18nmenu_menu_link_alter(&$item
     // Setting the alter option to true ensures that
     // hook_translated_menu_link_alter() will be called.
     $item['options']['alter'] = TRUE;
+    // We store here the current translation set to allow node translation.
+    $map = explode('/', $item['link_path']);
+    if ($node_translation && $map[0] == 'node' && isset($map[1]) && intval($map[1])) {
+      // Get the translation set id from the menu item path.
+      $tnid = _i18nmenu_node_get_tnid($map[1]);
+      if ($tnid) {
+        // We always store the source node nid to allow a correct handling of
+        // the active trail in i18nmenu_nodeapi() and to improve performance in
+        // i18nmenu_node_item_translations_refresh_set().
+        $map[1] = $tnid;
+        // Use the source node nid in the link path.
+        $item['link_path'] = implode('/', $map);
+        // We store here the current translation set to allow later translation.
+        $item['options']['translations'] = i18nmenu_node_get_translations($tnid, NULL);
+      }
+      else {
+        $item['options']['translations'] = array();
+      }
+    }
   }
 }
 
 /**
+ * Return the translation set id for the give nid.
+ */
+function _i18nmenu_node_get_tnid($nid) {
+  static $tnids = array();
+  if (!isset($tnids[$nid])) {
+    $tnids[$nid] = db_fetch_object(db_query("SELECT n.tnid FROM {node} n WHERE n.nid = %d", $nid))->tnid;
+  }
+  return $tnids[$nid];
+}
+
+/**
  * Implementation of hook_translated_menu_link_alter().
  *
  * Translate menu links on the fly.
@@ -71,6 +127,11 @@ function i18nmenu_menu_link_alter(&$item
 function i18nmenu_translated_menu_link_alter(&$item, $map) {
   if ($item['module'] == 'menu') {
     $item['title'] = _i18nmenu_get_item($item);
+    if ($map[1] = i18nmenu_node_translation($item, $map)) {
+      // If there is a node translation available for the current language we
+      // replace its nid with the current one.
+      $item['href'] = implode('/', $map);
+    }
   }
 }
 
@@ -201,12 +262,12 @@ function i18nmenu_preprocess_page(&$vars
 /**
  * Optionally insert/update and return a localized menu item title.
  */
-function _i18nmenu_get_item($link, $update = FALSE) {
+function _i18nmenu_get_item($link, $update = FALSE, $langcode = NULL) {
   $key = 'menu:item:'. $link['mlid'] .':title';
   if ($update) {
     i18nstrings_update($key, $link['link_title']);
   }
-  return i18nstrings($key, $link['link_title']);
+  return i18nstrings($key, $link['link_title'], $langcode);
 }
 
 /**
@@ -233,14 +294,22 @@ function i18nmenu_get_router($path) {
 /**
  * Implementation of hook_form_form_id_alter().
  *
- * Register a submit callback to process menu title.
+ * Add a checkbox to the menu_edit_menu form to enable path translation for node
+ * links and register a submit callback to process menu title.
  */
 function i18nmenu_form_menu_edit_menu_alter(&$form, $form_state) {
+  $form['submit']['#weight'] = 9;
+  $form['i18nmenu_node_translation'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable node translation by default'),
+    '#description' => t('If this is checked the menu item forms will have the <em>Enable node translation</em> option checked by default.'),
+    '#default_value' => _i18nmenu_node_translation_enabled($form['menu_name']['#value']),
+  );
   $form['#submit'][] = 'i18nmenu_menu_edit_menu_submit';
 }
 
 /**
- * Submit handler for the menu_edit_item form.
+ * Submit handler for the menu_edit_menu form.
  *
  * On menu item insert or update, save a translation record.
  */
@@ -254,6 +323,11 @@ function i18nmenu_menu_edit_menu_submit(
     }
     i18nstrings_update_string($context, $form_state['values']['title']);
   }
+
+  // Save the node translation setting for the current menu.
+  $menu = ($form['#insert'] ? 'menu-' : '') . $form['menu_name']['#value'];
+  $value = $form_state['values']['i18nmenu_node_translation'];
+  variable_set("i18nmenu_node_translation_$menu", $value);
 }
 
 /**
@@ -269,6 +343,7 @@ function i18nmenu_form_menu_edit_item_al
   else {
     $language = '';
   }
+
   $form['menu']['language'] = array(
     '#type' => 'select',
     '#title' => t('Language'),
@@ -276,6 +351,19 @@ function i18nmenu_form_menu_edit_item_al
     '#options' => array('' => t('All languages')) + locale_language_list('name'),
     '#default_value' => $language,
   );
+
+  $router_item = menu_get_item();
+  $default = !empty($form['menu']['#item']) ?
+    !empty($form['menu']['#item']['options']['translations']) :
+    _i18nmenu_node_translation_enabled($router_item['page_arguments'][3]['menu_name']);
+
+  $form['menu']['i18nmenu_node_translation'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable node translation'),
+    '#description' => t('If this is checked node link paths will be replaced with the node translation paths.'),
+    '#default_value' => $default,
+  );
+
   array_unshift($form['#validate'], 'i18nmenu_menu_item_prepare_normal_path');
   $form['#submit'][] = 'i18nmenu_menu_item_update';
 }
@@ -316,7 +404,7 @@ function i18nmenu_menu_item_update($form
 function _i18nmenu_update_item($item) {
   list($item['menu_name'], $item['plid']) = explode(':', $item['parent']);
   // If this was an insert, determine the ID that was set.
-  if (!isset($link['mlid'])) {
+  if (!isset($item['mlid'])) {
     $item['mlid'] = db_result(db_query("SELECT MAX(mlid) FROM {menu_links} WHERE link_path = '%s' AND menu_name = '%s' AND module = 'menu' AND plid = %d AND link_title = '%s'", $item['link_path'], $item['menu_name'], $item['plid'], $item['link_title']));
   }
   if (!empty($item['mlid'])) {
@@ -350,10 +438,22 @@ function i18nmenu_form_alter(&$form, &$f
   if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
     $node = $form['#node'];
 
-    // Do nothing if the node already has a menu.
+    $node_translation_form = array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable translations'),
+      '#description' => t('If this is checked the menu item will be shared among all the node translations, however you will still be able to translate the menu link title.'),
+    );
+
+    // If the node already has a menu just add the node translation option.
     if (!empty($node->menu['mlid'])) {
+      $node_translation_form['#default_value'] = isset($node->menu['options']['translations']);
+      $form['menu']['i18nmenu_node_translation'] = $node_translation_form;
       return;
     }
+    else {
+      $menu_name = variable_get('menu_default_node_menu', 'primary-links');
+      $node_translation_form['#default_value'] = _i18nmenu_node_translation_enabled($menu_name);
+    }
 
     // Find the translation source node.  If creating a new node,
     // translation_source is set.  Otherwise, node_load the tnid.
@@ -367,6 +467,7 @@ function i18nmenu_form_alter(&$form, &$f
     }
     // If no translation node, return.
     else {
+      $form['menu']['i18nmenu_node_translation'] = $node_translation_form;
       return;
     }
 
@@ -374,28 +475,63 @@ function i18nmenu_form_alter(&$form, &$f
     node_object_prepare($tnode);
 
     if ($tnode->menu) {
-      // Set default values based on translation source's menu.
-      $form['menu']['link_title']['#default_value'] = $tnode->menu['link_title'];
-      $form['menu']['weight']['#default_value'] = $tnode->menu['weight'];
-      $form['menu']['parent']['#default_value'] = $tnode->menu['menu_name'] .':'. $tnode->menu['plid'];
+      $form['#tnode'] = $tnode;
+
+      if (isset($tnode->menu['options']['translations'])) {
+        // If node translation is enabled we share one single menu item among
+        // all the nodes belonging to the translation set.
+        $form['menu']['link_title_translation'] = $form['menu']['link_title'];
+        $form['menu']['link_title_translation']['#title'] = t('Menu title translation');
+        $default = _i18nmenu_get_item($tnode->menu, TRUE, $form['language']['#default_value']);
+        if (!empty($default)) {
+          $form['menu']['link_title_translation']['#default_value'] = $default;
+          $form['menu']['#collapsed'] = FALSE;
+        }
+
+        unset($form['menu']['link_title']);
+        unset($form['menu']['parent']);
+        unset($form['menu']['weight']);
 
-      // Set menu language to node language.
-      $form['menu']['language'] = array('#type' => 'value', '#value' => $node->language);
+        $form['#submit'][] = 'i18nmenu_form_node_submit';
+      }
+      else {
+        // Set default values based on translation source's menu.
+        $form['menu']['link_title']['#default_value'] = $tnode->menu['link_title'];
+        $form['menu']['weight']['#default_value'] = $tnode->menu['weight'];
+        $form['menu']['parent']['#default_value'] = $tnode->menu['menu_name'] .':'. $tnode->menu['plid'];
 
-      // Customized must be set to 1 to save language.
-      $form['menu']['customized'] = array('#type' => 'value', '#value' => 1);
+        // Set menu language to node language.
+        $form['menu']['language'] = array('#type' => 'value', '#value' => $node->language);
+
+        // Customized must be set to 1 to save language.
+        $form['menu']['customized'] = array('#type' => 'value', '#value' => 1);
+      }
     }
   }
 }
 
 /**
+ * Submit handler for the node form.
+ */
+function i18nmenu_form_node_submit($form, &$form_state) {
+  $values =& $form_state['values'];
+  $values['status_changed'] = $form['#node']->status != $values['status'];
+  if (!empty($form['#tnode']->menu['link_title']) && !empty($values['menu']['link_title_translation'])) {
+    i18nstrings_save_translation($values['language'], $form['#tnode']->menu['link_title'], $values['menu']['link_title_translation'], 'menu');
+  }
+}
+
+/**
  * Implementation of hook_nodeapi().
  *
  * Save or delete menu item strings associated with nodes.
  */
-function i18nmenu_nodeapi(&$node, $op) {
+function i18nmenu_nodeapi(&$node, $op, $a3, $page) {
   switch ($op) {
     case 'insert':
+      if ($node->translation_source) {
+        i18nmenu_node_item_translations_refresh_set($node->translation_source->nid);
+      }
     case 'update':
       if (isset($node->menu)) {
         $item = $node->menu;
@@ -408,6 +544,9 @@ function i18nmenu_nodeapi(&$node, $op) {
           _i18nmenu_update_item($item);
         }
       }
+      if ($node->tnid && !empty($node->status_changed)) {
+        i18nmenu_node_item_translations_refresh_set($node->tnid);
+      }
       break;
     case 'delete':
       // Delete all menu item link translations that point to this node.
@@ -415,6 +554,9 @@ function i18nmenu_nodeapi(&$node, $op) {
       while ($m = db_fetch_array($result)) {
         _i18nmenu_delete_item($m['mlid']);
       }
+      if ($node->tnid) {
+        i18nmenu_node_item_translations_refresh_set($node->tnid);
+      }
       break;
     case 'prepare translation':
       if (empty($node->menu['mlid']) && !empty($node->translation_source)) {
@@ -425,5 +567,169 @@ function i18nmenu_nodeapi(&$node, $op) {
         $node->menu['weight'] = $tnode->menu['weight'];
       }
       break;
+    case 'view':
+      if ($page) {
+        i18nmenu_node_set_item($node);
+      }
+      break;
+  }
+}
+
+/**
+ * Set as current menu item the source node to correctly handle the active
+ * trail.
+ */
+function i18nmenu_node_set_item($node) {
+  if (!empty($node->tnid) && $node->tnid != $node->nid) {
+    $item = menu_get_item();
+    $href = explode('/', $item['href']);
+    $item['original_href'] = $href;
+    $href[1] = $node->tnid;
+    $item['href'] = implode('/', $href);
+    menu_set_item(NULL, $item);
+  }
+}
+
+/**
+ * Return the nid of the node translation for the given language if available.
+ */
+function i18nmenu_node_translation($item, $map, $langcode = NULL) {
+  if (empty($item['options']['translations']) || !_i18nmenu_node_translation_enabled($item['menu_name'])) {
+    return FALSE;
+  }
+
+  // If the current item is also the active item, in $map[1] we have a node
+  // object instead of a simple nid.
+  $nid = is_object($map[1]) ? $map[1]->nid : $map[1];
+
+  if (empty($langcode)) {
+    // Retrieve the current language.
+    $langcode = i18n_get_lang();
+  }
+
+  // If the translation is available and is not the current element we use it.
+  // For performance reasons we check only if the node status is published or
+  // if the user can see unpublished nodes. Uncommon cases of fine-grained
+  // access-controlled translated node menu items might lead to "access denied"
+  // pages.
+  $translations = $item['options']['translations'];
+  if (is_array($translations) && isset($translations[$langcode]) && $translations[$langcode]->nid != $nid && ($translations[$langcode]->status || user_access('administer nodes'))) {
+    return $translations[$langcode]->nid;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Return TRUE if node item translation is enabled for the given menu.
+ */
+function _i18nmenu_node_translation_enabled($menu_name) {
+  return variable_get("i18nmenu_node_translation_$menu_name", TRUE);
+}
+
+/**
+ * Return an array of node translation nids keyed by language.
+ */
+function i18nmenu_node_get_translations($tnid, $field = 'nid') {
+  static $translations = array();
+
+  if (!isset($translations[$tnid])) {
+    $translations[$tnid] = array();
+    if ($tnid) {
+      $result = db_query("SELECT n.language, n.nid, n.status FROM {node} n WHERE n.tnid = %d", $tnid);
+      while ($row = db_fetch_object($result)) {
+        $translations[$tnid][$row->language] = $row;
+      }
+    }
+  }
+
+  if (!$field) {
+    return $translations[$tnid];
+  }
+  else {
+    $result = array();
+    foreach ($translations[$tnid] as $langcode => $node) {
+      $result[$langcode] = $node->$field;
+    }
+    return $result;
+  }
+}
+
+/**
+ * Implementation of hook_form_form_id_alter().
+ */
+function i18nmenu_form_i18n_node_select_translation_alter(&$form, $form_state) {
+  $form['#submit'][] = 'i18nmenu_node_translation_set_update';
+}
+
+/**
+ * Submit handler for i18n_node_select_translation form.
+ *
+ * Refresh menu items' translation cache if the translation set has changed.
+ */
+function i18nmenu_node_translation_set_update($form, &$form_state) {
+  $changed = FALSE;
+  $nids = array($form['node']['#value']->nid);
+
+  foreach ($form_state['values']['translations']['nid'] as $langcode => $nid) {
+    $previous = $form['translations']['nid'][$langcode]['#value'];
+    if ($previous != $nid) {
+      $changed = TRUE;
+    }
+    if ($previous) {
+      $nids[] = $previous;
+    }
+    if ($nid) {
+      $nids[] = $nid;
+    }
+  }
+
+  if ($changed) {
+    $nids = array_unique($nids);
+    i18nmenu_node_item_translations_refresh($nids);
+  }
+}
+
+/**
+ * Refresh the translation cache for any menu item belonging to the translation
+ * sets identified by the given tnid.
+ */
+function i18nmenu_node_item_translations_refresh_set($tnid) {
+  $nids = i18nmenu_node_get_translations($tnid);
+  if (empty($nids)) {
+    $nids = array($tnid);
+  }
+  i18nmenu_node_item_translations_refresh($nids);
+}
+
+/**
+ * Refresh the translation cache for any menu item belonging to the translation
+ * sets identified by the given nids.
+ */
+function i18nmenu_node_item_translations_refresh($nids) {
+  $conditions = implode(' OR ', array_fill(0, count($nids), "link_path LIKE 'node/%d%%'"));
+  $query = 'SELECT mlid FROM {menu_links} WHERE '. $conditions .' ORDER by mlid';
+  _i18nmenu_node_item_translations_refresh($query, $nids);
+}
+
+/**
+ * Refresh the translation cache for any menu item pointing to a node page.
+ */
+function i18nmenu_node_item_translations_refresh_all() {
+  // TODO: Consider using a batch process here.
+  $query = 'SELECT mlid FROM {menu_links} WHERE link_path LIKE \'node/%%\' ORDER by mlid';
+  _i18nmenu_node_item_translations_refresh($query);
+  watchdog('i18nmenu', 'Translation cache has been refreshed for all node menu items.', array(), WATCHDOG_INFO);
+}
+
+/**
+ * Helper function: actually refresh the menu items returned by the given query.
+ */
+function _i18nmenu_node_item_translations_refresh($query, $args = NULL) {
+  $result = db_query($query, $args);
+  while ($row = db_fetch_object($result)) {
+    // Refresh the item translation cache.
+    $item = menu_link_load($row->mlid);
+    menu_link_save($item);
   }
 }
