Only in nat: .DS_Store
diff -rup nat-HEAD/nat.module nat/nat.module
--- nat-HEAD/nat.module	2007-12-17 00:03:49.000000000 -0800
+++ nat/nat.module	2008-02-05 02:11:53.000000000 -0800
@@ -15,9 +15,11 @@
  *  o Filter handling for body/description fields.
  *  o Duplicate handling?
  *
- * Features to be added:
+ * Features recently added:
  *  o Node deletes: Optionally delete child nodes associated via NAT.
- *  o Maintain hierarchy on unassociated vocabularies (on a best effort basis?)
+ *  o Maintain hierarchy on unassociated vocabularies
+ *  o Sync terms -> nodes, term depth can determine nodetype
+ *  o i18n language for term: Optionally add i18n language data to term
  */
 
 /**
@@ -96,24 +98,19 @@ function nat_nodeapi(&$node, $op, $tease
       $node->nat = nat_get_terms($node->nid);
       break;
     case 'insert':
-      $body = $node->body;
-      // Copying over the node body is optional.
-      $body = isset($nat_config['body'][$node->type]) ? $body : '';
+      // if terms already exist then this node was created from a sync
+      if(isset($node->nat)) {
+        break;
+      }
 
       // Add node title as terms.
-      $terms = _nat_add_terms($nat_config['types'][$node->type], $node->title, $body, $node->taxonomy, $node->nat['related'], $node->nat['synonyms']);
+      $terms = _nat_add_terms($nat_config['types'][$node->type], $node);
 
       // Save node-term association in the NAT table.
       _nat_save_association($node->nid, $terms);
       break;
     case 'update':
-      $body = $node->body;
-      // Copying over the node body is optional.
-      $body = isset($nat_config['body'][$node->type]) ? $body : '';
-
-      // Update node title in term(s).
-      $terms = nat_get_terms($node->nid);
-      _nat_update_terms($terms, $node->title, $body, $node->taxonomy, $node->nat['related'], $node->nat['synonyms']);
+      _nat_update_node_terms($node);
       break;
     case 'delete':
       // Deleting the associated term when a node is deleted is optional.
@@ -127,6 +124,26 @@ function nat_nodeapi(&$node, $op, $tease
 }
 
 /**
+ * Implementation of hook_taxonomy().
+ */
+function nat_taxonomy($op, $type, $id) {
+
+  if($op == 'delete' && $type == 'term') {
+    $config = _nat_variable_get();
+
+    // term has already been deleted - so nat_get_nids() will not work
+    $result = db_query("SELECT nat.nid, node.type FROM {nat} nat INNER JOIN {node} node USING (nid) WHERE nat.tid = %d",$id['tid']);
+
+    // delete all nat associated nodetypes set for deletion
+    while ($node = db_fetch_object($result)) {
+      if($config['delete_node'][$node->type] == 1) {
+        node_delete($node->nid);
+      }
+    }
+  }
+}
+
+/**
  * Implementation of hook_form_alter().
  */
 function nat_form_alter($form_id, &$form) {
@@ -235,6 +252,18 @@ function nat_settings_form() {
       '#default_value' => isset($nat_config['delete'][$type]) ? $nat_config['delete'][$type] : 0,
       '#parents' => array('delete', $type)
     );
+    $form['nat_'. $type]['delete_node'. $type] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Delete associated node if a term is deleted.'),
+      '#default_value' => isset($nat_config['delete_node'][$type]) ? $nat_config['delete_node'][$type] : 0,
+      '#parents' => array('delete_node', $type)
+    );
+    $form['nat_'. $type]['add_language'. $type] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Add i18n language information to term.'),
+      '#default_value' => isset($nat_config['add_language'][$type]) ? $nat_config['add_language'][$type] : 0,
+      '#parents' => array('add_language', $type)
+    );
     $form['nat_'. $type]['related_'. $type] = array(
       '#type' => 'checkbox',
       '#title' => t('Allow users to define synonyms and related terms when they create and edit nodes.'),
@@ -261,6 +290,8 @@ function nat_settings_form_submit($form_
 
   $form_values['body'] = array_filter($form_values['body']);
   $form_values['delete'] = array_filter($form_values['delete']);
+  $form_values['delete_node'] = array_filter($form_values['delete_node']);
+  $form_values['add_language'] = array_filter($form_values['add_language']);
   $form_values['node_links'] = array_filter($form_values['node_links']);
   $form_values['related'] = array_filter($form_values['related']);
 
@@ -282,30 +313,63 @@ function nat_sync_form() {
 
   $nat_config = _nat_variable_get();
   $options = array();
+  $form['sync'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Sync associations'),
+    '#description' => t('The Sync operation will create NAT associations (and terms or nodes) for nodes or terms (marked for NAT association) but not present in the NAT table'),
+    '#collapsible' => TRUE
+  );
+  $form['sync']['terms_to_nodes'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Create nodes from existing terms'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
   foreach ($nat_config['types'] as $type => $associations) {
     if (!empty($associations)) {
       foreach ($associations as $vid) {
-        $options[$type .'|'. $vid] = t('@type &lsaquo;-&rsaquo; !vocabulary', array('@type' => $type, '!vocabulary' => $vocabularies[$vid]));
+        $vocab_options[$type .'|'. $vid] = t('Unassociated <i>@type</i> nodes will create --&rsaquo; <i>!vocabulary</i> terms', array('@type' => $type, '!vocabulary' => $vocabularies[$vid]));
+        
+        $node_options = array('no'=>'','all' =>'from all vocabulary depths');
+        $tree = taxonomy_get_tree($vid);
+        $maxdepth = _array_depth($tree);
+        
+        if($maxdepth) {
+          while($maxdepth >= 0) {
+            $node_options[$maxdepth] = t('only from terms at depth') . ' ' . $maxdepth;
+            --$maxdepth;
+          }
+        }
+        
+        $form['sync']['terms_to_nodes']["$type"] = array(
+          '#type' => 'select',
+          '#title' => t('Unassociated <i>!vocabulary</i> terms will create --&rsaquo; <i>@type</i> nodes', array('@type' => $type, '!vocabulary' => $vocabularies[$vid])),
+          '#description' => t('Any terms not already NAT associated with the selected node types will be associated.'),
+          '#required' => FALSE,
+          '#options' => $node_options
+        );
+        
+        // $node_options[$type .'|'. $vid] = t('Unassociated <i>!vocabulary</i> terms will create --&rsaquo; <i>@type</i> nodes', array('@type' => $type, '!vocabulary' => $vocabularies[$vid]));
       }
     }
   }
-  if (empty($options)) {
+  if (empty($vocab_options)) {
     drupal_set_message(t('There are no vocabularies available to sync.'));
     drupal_goto('admin/settings/nat');
   }
 
-  $form['sync'] = array(
+  $form['sync']['nodes_to_terms'] = array(
     '#type' => 'fieldset',
-    '#title' => t('Sync associations'),
-    '#description' => t('The Sync operation will create NAT associations (and terms) for nodes (marked for NAT association) not present in the NAT table.'),
-    '#collapsible' => TRUE
+    '#title' => t('Create terms from existing nodes'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
   );
-  $form['sync']['vocabularies'] = array(
+  $form['sync']['nodes_to_terms']['vocabularies'] = array(
     '#type' => 'checkboxes',
-    '#title' => t('Select the vocabularies to sync with associated node tables'),
+    '#title' => t('Select the node types to sync with associated vocabularies'),
     '#description' => t('Any nodes not already NAT associated with the selected vocabularies will be associated.'),
-    '#required' => TRUE,
-    '#options' => $options
+    '#required' => FALSE,
+    '#options' => $vocab_options
   );
   $form['submit'] = array('#type' => 'submit', '#value' => t('Sync tables'));
 
@@ -316,7 +380,8 @@ function nat_sync_form() {
  * Process NAT sync form submissions.
  */
 function nat_sync_form_submit($form_id, $form_values) {
-  _nat_sync_associations(array_filter($form_values['vocabularies']));
+  _nat_sync_associations_vocab(array_filter($form_values['vocabularies']));
+  _nat_sync_associations_nodes($form_values);
 }
 
 /**
@@ -550,33 +615,42 @@ function _nat_get_vocabularies() {
 
 /**
  * Add node titles as terms into the taxonomy system.
- * @todo Ideas are welcome to allow retaining the hierarchy for vocabularies not
- * present in the node form.
  *
  * @param $vids
  *   An array of vocabulary IDs denoting the vocabularies into which a NAT term
  * is to be added.
- * @param $title
- *   Node title.
- * @param $body
- *   Node body.
- * @param $hierarchy
- *   The taxonomy array for the node in question. This is used to, if set,
- * insert the new term as a child term of the selected parent.
+ * @param $node
+ *   Complete node.
  * @return $tids
  *   An array of term objects.
  */
-function _nat_add_terms($vids, $title, $body, $hierarchy = array(), $relations = array(), $synonyms = NULL) {
+function _nat_add_terms($vids, $node) {
+
+  $nat_config = _nat_variable_get();
+
+  $title = $node->title;
+
+  // Copying over the node body is optional.
+  $body = isset($nat_config['body'][$node->type]) ? $node->body : '';
+
+  $hierarchy =  $node->taxonomy;
+  $relations = isset($node->nat['related']) ? $node->nat['related'] : array();
+  $synonyms = $node->nat['synonyms'];
+
   $edit = array(
     'name' => $title,
     'description' => $body,
-    'weight' => 0
+    'weight' => 0,
   );
 
   if (!empty($synonyms)) {
     $edit['synonyms'] = $synonyms;
   }
 
+  if (isset($node->language)  && $nat_config['add_language'][$node->type] == 1) {
+    $edit['language'] = $node->language;
+  }
+
   $tids = array();
 
   foreach ($vids as $vid) {
@@ -607,31 +681,52 @@ function _nat_add_terms($vids, $title, $
 /**
  * Update saved node-terms.
  *
- * @param $terms
- *   An array of existing NAT-term objects.
- * @param $title
- *   Title of the node.
- * @param $body
- *   Body of the node.
- * @param $hierarchy
- *   The taxonomy array for the node in question. This is used to, if set,
- * insert the new term as a child term of the selected parent.
+ * @param $node
+ *   Node for which terms should be updated
  */
-function _nat_update_terms($terms, $title, $body, $hierarchy, $relations = array(), $synonyms = null) {
+function _nat_update_node_terms($node) {
+
+  $nat_config = _nat_variable_get();
+
+  $terms = nat_get_terms($node->nid);
+
+  // Update node title in term(s).
+  $title = $node->title;
+
+  // Copying over the node body is optional.
+  $body = isset($nat_config['body'][$node->type]) ? $node->body : '';
+
+  $hierarchy =  $node->taxonomy;
+  $relations = isset($node->nat['related']) ? $node->nat['related'] : array();
+  $synonyms = $node->nat['synonyms'];
+
   $edit = array(
     'name' => $title,
     'description' => $body,
-    'weight' => 0
   );
 
   if (!empty($synonyms)) {
     $edit['synonyms'] = $synonyms;
   }
 
+  if (isset($node->language)  && $nat_config['add_language'][$node->type] == 1) {
+    $edit['language'] = $node->language;
+  }
+
   foreach ($terms as $term) {
     if (isset($hierarchy[$term->vid])) {
       $edit['parent'] = $hierarchy[$term->vid];
     }
+    elseif(count(taxonomy_get_parents($term->tid))) {
+      // preserves term's parent if it exists
+      $parents = taxonomy_get_parents($term->tid);
+      
+      // $parents is an object but taxonomy_save_term expects an array
+      foreach($parents as $parent) {
+        $parent_array[] = array($parent->tid => $parent->tid);
+      }
+      $edit['parent'] = $parent_array;
+    }
     else {
       $edit['parent'] = array();
     }
@@ -641,6 +736,7 @@ function _nat_update_terms($terms, $titl
     }
 
     $edit['tid'] = $term->tid;
+
     taxonomy_save_term($edit);
   }
 }
@@ -690,8 +786,8 @@ function _nat_delete_association($nid) {
  * @param $associations
  *   Associative array denoting the node-vocabulary pair that is to be synced.
  */
-function _nat_sync_associations($associations) {
-  $nat_config = _nat_variable_get();
+function _nat_sync_associations_vocab($associations) {
+  $nat_config = variable_get('nat_config', array());
 
   $counter = 0;
   foreach ($associations as $association) {
@@ -703,7 +799,7 @@ function _nat_sync_associations($associa
       $body = isset($nat_config['body'][$node['type']]) ? $node['body'] : '';
 
       // Add node title as terms.
-      $terms = _nat_add_terms(array($association[1]), $node['title'], $body);
+      $terms = _nat_add_terms(array($association[1]), $node);
 
       // Save node-term association in the NAT table.
       _nat_save_association($node['nid'], $terms);
@@ -711,7 +807,117 @@ function _nat_sync_associations($associa
       $counter++;
     }
   }
-  drupal_set_message(t('NAT sync complete: %count nodes synced.', array('%count' => $counter)));
+  if($counter > 0) {
+    drupal_set_message(t('NAT sync complete: %count nodes->terms synced.', array('%count' => $counter)));
+  }
+}
+
+/**
+ * Synchronize NAT node-term relationships. Create associated nodes for terms
+ * where missing.
+ *
+ * @param $associations
+ *   Associative array denoting the node-vocabulary pair that is to be synced.
+ */
+function _nat_sync_associations_nodes($form_values) {
+
+  global $user;
+
+  $vocabularies = _nat_get_vocabularies();
+  $nat_config = variable_get('nat_config', array());
+
+  $counter = array();
+  foreach ($nat_config['types'] as $type => $associations) {
+    if (!empty($associations) && $form_values[$type] != 'no') {
+      foreach ($associations as $vid) {
+
+        // get terms for vocabulary
+        $terms = taxonomy_get_tree($vid);
+    
+        foreach($terms as $key => $term) {
+                
+          if($term->depth == $form_values[$type] || $form_values[$type] == 'all') {
+
+            // find node nid associated with term
+            $natnid = db_fetch_object(db_query('SELECT n.nid FROM {nat} n WHERE n.tid = %d', $term->tid));
+            
+            // if no associated node - then create and save node
+            if(!is_numeric($natnid->nid)) {
+            
+              // build node
+              $new_node = (object)$node;
+              $new_node->type = $type;
+              node_object_prepare($new_node); 
+              $new_node->title = $term->name;
+              $new_node->status = 1;
+              
+              $parent = taxonomy_get_parents($term->tid);
+              if($parent) {
+                $new_node->taxonomy = array('$association[1]' => current($parent)->tid);
+              }
+      
+              $new_node->body = isset($nat_config['body'][$association[0]]) ? $term->description : '';        
+              $new_node->uid = $user->uid;
+              $new_node->nat = 1; // set so that new term is not created when node_api hook is called
+      
+              // save node
+              node_save($new_node);
+              
+              // save association now that we have a nid
+              _nat_save_association($new_node->nid, array(array('tid'=>$term->tid,'vid'=>$association[1])));
+              
+              ++$counter[$type];
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if(count($counter)) {
+    foreach($counter as $key => $value) {
+      drupal_set_message(t('NAT sync complete: %value terms --> <i>%key</i> nodes synced.', array('%value' => $value, '%key' => $key)));
+    }
+  }
+}
+
+/** 
+ * Find maximum depth of a vocabulary
+ *
+ * @param $vid
+ *   vid of the vocabulary
+ * @return $depth
+ *   An integer with max depth
+ */
+
+function _vocabulary_depth($vid) { 
+
+  $tree = taxonomy_get_tree($vid);
+  $depth = _array_depth($tree);
+
+  return $depth;
+}
+
+/** 
+ * Find maximum depth of an array
+ *
+ * @param $array
+ *   Associative array denoting the node-vocabulary pair that is to be synced.
+ * @param $depthcount
+ *   For internal use
+ * @return $depthcount
+ *   An integer with max depth
+ */
+
+function _array_depth($array) {
+
+  $depth = 0;
+  foreach ($array as $value) {
+      if($value->depth > $depth) {
+        $depth = $value->depth;
+      }
+  }
+  return $depth;
 }
 
 /**
@@ -730,7 +936,9 @@ function _nat_variable_get($name = NULL)
       'types' => array(),
       'body' => array(),
       'delete' => array(),
-      'node_links' => array()
+      'node_links' => array(),
+      'add_language' => array(),
+      'delete_node' => array(),
     );
     $variables = variable_get('nat_config', array());
     $variables = array_merge($defaults, $variables);
