Index: localizer.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/localizer/localizer.module,v
retrieving revision 1.2.2.48
diff -u -p -r1.2.2.48 localizer.module
--- localizer.module	2 Apr 2007 21:50:31 -0000	1.2.2.48
+++ localizer.module	3 Apr 2007 15:49:00 -0000
@@ -1125,7 +1125,11 @@ function localizer_get_domain() {
 }
 
 /**
-* Disable controls on a form
+* Disable controls on a form.
+* 
+* This function actually does the negation of what you'd expect.
+* @todo Update all function calls to localizer_disable_fields().
+* 
 * @param form
 * form with the controls to disable
 * @param enabledcontrols
@@ -1143,13 +1147,55 @@ function localizer_formdisablecontrols(&
         continue;
       }
       else {
-        if($item['#type']=='select') $form[$key]['#attributes']['disabled']='disabled';
-        if($item['#type']=='checkbox') $form[$key]['#attributes']['disabled']='disabled';
-        if($item['#type']=='checkboxes') $form[$key]['#attributes']['disabled']='disabled';
-        if($item['#type']=='weight') $form[$key]['#attributes']['disabled']='disabled';
-        if($item['#type']=='radios') $form[$key]['#attributes']['disabled']='disabled';
-        if($item['#type']=='textfield') $form[$key]['#attributes']['disabled']='disabled';
-        if($item['#type']=='textarea') $form[$key]['#attributes']['disabled']='disabled';
+        if (in_array($item['#type'], array('select', 'checkbox', 'checkboxes', 'weight', 'radios', 'textfield', 'textarea'))) {
+          $form[$key]['#attributes']['disabled']='disabled';
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Disable certain fields in a form that can not be translated.
+ * 
+ * Each translation form contains fields that always need to be inherited from
+ * the source object. Since some fields are not submitted when they are
+ * disabled, the field property #required is removed from each disabled field.
+ * 
+ * This function expects the fieldnames as array keys. The value of a key does
+ * not matter. Most modules can re-use the fields defined in
+ * hook_localizer_fields().
+ * 
+ * Example:
+ * To disable the field 'myfield' in a form, $fields has to be
+ *    array('myfield' => 1)
+ * 
+ * There is no localizer_enable_fields(), since other modules can add arbitrary
+ * fields to a form. Disabling all but some not is not practicable.
+ * 
+ * @param $form
+ *    The form to process, passed by reference.
+ * @param $fields
+ *    An one-dimensional array containing the fieldnames to disable as keys.
+ * 
+ * @return
+ *    Nothing. $form is passed by reference.
+ */
+function localizer_disable_fields(&$form, $fields) {
+  reset($form);
+  while (list($key) = each($form)) {
+    $item = &$form[$key];
+    if($item['#type'] == 'fieldset') {
+      localizer_disable_fields($item, $fields);
+    }
+    else if (isset($fields[$key])) {
+      if (in_array($item['#type'], array('select', 'checkbox', 'checkboxes', 'weight', 'radios', 'textfield', 'textarea'))) {
+        $form[$key]['#attributes']['disabled'] = 'disabled';
+        
+        // Unset property #required for disabled fields.
+        if (isset($form[$key]['#required'])) {
+          unset($form[$key]['#required']);
+        }
       }
     }
   }
Index: models/localizernode.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/localizer/models/localizernode.php,v
retrieving revision 1.2.2.5
diff -u -p -r1.2.2.5 localizernode.php
--- models/localizernode.php	12 Mar 2007 19:11:08 -0000	1.2.2.5
+++ models/localizernode.php	4 Apr 2007 22:09:43 -0000
@@ -1,10 +1,11 @@
 <?php
 
-function localizernode_find($conditions=NULL, $howmany='all', $force=FALSE) {
-  static $localcache=array();
+function localizernode_find($conditions = NULL, $all = TRUE, $force = FALSE) {
+  static $localcache = array();
 
-  if(array_key_exists($conditions, $localcache) && isset($localcache[$conditions . ":$howmany"]) && !$force) {
-    return $localcache[$conditions];
+  $cid = $conditions .':'. ($all ? 'all' : 'one');
+  if (isset($localcache[$cid]) && !$force) {
+    return $localcache[$cid];
   }
 
   $items = array();
@@ -20,7 +21,7 @@ function localizernode_find($conditions=
     $items[$item->nid]['locale'] = $item->locale;
   }
 
-  if($howmany=='one') {
+  if (!$all) {
     $oneitem = array();
     foreach($items as $nid=>$item) {
       foreach($item as $key=>$value) {
@@ -28,25 +29,20 @@ function localizernode_find($conditions=
       }
       break;
     }
-    $localecache[$conditions . ":$howmany"] = $oneitem;
+    $localecache[$cid] = $oneitem;
     return $oneitem;
   }
   else {
-    $localecache[$conditions . ":$howmany"] = $items;
+    $localecache[$cid] = $items;
     return $items;
   }
 }
 
-function localizernode_findone($conditions=NULL, $force=FALSE) {
-  return localizernode_find($conditions, 'one', $force);
-}
-
-function localizernode_findall($conditions=NULL, $force=FALSE) {
-  return localizernode_find($conditions, 'all', $force);
-}
-
-function localizernode_findbynid($nid, $force=FALSE) {
-  return localizernode_findone("nid=$nid", 'one', $force);
+/**
+ * Fetch a single node from translation index.
+ */
+function localizernode_findbynid($nid, $force = FALSE) {
+  return localizernode_find("nid=$nid", FALSE, $force);
 }
 
 function localizernode_save($item) {
@@ -71,9 +67,15 @@ function localizernode_update($item) {
   }
 }
 
+/**
+ * Delete a node reference in localizer's index.
+ * 
+ * @todo If a parent node is deleted, all translations of this node have to be
+ *       deleted, too.
+ */
 function localizernode_delete($nid) {
-  if($uid) {
-    db_query('DELETE FROM {localizernode} WHERE nid=' . $uid);
+  if ($nid) {
+    db_query('DELETE FROM {localizernode} WHERE nid='. $nid);
   }
 }
 
@@ -83,4 +85,3 @@ function localizernode_delete_all($condi
   }
 }
 
-?>
\ No newline at end of file
Index: modules/localizernode.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/localizer/modules/localizernode.install,v
retrieving revision 1.1.4.6
diff -u -p -r1.1.4.6 localizernode.install
--- modules/localizernode.install	29 Jan 2007 10:11:08 -0000	1.1.4.6
+++ modules/localizernode.install	4 Apr 2007 22:11:30 -0000
@@ -1,4 +1,6 @@
 <?php
+// $Id$
+
 function localizernode_install() {
   switch ($GLOBALS['db_type']) {
     case 'mysql':
@@ -11,7 +13,7 @@ function localizernode_install() {
         KEY localizernode_idx1 (pid),
         UNIQUE KEY localizernode_idx2 (locale,pid)
       ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
-    break;
+      break;
     case 'pgsql':
       db_query("CREATE TABLE {localizernode} (
         nid int4 NOT NULL ,
@@ -21,10 +23,13 @@ function localizernode_install() {
         UNIQUE (locale,pid)
       );");
       db_query("create index localizernode_pid_index on {localizernode} (pid)");
-    break;
+      break;
   }
   global $locale;
-  if(!$locale) $locale='en';
-  db_query("INSERT INTO {localizernode} (nid,locale,pid) SELECT nid,'" . $locale . "',nid FROM {node};");
+  if (!$locale) {
+    $locale = 'en';
+  }
+  // Import all existing nodes.
+  db_query("INSERT INTO {localizernode} (nid, locale, pid) SELECT nid, '%s', nid FROM {node}", $locale);
 }
-?>
+
Index: modules/localizernode.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/localizer/modules/localizernode.module,v
retrieving revision 1.2.2.21
diff -u -p -r1.2.2.21 localizernode.module
--- modules/localizernode.module	2 Apr 2007 21:50:34 -0000	1.2.2.21
+++ modules/localizernode.module	4 Apr 2007 22:12:04 -0000
@@ -1,11 +1,15 @@
 <?php
-include(drupal_get_path('module', 'localizer') . '/models/localizernode.php');
+// $Id$
 
 /**
 * Localizer node module.
-* @author Roberto Gerola, 2006, http://www.speedtech.it
+* @authors
+*   Roberto Gerola, 2006, http://www.speedtech.it
+*   Daniel F. Kudwien (sun), 2007, http://www.unleashedmind.com
 */
 
+include(drupal_get_path('module', 'localizer') . '/models/localizernode.php');
+
 /**
 * Implementation of the hook menu for adding localizernode menu items.
 */
@@ -26,22 +30,49 @@ function localizernode_menu($may_cache) 
 }
 
 /**
-* Implementation of hook nodeapi.
-* The translation mechanism acts here, for the "load" $op
-*/
+ * Implementation of hook_nodeapi().
+ */
 function localizernode_nodeapi(&$node, $op, $teaser, $page) {  
   switch ($op) {
     case 'load':
-      $localizernode = localizernode_findbynid($node->nid);
-      return array('localizernode_locale' => $localizernode['locale'], 'localizernode_pid' => $localizernode['pid']);
+      // Add the content's locale
+      $properties = localizernode_findbynid($node->nid);
+      if (isset($properties['locale'])) {
+        $properties = array(
+          'localizernode_locale' => $properties['locale'],
+          'localizernode_pid' => $properties['pid'],
+        );
+      }
+      else {
+        $properties = array(
+          'localizernode_locale' => localizer_get_defaultcontentlocale(),
+          'localizernode_pid' => $node->nid,
+        );
+      }
+      return $properties;
+      
+    case 'submit':
+      if(isset($node->localizernode_locale) && isset($node->localizernode_pid)) {
+        localizernode_update_fields($node);
+      }
       break;
+    
     case 'insert':
     case 'update':
-      $localizernode = array('nid'=>$node->nid, 'pid'=>$node->localizernode_pid, 'locale'=>$node->localizernode_locale);
-      if($node->localizernode_locale) {
+      if(isset($node->localizernode_locale)) {
+        if (!isset($node->localizernode_pid)) {
+          // Set parent node id for new nodes to itself.
+          $node->localizernode_pid = $node->nid;
+        }
+        $localizernode = array(
+          'nid' => $node->nid,
+          'pid' => $node->localizernode_pid,
+          'locale' => $node->localizernode_locale,
+        );
         localizernode_save($localizernode);
       }
       break; 
+      
     case 'delete':
       localizernode_delete($node->nid);
       break;
@@ -49,61 +80,155 @@ function localizernode_nodeapi(&$node, $
 }
 
 /**
-* Modify the form for every node adding the necessary fields for localizer module : pid and locale
-*/
+ * Prepopulates a form with source content values, disables associated form
+ * fields and renders a language selection form at the top of a node form.
+ */
 function localizernode_form_alter($form_id, &$form) { 
+  if (!isset($form['type']) && !isset($form['type']['#value'])) { 
+    return;
+  }
+  $type = $form['type']['#value'];
+  if ($form_id != $type .'_node_form') {
+    // Exit if this is not a node form.
+    return;
+  }
+  
+  if (arg(3) == 'localizernode' && arg(4) && arg(5)) {
+    // We are translating a node on node/add/type/localizernode/#/locale
+    global $user;
+    
+    // Load the source node and reset some values to defaults.
+    $source_node = node_load(arg(4));
+    $source_node->nid = NULL;
+    $source_node->vid = NULL;
+    $source_node->uid = $user->uid;
+    $source_node->created = '';
+    if (isset($source_node->menu)) {
+      $source_node->menu = NULL;
+    }
+    if (isset($source_node->path)) {
+      $source_node->path = NULL;
+    }
+    $source_node->localizernode_locale = arg(5);
+    
+    // Replace the form with one that is prepopulated with values of the
+    // source node.
+    $form = array_merge($form, drupal_retrieve_form($type .'_node_form', $source_node));
+    $form['localizernode_pid'] = array(
+      '#type' => 'value',
+      '#value' => arg(4),
+    );
+  }
 
-if (!isset($form['type'])) { 
-  return;
+  // Set parent node id for node/add and node/#/edit.
+  if (!isset($form['localizernode_pid'])) {
+    $form['localizernode_pid'] = array(
+      '#type' => 'value',
+      '#value' => $form['#node']->localizernode_pid,
+    );
+  }
+  
+  // Determine and set default language for new nodes.
+  if (!isset($form['#node']->localizernode_locale)) {
+    $form['#node']->localizernode_locale = variable_get('localizer_default_content_any', FALSE) ? '-' : localizer_get_defaultcontentlocale();
+  }
+
+  // If this content type is enabled for translation, render a language
+  // selection list at the top of the form.
+  // @todo Could be checked earlier, but we have to set localizernode_locale
+  //       on every node to store it in the localizernode index.
+  $enabled_content_types = variable_get('localizer_contents_types', array('page' => 'page', 'story' => 'story'));
+  if ($enabled_content_types[$type]) {
+    $options = localizer_available_contentlocales();
+    $options = array_merge(array('-' => 'Any') , $options);
+    $form['localizernode_locale'] = array(
+      '#type' => 'select',
+      '#title' => t('Locale'),
+      '#default_value' => $form['#node']->localizernode_locale,
+      '#options' => $options,
+      '#required' => FALSE,
+      '#weight' => -18,
+    );
+  }
+  else {
+    $form['localizernode_locale'] = array(
+      '#type' => 'hidden',
+      '#default_value' => $form['#node']->localizernode_locale,
+    );
+  }
+  
+  // If we are in a translation form, then disable all prepopulated fields.
+  if (isset($form['#node']->localizernode_pid)) {
+    // This test is the key for detecting a translation form.
+    $parent_node = localizernode_findbynid($form['#node']->localizernode_pid);
+    if ($parent_node['locale'] != $form['#node']->localizernode_locale) {
+      // Retrieve a list of all node fields that have to be updated.
+      $disable_fields = module_invoke_all('localizer_fields');
+      localizer_disable_fields($form, $disable_fields);
+    }
+  }
 }
 
-switch ($form_id) { 
-  case $form['type']['#value'] .'_node_form':
-    if(arg(3) == 'localizernode') {
-      // We are translating a node in the form : node/add/type/localizernode/nid/lang
-      global $user;
-      $source_nid = arg(4);
-      $target_locale = arg(5);
-
-      $node = node_load($source_nid);
-      $node->nid = NULL;
-      $node->uid = $user->uid;
-      $node->created = 0;
-      $node->menu = NULL;
-      $node->path = NULL;
-      $node->localizernode_locale = $target_locale;
-
-      node_save($node);
-      drupal_goto('node/'. $node->nid . '/edit');
-    }
-
-    if(!isset($form['#node']->localizernode_locale)) {
-      $form['#node']->localizernode_locale = variable_get('localizer_default_content_any', FALSE) ? '-' : localizer_get_defaultcontentlocale();
-    }
-
-    $contents_types_enabled = variable_get('localizer_contents_types', array('page'=>'page', 'story'=>'story'));
-    if($contents_types_enabled[$form['type']['#value']]) {
-      $options = localizer_available_contentlocales();
-      $options = array_merge(array('-' => 'Any') , $options);
-      $form['localizernode_locale'] = array(
-        '#type' => 'select',
-        '#title' => t('Locale'),
-        '#default_value' => $form['#node']->localizernode_locale,
-        '#options' => $options,
-        '#required' => FALSE,
-        '#weight' => -18,
-      );
-    }
-    else
-    {
-      $form['localizernode_locale'] = array(
-        '#type' => 'hidden',
-        '#default_value' => $form['#node']->localizernode_locale,
-      );
+/**
+ * Updates fields in translations of a node.
+ * 
+ * If a translation of a node is saved, all disabled values have to be
+ * cloned from the parent node. Thus, all disabled fields defined by the
+ * content type are copied from the parent node to the translated node.
+ * 
+ * If a parent node is saved, all translations of this node have to be
+ * updated to synchronize all changes.
+ * 
+ * To achieve this, localizernode implements hook_localizer_fields(). Each
+ * module providing a node type is able to define type-specific fields that
+ * need to inherit the field values of the parent node in the translation set.
+ *
+ * Additionally, synchronizing disabled values on hook_save() is also the only
+ * way to properly disable form fields through localizer_disable_fields(),
+ * because values of disabled select fields are not submitted.
+ * 
+ * @param $node
+ *    A node object to process.
+ * 
+ * @return
+ *    Nothing. $node is passed by reference.
+ */
+function localizernode_update_fields(&$node) {
+  // If pid of the processed node is equal to the node's nid, this is a parent
+  // node. We then fetch and process all translations of this node instead.
+  if ($node->nid == $node->localizernode_pid) {
+    $nodes = localizernode_find("pid = $node->nid AND nid != $node->nid");
+    foreach ($nodes as $nid => $item) {
+      $localizernode = node_load($nid);
+      _localizernode_update_fields($localizernode);
     }
+  }
+  else {
+    _localizernode_update_fields($node);
+  }
+}
 
-    break;
-   }
+/**
+ * Helper function for localizernode_update_fields().
+ */
+function _localizernode_update_fields(&$node) {
+  // Retrieve a list of all node fields that have to be updated.
+  $update_fields = module_invoke_all('localizer_fields');
+  
+  // Load field values from parent node.
+  $source_node = node_load($node->localizernode_pid);
+  
+  // Update fields in translated node.
+  foreach ($source_node as $field => $value) {
+    if (isset($update_fields[$field])) {
+      if (is_string($update_fields[$field])) {
+        $node->$field = call_user_func($update_fields[$field], $value);
+      }
+      else {
+        $node->$field = $value;
+      }
+    }
+  }
 }
 
 /**
@@ -117,7 +242,7 @@ function localizernode_node_overview($no
   $header = array(t('Language'), t('Title'), t('Options'));
   foreach($languages as $lang => $langname){
     $options = array();
-    $localizernode=localizernode_findone('pid=' . $node->localizernode_pid . " AND locale='$lang'");
+    $localizernode=localizernode_find('pid='. $node->localizernode_pid ." AND locale='$lang'", FALSE);
     if($localizernode['nid']) {
       $trnode = db_fetch_object(db_query('SELECT n.nid, n.title, n.status, loc.locale FROM {node} n INNER JOIN {localizernode} loc ON n.nid = loc.nid AND n.nid = %d', $localizernode['nid']));
       $title = l($trnode->title, 'node/'. $trnode->nid, NULL, 'locale=' . $trnode->locale);
@@ -258,11 +383,11 @@ function localizernode_db_rewrite_sql($q
   }
 
   if($applylocalizer) {
-    $contents_types_enabled = variable_get('localizer_contents_types', array());
+    $enabled_content_types = variable_get('localizer_contents_types', array());
     preg_match('/type\s*=\s*\'\S*\'/', $query, $matches);
     $pos = strpos($matches[0], "'");
     $type = str_replace("'", "", substr($matches[0], $pos));
-    if($type && !$contents_types_enabled[$type]) $applylocalizer = FALSE;
+    if($type && !$enabled_content_types[$type]) $applylocalizer = FALSE;
   }
 
   if ($applylocalizer) {
@@ -275,17 +400,6 @@ function localizernode_db_rewrite_sql($q
   return $sql;
 }
 
-function localizernode_get_nids($nid) {
-  $nids = array();
-  $localizernode=localizernode_findbynid($nid);
-  $pid = $localizernode['pid'];
-  if($pid) {
-    $items = localizernode_findone('pid=' . $pid);
-    $nids = array_keys($items);
-  }
-  return $nids;
-}
-
 /**
 * Gets the localized node's id
 * @param nid the node id
@@ -298,7 +412,7 @@ function localizernode_get_localizednid(
   if($_locale != $node_locale) {
     $pid = $localizernode['pid'];
     if($pid) {
-      $localizernode = localizernode_findone('pid=' . $pid . " AND locale='$_locale'");
+      $localizernode = localizernode_find('pid='. $pid ." AND locale='$_locale'", FALSE);
       $trnid = $localizernode['nid'];
     }
   }
@@ -343,7 +457,7 @@ function localizernode_existscontentloca
   if($arguments[0]=='node' && is_numeric($arguments[1])) {
     $localizernode = localizernode_findbynid($arguments[1]);
     $node_pid = $localizernode['pid'];
-    $localizernode = localizernode_findone('pid=' . $node_pid . " AND locale='$_locale'");
+    $localizernode = localizernode_find('pid='. $node_pid ." AND locale='$_locale'", FALSE);
     $node_nid = $localizernode['nid'];
   }
   if($node_nid) {
@@ -411,4 +525,3 @@ function localizernode_get_localizedpath
   }
 }
 
-?>
\ No newline at end of file
