Index: feedapi_mapper.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi_mapper/Attic/feedapi_mapper.admin.inc,v
retrieving revision 1.1.2.2
diff -u -p -r1.1.2.2 feedapi_mapper.admin.inc
--- feedapi_mapper.admin.inc	7 Aug 2009 14:07:44 -0000	1.1.2.2
+++ feedapi_mapper.admin.inc	11 Aug 2009 14:47:41 -0000
@@ -27,19 +27,40 @@ function theme_feedapi_mapper_descriptio
  */
 function theme_feedapi_mapper_form($form) {
   $output = '';
-
+  _feedapi_mapper_load_mappers();
   $type_url_str = str_replace('_', '-', $form['#node']->type);
-  $url = isset($form['#node']->nid) ?
-    'node/' . $form['#node']->nid .'/map/delete/' :
-    'admin/content/node-type/'. $type_url_str .'/map/delete/';
+  if (isset($form['#node']->nid)) {
+    $delete_path = 'node/' . $form['#node']->nid .'/map/delete/';
+    $toggle_unique_path = 'node/' . $form['#node']->nid .'/map/unique/';
+  }
+  else {
+    $delete_path = 'admin/content/node-type/'. $type_url_str .'/map/delete/';
+    $toggle_unique_path = 'admin/content/node-type/'. $type_url_str .'/map/unique/';
+  }
   if (isset($form['#mapping']['mapping'])) {
+    $feed_settings = feedapi_get_settings($form['#node']->type, $form['#node']->vid);
+    $active_processors = array_keys($feed_settings['processors']);
     foreach ($form['#mapping']['mapping'] as $feed_path => $node_path) {
+      $target = unserialize($node_path);
+      if (function_exists($target[0] .'_feedapi_mapper')) {
+        $unique_supported = FALSE;
+        foreach ($active_processors as $processor) {
+          $unique_supported = ($unique_supported | call_user_func($target[0] .'_feedapi_mapper', 'unique supported', $form['#node'], $processor, NULL, $target[1]));
+        }
+        if ($unique_supported == TRUE) {
+          $unique = $form['#mapping']['unique'][$feed_path] ? t('Yes') : t('No');
+          $unique = l($unique, $toggle_unique_path . base64_encode($feed_path) .'/'. drupal_get_token($feed_path));
+        }
+        else {
+          $unique = t('N/A');
+        }
+      }
       $rows[] = array(
         // @todo: Set proper messages.
         isset($form['#feed_map'][$feed_path]) ? $form['#feed_map'][$feed_path] : '',
         isset($form['#field_map'][$node_path]) ? $form['#field_map'][$node_path] : t('<em>Error: missing target.</em>'),
-        $form['#mapping']['unique'][$feed_path] ? t('Yes') : t('No'),
-        l(t('Delete'), $url . base64_encode($feed_path)),
+	$unique,
+        l(t('Delete'), $delete_path . base64_encode($feed_path)),
       );
     }
   }
@@ -123,6 +144,32 @@ function feedapi_mapper_revert_submit($f
 }
 
 /**
+ * Toggles the value of the unique-ness of the given key
+ * @see theme_feedapi_mapper_form()
+ */
+function feedapi_mapper_unique_toggle($param, $encoded_key, $token) {
+  $key = base64_decode($encoded_key);
+  if (!drupal_valid_token($token, $key)) {
+    drupal_set_message(t('Invalid request'), 'error');
+    drupal_goto('');
+  }
+  if (is_string($param)) {
+    $node = new stdClass();
+    $node->type = str_replace('-', '_', $param);
+    $path = 'admin/content/node-type/'. $param .'/map';
+  }
+  else {
+    $node = $param;
+    $path = "node/{$node->nid}/map";
+    $param = $node->nid;
+  }
+  $mapping = feedapi_mapper_load_mapping($node);
+  feedapi_mapper_delete_mapping($param, $key);
+  feedapi_mapper_add_mapping($param, $key, $mapping['mapping'][$key], !$mapping['unique'][$key]);
+  drupal_goto($path);
+}
+
+/**
  * Form callback confirmation form for fall back to default.
  */
 function feedapi_mapper_default_form($form_state, $feed_node) {
@@ -276,9 +323,6 @@ function feedapi_mapper_form($form_state
     '#type' => 'select',
     '#options' => $field_map_options,
   );
-  $form['unique'] = array(
-    '#type' => 'checkbox',
-  );
   $form['add'] = array(
     '#type' => 'submit',
     '#value' => t('Add'),
@@ -348,7 +392,7 @@ function feedapi_mapper_form_validate($f
  */
 function feedapi_mapper_form_submit($form, &$form_state) {
   $param = ($form['#node']->nid && $form['#override']) ? $form['#node']->nid : $form['#node']->type;
-  feedapi_mapper_add_mapping($param, $form_state['values']['source'], $form_state['values']['target'], $form_state['values']['unique']);
+  feedapi_mapper_add_mapping($param, $form_state['values']['source'], $form_state['values']['target'], FALSE);
 }
 
 /**
Index: feedapi_mapper.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi_mapper/feedapi_mapper.module,v
retrieving revision 1.2.2.17
diff -u -p -r1.2.2.17 feedapi_mapper.module
--- feedapi_mapper.module	7 Aug 2009 15:43:43 -0000	1.2.2.17
+++ feedapi_mapper.module	11 Aug 2009 14:47:41 -0000
@@ -55,6 +55,13 @@ function feedapi_mapper_menu() {
     'access arguments' => array(1),
     'file' => 'feedapi_mapper.admin.inc',
   );
+  $items['node/%node/map/unique/%/%'] = array(
+    'page callback' => 'feedapi_mapper_toggle_unique',
+    'page arguments' => array(1, 4, 5),
+    'access callback' => 'feedapi_mapper_access_mapper',
+    'access arguments' => array(1),
+    'file' => 'feedapi_mapper.admin.inc',
+  );
   $items['node/%node/map/default'] = array(
     'title' => 'Default',
     'page callback' => 'drupal_get_form',
@@ -123,6 +130,14 @@ function feedapi_mapper_menu() {
       'access arguments' => array(3),
       'file' => 'feedapi_mapper.admin.inc',
     );
+    $items['admin/content/node-type/'. $type_url_str .'/map/unique/%/%'] = array(
+      'page callback' => 'feedapi_mapper_unique_toggle',
+      'page arguments' => array(3, 6, 7),
+      'load arguments' => array(3),
+      'access callback' => 'feedapi_mapper_access_mapper',
+      'access arguments' => array(3),
+      'file' => 'feedapi_mapper.admin.inc',
+    );
   }
   return $items;
 }
@@ -226,6 +241,51 @@ function feedapi_mapper_map($feed_node, 
 }
 
 /**
+ * Collects ids of duplicate items of $item.
+ * Most commonly used by FeedAPI processors to determine whether given item is unique.
+ * @see _feedapi_node_unique() for example.
+ *
+ * @param $feed_node
+ *   Feed node object of this feed item.
+ * @param $item
+ *   Feed item to decide on.
+ *
+ * @return
+ *   An array containing duplicate items per unique elements, FALSE if no unique elements were defined.
+ *   The exact format of the duplicate items is up to the implementer of feedapi_mapper($op = 'unique'):
+ *   array(
+ *     [0] => array(// duplicates for unique element 1, e. g. URL - format up to implementer ),
+ *     [1] => array(// duplicates for unique element 2, e. g. GUID - format up to implementer ),
+ *     // ...
+ *   )
+ *
+ *   For instance, feedapi_node processor expects arrays where the key is the item's node id
+ *   and the value is an array of feeds the feed item is associated with:
+ *
+ *   array(
+ *     [0] => array(itemid1 => array(feedid1, feedid2)),
+ *     [1] => array(itemid2 => array(feedid1, feedid2)),
+ *   )
+ */
+function feedapi_mapper_unique($feed_node, $processor, $item) {
+  _feedapi_mapper_load_mappers();
+  $uniques = feedapi_mapper_get_uniques($feed_node);
+  if (empty($uniques)) {
+    return FALSE;
+  }
+  $result = array();
+  foreach ($uniques as $unique) {
+    $feed_item_element = feedapi_mapper_get_element($unique['source'], _feedapi_mapper_obj2array($item));
+    $duplicates = call_user_func($unique['target'][0] .'_feedapi_mapper', 'unique', $feed_node, $processor, NULL, $feed_item_element, $unique['target'][1]);
+    // If they're not compatible with the given processor the result should be empty.
+    if (!empty($duplicates)) {
+      $result[] = $$duplicates;
+    }
+  }
+  return $result;
+}
+
+/**
  * Returns paths to unique feed elements.
  * 
  * @param $feed_node
@@ -371,6 +431,7 @@ function _feedapi_mapper_load_mapping($p
   if ($mapping = ctools_export_load_object('feedapi_mapper', 'conditions', array('param' => $param))) {
     $mapping = (array) array_pop($mapping);
     if (!empty($mapping['mapping'])) {
+      $mapping['unique'] = $mapping['unique_elements'];
       return $mapping;
     }
   }
Index: tests/feedapi_mapper.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi_mapper/tests/feedapi_mapper.test,v
retrieving revision 1.2.2.1
diff -u -p -r1.2.2.1 feedapi_mapper.test
--- tests/feedapi_mapper.test	7 Aug 2009 15:29:01 -0000	1.2.2.1
+++ tests/feedapi_mapper.test	11 Aug 2009 14:47:42 -0000
@@ -66,6 +66,8 @@ class FeedApiMapperBasicTestCase extends
       'filename' => 'drupal.xml',
       'mappers' => array('node'),
       'mapping' => array(
+        serialize(array('title')) => serialize(array('node', 'title')),
+        serialize(array('description')) => serialize(array('node', 'body')),
       ),
     );
 
Index: tests/feedapi_mapper_content.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedapi_mapper/tests/feedapi_mapper_content.test,v
retrieving revision 1.2.2.1
diff -u -p -r1.2.2.1 feedapi_mapper_content.test
--- tests/feedapi_mapper_content.test	7 Aug 2009 15:29:01 -0000	1.2.2.1
+++ tests/feedapi_mapper_content.test	11 Aug 2009 14:47:42 -0000
@@ -57,6 +57,7 @@ class FeedApiMapperContentTestCase exten
       'mappers' => array('node', 'content'),
       'mapping' => array(
         serialize(array('title')) => serialize(array('node', 'title')),
+        serialize(array('description')) => serialize(array('node', 'body')),
         serialize(array('options', 'raw' ,'comments')) => serialize(array('content', 'field_alpha')),
         serialize(array('options', 'timestamp')) => serialize(array('content', 'field_beta')),
         // @todo: use a field that contains a decimal
