? feeds-980212.patch
Index: feeds.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.install,v
retrieving revision 1.13.2.1
diff -u -p -r1.13.2.1 feeds.install
--- feeds.install	25 Sep 2010 16:07:50 -0000	1.13.2.1
+++ feeds.install	23 Nov 2010 23:16:24 -0000
@@ -163,11 +163,31 @@ function feeds_schema() {
         'not null' => TRUE,
         'description' => t("Node id of the owner feed, if available."),
       ),
+      'imported' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Import date of the feed item, as a Unix timestamp.'),
+      ),
+      'url' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => t('Link to the feed item.'),
+      ),
+      'guid' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => t('Unique identifier for the feed item.'),
+      ),
     ),
     'primary key' => array('tid'),
     'indexes' => array(
-      'id_feed_nid' => array('id', 'feed_nid'),
+      'id' => array('id'),
       'feed_nid' => array('feed_nid'),
+      'id_feed_nid' => array('id', 'feed_nid'),
+      'imported' => array('imported'),
+      'url' => array(array('url', 255)),
+      'guid' => array(array('guid', 255)),
     ),
   );
   $schema['feeds_push_subscriptions'] = array(
@@ -567,3 +587,43 @@ function feeds_update_6013() {
   variable_set('feeds_reschedule', TRUE);
   return array();
 }
+
+/**
+ * Update feeds_term_item to match feeds_node_item.
+ */
+function feeds_update_6014() {
+  $ret = array();
+
+  // Define new fields.
+  $fields = array(
+    'imported' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'default' => 0,
+      'description' => t('Import date of the feed item, as a Unix timestamp.'),
+    ),
+    'url' => array(
+      'type' => 'text',
+      'not null' => TRUE,
+      'description' => t('Link to the feed item.'),
+    ),
+    'guid' => array(
+      'type' => 'text',
+      'not null' => TRUE,
+      'description' => t('Unique identifier for the feed item.'),
+    ),
+  );
+
+  // Add new fields.
+  foreach ($fields as $field => $data) {
+    db_add_field($ret, 'feeds_term_item', $field, $data);
+  }
+
+  // Add new indexes.
+  db_add_index($ret, 'feeds_term_item', 'id', array('id'));
+  db_add_index($ret, 'feeds_term_item', 'imported', array('imported'));
+  db_add_index($ret, 'feeds_term_item', 'url', array(array('url', 255)));
+  db_add_index($ret, 'feeds_term_item', 'guid', array(array('guid', 255)));
+
+  return $ret;
+}
Index: feeds.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.module,v
retrieving revision 1.55.2.3
diff -u -p -r1.55.2.3 feeds.module
--- feeds.module	28 Oct 2010 19:58:22 -0000	1.55.2.3
+++ feeds.module	23 Nov 2010 23:16:24 -0000
@@ -430,19 +430,19 @@ function _feeds_nodeapi_node_processor($
  * Implementation of hook_taxonomy().
  */
 function feeds_taxonomy($op = NULL, $type = NULL, $term = NULL) {
-  if ($type =='term' && $term['tid']) {
+  if ($type == 'term' && !empty($term['tid'])) {
     switch ($op) {
       case 'delete':
         db_query("DELETE FROM {feeds_term_item} WHERE tid = %d", $term['tid']);
         break;
       case 'update':
-        if (isset($term['importer_id'])) {
+        if (isset($term['feeds_term_item'])) {
           db_query("DELETE FROM {feeds_term_item} WHERE tid = %d", $term['tid']);
         }
       case 'insert':
-        if (isset($term['importer_id'])) {
-          $term['id'] = $term['importer_id'];
-          drupal_write_record('feeds_term_item', $term);
+        if (isset($term['feeds_term_item'])) {
+          $term['feeds_term_item']['tid'] = $term['tid'];
+          drupal_write_record('feeds_term_item', $term['feeds_term_item']);
         }
         break;
     }
Index: plugins/FeedsTermProcessor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsTermProcessor.inc,v
retrieving revision 1.20.2.2
diff -u -p -r1.20.2.2 FeedsTermProcessor.inc
--- plugins/FeedsTermProcessor.inc	28 Oct 2010 20:48:10 -0000	1.20.2.2
+++ plugins/FeedsTermProcessor.inc	23 Nov 2010 23:16:24 -0000
@@ -20,20 +20,50 @@ class FeedsTermProcessor extends FeedsPr
       throw new Exception(t('You must define a vocabulary for Taxonomy term processor before importing.'));
     }
 
-    // Count number of created and updated nodes.
-    $created  = $updated = $no_name = 0;
+    // Count created, updated, and invalid terms.
+    $created = $updated = $no_name = 0;
 
     while ($item = $batch->shiftItem()) {
 
+      // Create/update if item does not exist or update existing is enabled.
       if (!($tid = $this->existingItemId($batch, $source)) || $this->config['update_existing'] != FEEDS_SKIP_EXISTING) {
 
-        // Map item to a term.
+        // Map feed item to a term.
         $term = array();
-        if ($tid && $this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
-          $term = (array) taxonomy_get_term($tid, TRUE);
-          $term = module_invoke_all('feeds_taxonomy_load', $term);
+
+        // Add term id if available.
+        if ($tid) {
+          $term['tid'] = $tid;
+        }
+
+        // Load the term if configured to update existing terms.
+        if (!empty($term['tid']) && $this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
+          // Load term.
+          $term = (array) taxonomy_get_term($term['tid'], TRUE);
+
+          // Load feeds_term_item data.
+          if ($feeds_term_item = db_fetch_array(db_query("SELECT imported, guid, url, feed_nid FROM {feeds_term_item} WHERE tid = %d", $term['tid']))) {
+            $term['feeds_term_item'] = $feeds_term_item;
+          }
+
+          // Allow other modules to act.
+          // @todo this breaks if hooks don't exist, or hooks don't return the term
+          if (module_implements('feeds_taxonomy_load')) {
+            $term = module_invoke_all('feeds_taxonomy_load', $term);
+          }
+        }
+
+        // Add feeds_term_item data.
+        $term['feeds_term_item']['id'] = $this->id;
+        if (!isset($term['feeds_term_item']['feed_nid'])) {
+          $term['feeds_term_item']['feed_nid'] = $source->feed_nid;
         }
-        $term = $this->map($batch, $term, $source->feed_nid);
+        if (!isset($term['feeds_term_item']['imported'])) {
+          $term['feeds_term_item']['imported'] = FEEDS_REQUEST_TIME;
+        }
+
+        // Map targets.
+        $term = $this->map($batch, $term);
 
         // Check if term name is set, otherwise continue.
         if (empty($term['name'])) {
@@ -41,19 +71,14 @@ class FeedsTermProcessor extends FeedsPr
           continue;
         }
 
-        // Add term id if available.
-        if (!empty($tid)) {
-          $term['tid'] = $tid;
-        }
-
         // Save the term.
-        $term['importer_id'] = $this->id;
-        $term['feed_nid'] = $source->feed_nid;
-        taxonomy_save_term($term);
-        if ($tid) {
+        $status = taxonomy_save_term($term);
+
+        // Track new and updated terms.
+        if ($status === SAVED_UPDATED) {
           $updated++;
         }
-        else {
+        elseif ($status === SAVED_NEW) {
           $created++;
         }
       }
@@ -65,8 +90,8 @@ class FeedsTermProcessor extends FeedsPr
       drupal_set_message(
         format_plural(
           $no_name,
-          'There was @number term that could not be imported because their name was empty. Check mapping settings on Taxomy term processor.',
-          'There were @number terms that could not be imported because their name was empty. Check mapping settings on Taxomy term processor.',
+          'There was @number term that could not be imported because it\'s name was empty. Check the mapping settings for the associated taxonomy term processor.',
+          'There were @number terms that could not be imported because their names were empty. Check the mapping settings for the associated taxonomy term processor.',
           array('@number' => $no_name)
         ),
         'error'
@@ -114,19 +139,25 @@ class FeedsTermProcessor extends FeedsPr
    * Execute mapping on an item.
    */
   protected function map(FeedsImportBatch $batch, $target_term = NULL) {
-    // Prepare term object, have parent class do the iterating.
+    // Prepare term object.
     if (!$target_term) {
       $target_term = array();
     }
+
+    // Verify vocabulary.
     if (!$vocabulary = $this->vocabulary()) {
       throw new Exception(t('No vocabulary specified for term processor'));
     }
     $target_term['vid'] = $vocabulary->vid;
+
+    // Have parent class do the mapping.
     $target_term = parent::map($batch, $target_term);
+
     // Taxonomy module expects synonyms to be supplied as a single string.
     if (isset($target_term['synonyms']) && is_array($target_term['synonyms'])) {
       $target_term['synonyms'] = implode("\n", $target_term['synonyms']);
     }
+
     return $target_term;
   }
 
@@ -186,6 +217,18 @@ class FeedsTermProcessor extends FeedsPr
   }
 
   /**
+   * Override setTargetElement to operate on a target item that is a term.
+   */
+  public function setTargetElement(&$target_item, $target_element, $value) {
+    if (in_array($target_element, array('url', 'guid'))) {
+      $target_item['feeds_term_item'][$target_element] = $value;
+    }
+    else {
+      parent::setTargetElement($target_item, $target_element, $value);
+    }
+  }
+
+  /**
    * Return available mapping targets.
    */
   public function getMappingTargets() {
@@ -203,6 +246,16 @@ class FeedsTermProcessor extends FeedsPr
         'name' => t('Term synonyms'),
         'description' => t('One synonym or an array of synonyms of the taxonomy term.'),
        ),
+       'url' => array(
+         'name' => t('URL'),
+         'description' => t('The external URL of the term. E. g. the feed item URL in the case of a syndication feed. May be unique.'),
+         'optional_unique' => TRUE,
+       ),
+       'guid' => array(
+         'name' => t('GUID'),
+         'description' => t('The external GUID of the term. E. g. the feed item GUID in the case of a syndication feed. May be unique.'),
+         'optional_unique' => TRUE,
+       ),
     );
     // Let implementers of hook_feeds_term_processor_targets() add their targets.
     $vocabulary = $this->vocabulary();
@@ -214,14 +267,24 @@ class FeedsTermProcessor extends FeedsPr
    * Get id of an existing feed item term if available.
    */
   protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) {
-
-    // The only possible unique target is name.
+    // Iterate through all unique targets and test whether they already
+    // exist in the database.
     foreach ($this->uniqueTargets($batch) as $target => $value) {
-      if ($target == 'name') {
-        $vocabulary = $this->vocabulary();
-        if ($tid = db_result(db_query("SELECT tid FROM {term_data} WHERE name = '%s' AND vid = %d", $value, $vocabulary->vid))) {
-          return $tid;
-        }
+      switch ($target) {
+        case 'url':
+          $tid = db_result(db_query("SELECT tid FROM {feeds_term_item} WHERE feed_nid = %d AND id = '%s' AND url = '%s'", $source->feed_nid, $this->id, $value));
+          break;
+        case 'guid':
+          $tid = db_result(db_query("SELECT tid FROM {feeds_term_item} WHERE feed_nid = %d AND id = '%s' AND guid = '%s'", $source->feed_nid, $this->id, $value));
+          break;
+        case 'name':
+          $vocabulary = $this->vocabulary();
+          $tid = db_result(db_query("SELECT tid FROM {term_data} WHERE name = '%s' AND vid = %d", $value, $vocabulary->vid));
+          break;
+      }
+      if ($tid) {
+        // Return the first tid found.
+        return $tid;
       }
     }
     return 0;
