diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 38e5f13acfa6211fdc39466e5d4af7144552c7d1..3875be205897f819d2a2b23d735bfe6a97531cfe 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -3,6 +3,8 @@
 Feeds 7.x 2.0 XXXXXXXXXXXXXXXXXXX
 ---------------------------------
 
+- #929066 alex_b: Track all imported items. Note: All views that use 'Feeds
+  Item' fields or relationships need updating.
 - #930018 alex_b: Don't show file upload when 'Supply path directly' is
   selected.
 - #927892 alex_b: Add "Process in background" feature. Allows one-off imports to
diff --git a/feeds.install b/feeds.install
index 62d0fd8c480a05c56a4f51914a670c898a8e2e22..188746f4d4202b4c284310caf25192af0ada078a 100644
--- a/feeds.install
+++ b/feeds.install
@@ -100,27 +100,34 @@ function feeds_schema() {
       'id_source' => array('id', array('source', 128)),
     ),
   );
-  $schema['feeds_node_item'] = array(
-    'description' => 'Stores additional information about feed item nodes. Used by FeedsNodeProcessor.',
+  $schema['feeds_item'] = array(
+    'description' => 'Tracks items such as nodes, terms, users.',
     'fields' => array(
-      'nid' => array(
+      'entity_type' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The entity type.',
+      ),
+      'entity_id' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
-        'description' => 'Primary Key: The feed item node\'s nid.',
+        'description' => 'The imported entity\'s serial id.',
       ),
       'id' => array(
         'type' => 'varchar',
         'length' => 128,
         'not null' => TRUE,
         'default' => '',
-        'description' => 'The id of the fields object that is the producer of this item.',
+        'description' => 'The id of the importer that created this item.',
       ),
       'feed_nid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
-        'description' => 'Node id of the owner feed, if available.',
+        'description' => 'Node id of the source, if available.',
       ),
       'imported' => array(
         'type' => 'int',
@@ -143,46 +150,16 @@ function feeds_schema() {
         'length' => 32, // The length of an MD5 hash.
         'not null' => TRUE,
         'default' => '',
-        'description' => 'The hash of the item.',
+        'description' => 'The hash of the source item.',
       ),
     ),
-    'primary key' => array('nid'),
+    'primary key' => array('entity_type', 'entity_id'),
     'indexes' => array(
       'id' => array('id'),
       'feed_nid' => array('feed_nid'),
+      'lookup_url' => array('entity_type', 'id', 'feed_nid', array('url', 255)),
+      'lookup_guid' => array('entity_type', 'id', 'feed_nid', array('guid', 255)),
       'imported' => array('imported'),
-      'url' => array(array('url', 255)),
-      'guid' => array(array('guid', 255)),
-    ),
-  );
-  $schema['feeds_term_item'] = array(
-    'description' => 'Tracks imported terms.',
-    'fields' => array(
-      'tid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'Imported term id.',
-      ),
-      'id' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The id of the fields object that is the creator of this item.',
-      ),
-      'feed_nid' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'Node id of the owner feed, if available.',
-      ),
-    ),
-    'primary key' => array('tid'),
-    'indexes' => array(
-      'id_feed_nid' => array('id', 'feed_nid'),
-      'feed_nid' => array('feed_nid'),
     ),
   );
   $schema['feeds_push_subscriptions'] = array(
@@ -285,3 +262,78 @@ function feeds_update_7201(&$sandbox) {
   );
   db_add_field('feeds_source', 'imported', $spec);
 }
+
+/**
+ * Create a single feeds_item table tracking all imports.
+ */
+function feeds_update_7202(&$sandbox) {
+  $spec = array(
+    'description' => 'Tracks items such as nodes, terms, users.',
+    'fields' => array(
+      'entity_type' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The entity type.',
+      ),
+      'entity_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The imported entity\'s serial id.',
+      ),
+      'id' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The id of the importer that created this item.',
+      ),
+      'feed_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'Node id of the source, if available.',
+      ),
+      'imported' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Import date of the feed item, as a Unix timestamp.',
+      ),
+      'url' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => 'Link to the feed item.',
+      ),
+      'guid' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => 'Unique identifier for the feed item.'
+      ),
+      'hash' => array(
+        'type' => 'varchar',
+        'length' => 32, // The length of an MD5 hash.
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The hash of the source item.',
+      ),
+    ),
+    'primary key' => array('entity_type', 'entity_id'),
+    'indexes' => array(
+      'id' => array('id'),
+      'feed_nid' => array('feed_nid'),
+      'lookup_url' => array('entity_type', 'id', 'feed_nid', array('url', 255)),
+      'lookup_guid' => array('entity_type', 'id', 'feed_nid', array('guid', 255)),
+      'imported' => array('imported'),
+    ),
+  );
+  db_create_table('feeds_item', $spec);
+  // Copy all existing values from old tables and drop them.
+  $insert = "INSERT INTO {feeds_item} (entity_type, entity_id, id, feed_nid, imported, url, guid, hash)";
+  db_query($insert . " SELECT 'node', nid, id, feed_nid, imported, url, guid, hash FROM {feeds_node_item}");
+  db_query($insert . " SELECT 'taxonomy_term', tid, id, feed_nid, 0, '', '', '' FROM {feeds_term_item}");
+  db_drop_table('feeds_node_item');
+  db_drop_table('feeds_term_item');
+}
diff --git a/feeds.module b/feeds.module
index 6511c94cc79df200faff3e02e0300388f64c20e5..697e4fafd2d2af92dd76f8f65ecd4ee6f0fbba4d 100644
--- a/feeds.module
+++ b/feeds.module
@@ -396,13 +396,6 @@ function feeds_feeds_plugins() {
   return _feeds_feeds_plugins();
 }
 
-/**
- * Implements hook_node_load().
- */
-function feeds_node_load($nodes) {
-  _feeds_node_processor_node_load($nodes);
-}
-
 /**
  * Implements hook_node_validate().
  */
@@ -463,7 +456,10 @@ function feeds_node_presave($node) {
  * Implements hook_node_insert().
  */
 function feeds_node_insert($node) {
-  _feeds_node_processor_node_insert($node);
+  // Node produced by source.
+  feeds_item_info_insert($node, $node->nid);
+
+  // Source attached to node.
   feeds_node_update($node);
   if ($importer_id = feeds_get_importer_id($node->type)) {
     $source = feeds_source($importer_id, $node->nid);
@@ -481,100 +477,79 @@ function feeds_node_insert($node) {
  * Implements hook_node_update().
  */
 function feeds_node_update($node) {
-  _feeds_node_processor_node_update($node);
-  if (!$importer_id = feeds_get_importer_id($node->type)) {
-    return;
+  // Node produced by source.
+  feeds_item_info_update($node, $node->nid);
+
+  // Source attached to node.
+  if ($importer_id = feeds_get_importer_id($node->type)) {
+    $source = feeds_source($importer_id, $node->nid);
+    $source->addConfig($node->feeds);
+    $source->save();
   }
-  // Add configuration to feed source and save.
-  $source = feeds_source($importer_id, $node->nid);
-  $source->addConfig($node->feeds);
-  $source->save();
 }
 
 /**
  * Implements hook_node_delete().
  */
 function feeds_node_delete($node) {
-  _feeds_node_processor_node_delete($node);
+  // Node produced by source.
+  db_delete('feeds_item')
+    ->condition('entity_type', 'node')
+    ->condition('entity_id', $node->nid)
+    ->execute();
+  // Source attached to node.
   if ($importer_id = feeds_get_importer_id($node->type)) {
     feeds_source($importer_id, $node->nid)->delete();
   }
 }
 
 /**
- * FeedsNodeProcessor's hook_node_load().
- */
-function _feeds_node_processor_node_load($nodes) {
-  if ($items = db_query("SELECT nid, imported, guid, url, feed_nid FROM {feeds_node_item} WHERE nid IN (:nids)", array(':nids' => array_keys($nodes)))) {
-    foreach ($items as $item) {
-      $nodes[$item->nid]->feeds_node_item = $item;
-    }
-  }
-}
-
-/**
- * FeedsNodeProcessor's hook_node_insert().
+ * Implements hook_taxonomy_term_insert().
  */
-function _feeds_node_processor_node_insert($node) {
-  if (isset($node->feeds_node_item)) {
-    $node->feeds_node_item->nid = $node->nid;
-    drupal_write_record('feeds_node_item', $node->feeds_node_item);
-  }
+function feeds_taxonomy_term_insert($term) {
+  feeds_item_info_insert($term, $term->tid);
 }
 
 /**
- * FeedsNodeProcessor's hook_node_update().
+ * Implements hook_taxonomy_term_update().
  */
-function _feeds_node_processor_node_update($node) {
-  if (isset($node->feeds_node_item)) {
-    $node->feeds_node_item->nid = $node->nid;
-    drupal_write_record('feeds_node_item', $node->feeds_node_item, 'nid');
-  }
+function feeds_taxonomy_term_update($term) {
+  feeds_item_info_update($term, $term->tid);
 }
 
 /**
- * FeedsNodeProcessor's hook_node_delete().
+ * Implements hook_taxonomy_delete().
  */
-function _feeds_node_processor_node_delete($node) {
-  db_delete('feeds_node_item')
-    ->condition('nid', $node->nid)
+function feeds_taxonomy_term_delete($term) {
+  db_delete('feeds_item')
+    ->condition('entity_type', 'taxonomy_term')
+    ->condition('entity_id', $term->tid)
     ->execute();
 }
 
 /**
- * Implements hook_taxonomy_term_update().
+ * Implements hook_user_insert().
  */
-function feeds_taxonomy_term_update($term) {
-  if (isset($term->feeds_importer_id)) {
-    db_delete('feeds_term_item')
-      ->condition('tid', $term->tid)
-      ->execute();
-  }
+function feeds_user_insert(&$edit, $account, $category) {
+  feeds_item_info_insert($account, $account->uid);
 }
 
 /**
- * Implements hook_taxonomy_term_insert().
+ * Implements hook_user_update().
  */
-function feeds_taxonomy_term_insert($term) {
-  if (isset($term->feeds_importer_id)) {
-    $record = array(
-      'id' => $term->feeds_importer_id,
-      'tid' => $term->tid,
-      'feed_nid' => $term->feed_nid,
-    );
-    drupal_write_record('feeds_term_item', $record);
-  }
+function feeds_user_update(&$edit, $account, $category) {
+  feeds_item_info_update($account, $account->uid);
 }
 
 /**
- * Implements hook_taxonomy_delete().
+ * Implements hook_user_delete().
  */
-function feeds_taxonomy_term_delete($term) {
-  db_delete('feeds_term_item')
-    ->condition('tid', $term->tid)
+function feeds_user_delete($account) {
+  db_delete('feeds_item')
+    ->condition('entity_type', 'user')
+    ->condition('entity_id', $account->uid)
     ->execute();
 }
-
 /**
  * Implements hook_form_alter().
  */
@@ -719,6 +694,42 @@ function feeds_dbg($msg) {
   }
 }
 
+/**
+ * Loads an item info object.
+ *
+ * Example usage:
+ *
+ * $info = feeds_item_info_load('node', $node->nid);
+ */
+function feeds_item_info_load($entity_type, $entity_id) {
+  return db_select('feeds_item')
+    ->fields('feeds_item')
+    ->condition('entity_type', $entity_type)
+    ->condition('entity_id', $entity_id)
+    ->execute()
+    ->fetch();
+}
+
+/**
+ * Inserts an item info object into the feeds_item table.
+ */
+function feeds_item_info_insert($entity, $entity_id) {
+  if (isset($entity->feeds_item)) {
+    $entity->feeds_item->entity_id = $entity_id;
+    drupal_write_record('feeds_item', $entity->feeds_item);
+  }
+}
+
+/**
+ * Updates an item info object in he feeds_item table.
+ */
+function feeds_item_info_update($entity, $entity_id) {
+  if (isset($entity->feeds_item)) {
+    $entity->feeds_item->entity_id = $entity_id;
+    drupal_write_record('feeds_item', $entity->feeds_item, array('entity_type', 'entity_id'));
+  }
+}
+
 /**
  * @}
  */
diff --git a/feeds_import/feeds_import.test b/feeds_import/feeds_import.test
index f8941cf3ba38faebcfbde812b8327bbdb52e6eda..ca3258f3e2340577e83be97abc95ebc7c4d40d4b 100644
--- a/feeds_import/feeds_import.test
+++ b/feeds_import/feeds_import.test
@@ -61,7 +61,7 @@ class FeedsExamplesNodeTestCase extends FeedsWebTestCase {
 
     // Assert DB status as is and again after an additional import.
     for ($i = 0; $i < 2; $i++) {
-      $count = db_query("SELECT COUNT(*) FROM {feeds_node_item}")->fetchField();
+      $count = db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'")->fetchField();
       $this->assertEqual($count, 8, 'Found correct number of items.');
       $count = db_query("SELECT COUNT(*) FROM {node} WHERE type = 'article' AND status = 1 AND uid = 0")->fetchField();
       $this->assertEqual($count, 8, 'Found correct number of items.');
diff --git a/feeds_news/feeds_news.test b/feeds_news/feeds_news.test
index 86874e32a482be17ef9383ffba563e210dcde120..4ef7e07c5afb0570bbcbb8de3d06de26e31b8bb0 100644
--- a/feeds_news/feeds_news.test
+++ b/feeds_news/feeds_news.test
@@ -50,8 +50,8 @@ class FeedsExamplesFeedTestCase extends FeedsWebTestCase {
     $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'feed_item'"));
     $this->assertEqual($count, 10, 'Found the correct number of feed item nodes in database.');
 
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
-    $this->assertEqual($count, 10, 'Found the correct number of records in feeds_node_item.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'"));
+    $this->assertEqual($count, 10, 'Found the correct number of records in feeds_item.');
 
     $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE title = 'Open Atrium Translation Workflow: Two Way Translation Updates'"));
     $this->assertEqual($count, 1, 'Found title.');
@@ -62,13 +62,13 @@ class FeedsExamplesFeedTestCase extends FeedsWebTestCase {
     $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE title = 'Scaling the Open Atrium UI'"));
     $this->assertEqual($count, 1, 'Found title.');
 
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item} WHERE url = 'http://developmentseed.org/blog/2009/oct/06/open-atrium-translation-workflow-two-way-updating'"));
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node' AND url = 'http://developmentseed.org/blog/2009/oct/06/open-atrium-translation-workflow-two-way-updating'"));
     $this->assertEqual($count, 1, 'Found feed_node_item record.');
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item} WHERE url = 'http://developmentseed.org/blog/2009/oct/05/week-dc-tech-october-5th-edition'"));
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node' AND url = 'http://developmentseed.org/blog/2009/oct/05/week-dc-tech-october-5th-edition'"));
     $this->assertEqual($count, 1, 'Found feed_node_item record.');
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item} WHERE guid = '974 at http://developmentseed.org'"));
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node' AND guid = '974 at http://developmentseed.org'"));
     $this->assertEqual($count, 1, 'Found feed_node_item record.');
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item} WHERE guid = '970 at http://developmentseed.org'"));
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node' AND guid = '970 at http://developmentseed.org'"));
     $this->assertEqual($count, 1, 'Found feed_node_item record.');
 
     // Remove all items
@@ -85,15 +85,15 @@ class FeedsExamplesFeedTestCase extends FeedsWebTestCase {
     $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'feed_item'"));
     $this->assertEqual($count, 0, 'Found the correct number of feed item nodes in database.');
 
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
-    $this->assertEqual($count, 0, 'Found the correct number of records in feeds_node_item.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'"));
+    $this->assertEqual($count, 0, 'Found the correct number of records in feeds_item.');
 
     // Create a batch of nodes.
     $this->createFeedNodes('feed', 10, 'feed');
     $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'feed_item'"));
     $this->assertEqual($count, 100, 'Imported 100 nodes.');
-    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
-    $this->assertEqual($count, 100, 'Found 100 records in feeds_node_item.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'"));
+    $this->assertEqual($count, 100, 'Found 100 records in feeds_item.');
   }
 }
 
diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc
index 5f2a8d2bb8133326c2f4a1f048928f7935145034..d3c5debd489e812a9b80348ffe6130a7ff88e681 100644
--- a/plugins/FeedsNodeProcessor.inc
+++ b/plugins/FeedsNodeProcessor.inc
@@ -19,6 +19,14 @@ define('FEEDS_NODE_DEFAULT_FORMAT', -1);
  * Creates nodes from feed items.
  */
 class FeedsNodeProcessor extends FeedsProcessor {
+  /**
+   * Define entity type.
+   */
+  protected function __construct($id) {
+    parent::__construct($id);
+    $this->entity_type = 'node';
+  }
+
   /**
    * Implements FeedsProcessor::process().
    */
@@ -37,8 +45,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
 
         // Assemble node, map item to it, save.
         try {
-          $node = $this->buildNode($nid, $source->feed_nid);
-          $node->feeds_node_item->hash = $hash;
+          $node = empty($nid) ? $this->newNode($source, $hash) : $this->loadNode($source, $hash, $nid);
           $this->map($source, $parser_result, $node);
           node_save($node);
           if (!empty($nid)) {
@@ -76,17 +83,17 @@ class FeedsNodeProcessor extends FeedsProcessor {
   public function clear(FeedsSource $source) {
     $state = $source->state(FEEDS_PROCESS_CLEAR);
     if (empty($state->total)) {
-      $state->total = db_query("SELECT COUNT(n.nid) FROM {node} n JOIN {feeds_node_item} fn ON n.nid = fn.nid WHERE fn.id = :id AND fn.feed_nid = :nid", array(':id' => $source->id, ':nid' => $source->feed_nid))->fetchField();
+      $state->total = db_query("SELECT COUNT(n.nid) FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND fi.feed_nid = :nid", array(':id' => $source->id, ':nid' => $source->feed_nid))->fetchField();
     }
     $count = $this->getLimit();
     $nids = array();
-    $nodes = db_query_range("SELECT n.nid FROM {node} n JOIN {feeds_node_item} fn ON n.nid = fn.nid WHERE fn.id = :id AND fn.feed_nid = :nid", 0, $count, array(':id' => $source->id, ':nid' => $source->feed_nid));
+    $nodes = db_query_range("SELECT n.nid FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND fi.feed_nid = :nid", 0, $count, array(':id' => $source->id, ':nid' => $source->feed_nid));
     foreach ($nodes as $node) {
       $nids[$node->nid] = $node->nid;
       $state->deleted++;
     }
     node_delete_multiple($nids);
-    if (db_query_range("SELECT 1 FROM {node} n JOIN {feeds_node_item} fn ON n.nid = fn.nid WHERE fn.id = :id AND fn.feed_nid = :nid", 0, 1, array(':id' => $source->id, ':nid' => $source->feed_nid))->fetchField()) {
+    if (db_query_range("SELECT 1 FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND fi.feed_nid = :nid", 0, 1, array(':id' => $source->id, ':nid' => $source->feed_nid))->fetchField()) {
       $state->progress($state->total, $state->deleted);
       return;
     }
@@ -122,13 +129,13 @@ class FeedsNodeProcessor extends FeedsProcessor {
       return;
     }
     $count = $this->getLimit();
-    $nodes = db_query_range("SELECT n.nid FROM {node} n JOIN {feeds_node_item} fni ON n.nid = fni.nid WHERE fni.id = :id AND n.created < :created", 0, $count, array(':id' => $this->id, ':created' => REQUEST_TIME - $time));
+    $nodes = db_query_range("SELECT n.nid FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND n.created < :created", 0, $count, array(':id' => $this->id, ':created' => REQUEST_TIME - $time));
     $nids = array();
     foreach ($nodes as $node) {
       $nids[$node->nid] = $node->nid;
     }
     node_delete_multiple($nids);
-    if (db_query_range("SELECT 1 FROM {node} n JOIN {feeds_node_item} fni ON n.nid = fni.nid WHERE fni.id = :id AND n.created < :created", 0, 1, array(':id' => $this->id, ':created' => REQUEST_TIME - $time))->fetchField()) {
+    if (db_query_range("SELECT 1 FROM {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE fi.id = :id AND n.created < :created", 0, 1, array(':id' => $this->id, ':created' => REQUEST_TIME - $time))->fetchField()) {
       return FEEDS_BATCH_ACTIVE;
     }
     return FEEDS_BATCH_COMPLETE;
@@ -141,13 +148,6 @@ class FeedsNodeProcessor extends FeedsProcessor {
     return $this->config['expire'];
   }
 
-  /**
-   * Return a count for the given items
-   */
-  public function itemCount(FeedsSource $source) {
-    return db_query("SELECT count(*) FROM {feeds_node_item} WHERE feed_nid = :feed_nid", array(':feed_nid' => $source->feed_nid))->fetchField();
-  }
-
   /**
    * Override parent::configDefaults().
    */
@@ -248,16 +248,6 @@ class FeedsNodeProcessor extends FeedsProcessor {
    */
   public function setTargetElement($target_node, $target_element, $value) {
     switch ($target_element) {
-      case 'url':
-      case 'guid':
-        $target_node->feeds_node_item->$target_element = $value;
-        break;
-      case 'title':
-      case 'status':
-      case 'nid':
-      case 'uid':
-        $target_node->$target_element = $value;
-        break;
       case 'created':
         $target_node->created = REQUEST_TIME;
         if (is_numeric($value)) {
@@ -271,6 +261,9 @@ class FeedsNodeProcessor extends FeedsProcessor {
           $target_node->created = $value->getValue();
         }
         break;
+      default:
+        parent::setTargetElement($target_node, $target_element, $value);
+        break;
     }
   }
 
@@ -279,7 +272,7 @@ class FeedsNodeProcessor extends FeedsProcessor {
    */
   public function getMappingTargets() {
     $type = node_type_get_type($this->config['content_type']);
-    $targets = array();
+    $targets = parent::getMappingTargets();
     if ($type->has_title) {
       $targets['title'] = array(
         'name' => t('Title'),
@@ -304,16 +297,6 @@ class FeedsNodeProcessor extends FeedsProcessor {
         'name' => t('Published date'),
         'description' => t('The UNIX time when a node has been published.'),
       ),
-      'url' => array(
-        'name' => t('URL'),
-        'description' => t('The external URL of the node. 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 node. E. g. the feed item GUID in the case of a syndication feed. May be unique.'),
-        'optional_unique' => TRUE,
-      ),
     );
 
     // Let other modules expose mapping targets.
@@ -327,6 +310,9 @@ class FeedsNodeProcessor extends FeedsProcessor {
    * Get nid of an existing feed item node if available.
    */
   protected function existingItemId(FeedsSource $source, FeedsParserResult $result) {
+    if ($nid = parent::existingItemId($source, $result)) {
+      return $nid;
+    }
 
     // Iterate through all unique targets and test whether they do already
     // exist in the database.
@@ -335,12 +321,6 @@ class FeedsNodeProcessor extends FeedsProcessor {
         case 'nid':
           $nid = db_query("SELECT nid FROM {node} WHERE nid = :nid", array(':nid' => $value))->fetchField();
           break;
-        case 'url':
-          $nid = db_query("SELECT nid FROM {feeds_node_item} WHERE feed_nid = :nid AND id = :id AND url = :url", array(':nid' => $source->feed_nid, ':id' => $source->id, ':url' => $value))->fetchField();
-          break;
-        case 'guid':
-          $nid = db_query("SELECT nid FROM {feeds_node_item} WHERE feed_nid = :nid AND id = :id AND guid = :guid", array(':nid' => $source->feed_nid, ':id' => $source->id, ':guid' => $value))->fetchField();
-          break;
       }
       if ($nid) {
         // Return with the first nid found.
@@ -351,77 +331,48 @@ class FeedsNodeProcessor extends FeedsProcessor {
   }
 
   /**
-   * Creates a new node object in memory and returns it.
+   * Creates a new node in memory and returns it.
    */
-  protected function buildNode($nid, $feed_nid) {
+  protected function newNode($source, $hash) {
     $node = new stdClass();
-    $populate = FALSE;
-    if (empty($nid)) {
-      $node->created = REQUEST_TIME;
-      $populate = TRUE;
-    }
-    else {
-      if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
-        $node = node_load($nid, NULL, TRUE);
-      }
-      else {
-        $node = db_query("SELECT nid, vid, created FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetch();
-        $populate = TRUE;
-      }
-    }
-    if ($populate) {
-      $node->type = $this->config['content_type'];
-      $node->changed = REQUEST_TIME;
-      $node->format = ($this->config['input_format'] == FEEDS_NODE_DEFAULT_FORMAT) ? filter_fallback_format() : $this->config['input_format'];
-      $node->feeds_node_item = new stdClass();
-      $node->feeds_node_item->id = $this->id;
-      $node->feeds_node_item->imported = REQUEST_TIME;
-      $node->feeds_node_item->feed_nid = $feed_nid;
-      $node->feeds_node_item->url = '';
-      $node->feeds_node_item->guid = '';
-    }
-
-    // Give mappers a hint at what they're operating on.
-    $node->entity_type = 'node';
-
-    // Let other modules populate default values.
+    $node->type = $this->config['content_type'];
+    $node->changed = REQUEST_TIME;
+    $node->format = ($this->config['input_format'] == FEEDS_NODE_DEFAULT_FORMAT) ? filter_fallback_format() : $this->config['input_format'];
+    $node->created = REQUEST_TIME;
+    $this->newItemInfo($node, $source->feed_nid, $hash);
     node_object_prepare($node);
-
     // Populate properties that are set by node_object_prepare().
-    $node->log = 'Created/updated by FeedsNodeProcessor';
-    if ($populate) {
-      $node->uid = $this->config['author'];
-    }
+    $node->log = 'Created by FeedsNodeProcessor';
+    $node->uid = $this->config['author'];
     return $node;
   }
 
   /**
-   * Create MD5 hash of item and mappings array.
-   *
-   * Include mappings as a change in mappings may have an affect on the item
-   * produced.
+   * Loads an existing node.
    *
-   * @return Always returns a hash, even with empty, NULL, FALSE:
-   *  Empty arrays return 40cd750bba9870f18aada2478b24840a
-   *  Empty/NULL/FALSE strings return d41d8cd98f00b204e9800998ecf8427e
+   * If the update existing method is not FEEDS_UPDATE_EXISTING, only the node
+   * table will be loaded, foregoing the node_load API for better performance.
    */
-  protected function hash($item) {
-    static $serialized_mappings;
-    if (!$serialized_mappings) {
-      $serialized_mappings = serialize($this->config['mappings']);
+  protected function loadNode($source, $hash, $nid) {
+    if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
+      $node = node_load($nid, NULL, TRUE);
     }
-    return hash('md5', serialize($item) . $serialized_mappings);
-  }
-
-  /**
-   * Retrieve MD5 hash of $nid from DB.
-   * @return Empty string if no item is found, hash otherwise.
-   */
-  protected function getHash($nid) {
-    if ($hash = db_query("SELECT hash FROM {feeds_node_item} WHERE nid = :nid", array(':nid' => $nid))->fetchField()) {
-      // Return with the hash.
-      return $hash;
+    else {
+      // We're replacing the existing node. Only save the absolutely necessary.
+      $node = db_query("SELECT created, nid, vid, type FROM {node} WHERE nid = :nid", array(':nid' => $nid))->fetch();
+      $node->uid = $this->config['author'];
+    }
+    if ($this->loadItemInfo($node)) {
+      $this->newItemInfo($node, $source->feed_nid, $hash);
+    }
+    node_object_prepare($node);
+    // Populate properties that are set by node_object_prepare().
+    if ($this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
+      $node->log = 'Updated by FeedsNodeProcessor';
     }
-    return '';
+    else {
+      $node->log = 'Replaced by FeedsNodeProcessor';
+    }
+    return $node;
   }
 }
diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc
index 24fb078902d1388dd83a67321f0d1ab478bae5ab..b30678e19b17ceb3e58f001a643a554f63bf3fcb 100644
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -15,6 +15,10 @@ define('FEEDS_PROCESS_LIMIT', 50);
  * Abstract class, defines interface for processors.
  */
 abstract class FeedsProcessor extends FeedsPlugin {
+  /**
+   * Entity type this processor operates on.
+   */
+  protected $entity_type;
 
   /**
    * Process the result of the parser or previous processors.
@@ -77,10 +81,10 @@ abstract class FeedsProcessor extends FeedsPlugin {
   }
 
   /**
-   * Return the number of items imported by this processor.
+   * Counts the number of items imported by this processor.
    */
   public function itemCount(FeedsSource $source) {
-    return 0;
+    return db_query("SELECT count(*) FROM {feeds_item} WHERE entity_type = :entity_type AND feed_nid = :feed_nid", array(':entity_type' => $this->entity_type, ':feed_nid' => $source->feed_nid))->fetchField();
   }
 
   /**
@@ -203,7 +207,18 @@ abstract class FeedsProcessor extends FeedsPlugin {
    *   FALSE otherwise.
    */
   public function getMappingTargets() {
-    return array();
+    return array(
+      'url' => array(
+        'name' => t('URL'),
+        'description' => t('The external URL of the item. 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 globally unique identifier of the item. E. g. the feed item GUID in the case of a syndication feed. May be unique.'),
+        'optional_unique' => TRUE,
+      ),
+    );
   }
 
   /**
@@ -212,7 +227,15 @@ abstract class FeedsProcessor extends FeedsPlugin {
    * @ingroup mappingapi
    */
   public function setTargetElement($target_item, $target_element, $value) {
-    $target_item->$target_element = $value;
+    switch ($target_element) {
+      case 'url':
+      case 'guid':
+        $target_item->feeds_item->$target_element = $value;
+        break;
+      default:
+        $target_item->$target_element = $value;
+        break;
+    }
   }
 
   /**
@@ -224,11 +247,37 @@ abstract class FeedsProcessor extends FeedsPlugin {
    *   The source information about this import.
    * @param $result
    *   A FeedsParserResult object.
+   *
+   * @return
+   *   The serial id of an entity if found, 0 otherwise.
    */
   protected function existingItemId(FeedsSource $source, FeedsParserResult $result) {
+    $query = db_select('feeds_item')
+      ->fields('feeds_item', array('entity_id'))
+      ->condition('feed_nid', $source->feed_nid)
+      ->condition('entity_type', $this->entity_type)
+      ->condition('id', $source->id);
+
+    // Iterate through all unique targets and test whether they do already
+    // exist in the database.
+    foreach ($this->uniqueTargets($source, $result) as $target => $value) {
+      switch ($target) {
+        case 'url':
+          $entity_id = $query->condition('url', $value)->execute()->fetchField();
+          break;
+        case 'guid':
+          $entity_id = $query->condition('guid', $value)->execute()->fetchField();
+          break;
+      }
+      if (isset($entity_id)) {
+        // Return with the content id found.
+        return $entity_id;
+      }
+    }
     return 0;
   }
 
+
   /**
    * Utility function that iterates over a target array and retrieves all
    * sources that are unique.
@@ -252,4 +301,75 @@ abstract class FeedsProcessor extends FeedsPlugin {
     }
     return $targets;
   }
+
+  /**
+   * Adds Feeds specific information on $entity->feeds_item.
+   *
+   * @param $entity
+   *   The entity object to be populated with new item info.
+   * @param $feed_nid
+   *   The feed nid of the source that produces this entity.
+   * @param $hash
+   *   The fingerprint of the source item.
+   */
+  protected function newItemInfo($entity, $feed_nid, $hash = '') {
+    $entity->feeds_item = new stdClass();
+    $entity->feeds_item->entity_id = 0;
+    $entity->feeds_item->entity_type = $this->entity_type;
+    $entity->feeds_item->id = $this->id;
+    $entity->feeds_item->feed_nid = $feed_nid;
+    $entity->feeds_item->imported = REQUEST_TIME;
+    $entity->feeds_item->hash = $hash;
+    $entity->feeds_item->url = '';
+    $entity->feeds_item->guid = '';
+  }
+
+  /**
+   * Loads existing entity information and places it on $entity->feeds_item.
+   *
+   * @param $entity
+   *   The entity object to load item info for. Id key must be present.
+   *
+   * @return
+   *   TRUE if item info could be loaded, false if not.
+   */
+  protected function loadItemInfo($entity) {
+    $entity_info = entity_get_info($this->entity_type);
+    $key = $entity_info['entity keys']['id'];
+    if ($item_info = feeds_item_info_load($this->entity_type, $entity->$key)) {
+      $entity->feeds_item = $item_info;
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Create MD5 hash of item and mappings array.
+   *
+   * Include mappings as a change in mappings may have an affect on the item
+   * produced.
+   *
+   * @return Always returns a hash, even with empty, NULL, FALSE:
+   *  Empty arrays return 40cd750bba9870f18aada2478b24840a
+   *  Empty/NULL/FALSE strings return d41d8cd98f00b204e9800998ecf8427e
+   */
+  protected function hash($item) {
+    static $serialized_mappings;
+    if (!$serialized_mappings) {
+      $serialized_mappings = serialize($this->config['mappings']);
+    }
+    return hash('md5', serialize($item) . $serialized_mappings);
+  }
+
+  /**
+   * Retrieve MD5 hash of $nid from DB.
+   * @return Empty string if no item is found, hash otherwise.
+   */
+  protected function getHash($entity_id) {
+    if ($hash = db_query("SELECT hash FROM {feeds_item} WHERE entity_type = :type AND entity_id = :id", array(':type' => $this->entity_type, ':id' => $entity_id))->fetchField()) {
+      // Return with the hash.
+      return $hash;
+    }
+    return '';
+  }
 }
diff --git a/plugins/FeedsTermProcessor.inc b/plugins/FeedsTermProcessor.inc
index 9cf4fb8768143221735fc5e48e9cf9e16386ba1b..cd817a53c99ba91c9d1383c0b9981d72a9a532c3 100644
--- a/plugins/FeedsTermProcessor.inc
+++ b/plugins/FeedsTermProcessor.inc
@@ -10,6 +10,14 @@
  * Feeds processor plugin. Create taxonomy terms from feed items.
  */
 class FeedsTermProcessor extends FeedsProcessor {
+  /**
+   * Define entity type.
+   */
+  protected function __construct($id) {
+    parent::__construct($id);
+    $this->entity_type = 'taxonomy_term';
+  }
+
 
   /**
    * Implements FeedsProcessor::process().
@@ -28,11 +36,12 @@ class FeedsTermProcessor extends FeedsProcessor {
       if (!($tid = $this->existingItemId($source, $parser_result)) || $this->config['update_existing'] != FEEDS_SKIP_EXISTING) {
 
         // Map item to a term.
-        $term = new stdClass();
         if ($tid && $this->config['update_existing'] == FEEDS_UPDATE_EXISTING) {
-          $term = taxonomy_term_load($tid);
+          $term = $this->loadTerm($source, $tid);
+        }
+        else {
+          $term = $this->newTerm($source);
         }
-        $term->entity_type = 'taxonomy_term';
         $term = $this->map($source, $parser_result, $term);
 
         // Check if term name is set, otherwise continue.
@@ -91,10 +100,12 @@ class FeedsTermProcessor extends FeedsProcessor {
     $vocabulary = $this->vocabulary();
     $terms = db_query("SELECT td.tid
                         FROM {taxonomy_term_data} td
-                        JOIN {feeds_term_item} ft ON td.tid = ft.tid
+                        JOIN {feeds_item} fi
+                        ON fi.entity_type = 'taxonomy_term'
+                        AND td.tid = fi.entity_id
                         WHERE td.vid = :vid
-                        AND ft.id = :id
-                        AND ft.feed_nid = :feed_nid",
+                        AND fi.id = :id
+                        AND fi.feed_nid = :feed_nid",
                         array(
                           ':vid' => $vocabulary->vid,
                           ':id' => $this->id,
@@ -114,22 +125,6 @@ class FeedsTermProcessor extends FeedsProcessor {
     }
   }
 
-  /**
-   * Execute mapping on an item.
-   */
-  protected function map(FeedsSource $source, FeedsParserResult $result, $target_term = NULL) {
-    // Prepare term object, have parent class do the iterating.
-    if (!$target_term) {
-      $target_term = new stdClass();
-    }
-    if (!$vocabulary = $this->vocabulary()) {
-      throw new Exception(t('No vocabulary specified for term processor'));
-    }
-    $target_term->vid = $vocabulary->vid;
-    $target_term = parent::map($source, $result, $target_term);
-    return $target_term;
-  }
-
   /**
    * Override parent::configDefaults().
    */
@@ -184,7 +179,8 @@ class FeedsTermProcessor extends FeedsProcessor {
    * Return available mapping targets.
    */
   public function getMappingTargets() {
-    $targets = array(
+    $targets = parent::getMappingTargets();
+    $targets += array(
       'name' => array(
         'name' => t('Term name'),
         'description' => t('Name of the taxonomy term.'),
@@ -207,6 +203,9 @@ class FeedsTermProcessor extends FeedsProcessor {
    * Get id of an existing feed item term if available.
    */
   protected function existingItemId(FeedsSource $source, FeedsParserResult $result) {
+    if ($tid = parent::existingItemId($source, $result)) {
+      return $tid;
+    }
 
     // The only possible unique target is name.
     foreach ($this->uniqueTargets($source, $result) as $target => $value) {
@@ -243,4 +242,26 @@ class FeedsTermProcessor extends FeedsProcessor {
       }
     }
   }
+
+  /**
+   * Creates a new term in memory and returns it.
+   */
+  protected function newTerm($source) {
+    $term = new stdClass();
+    $this->newItemInfo($term, $source->feed_nid);
+    $vocabulary = $this->vocabulary();
+    $term->vid = $vocabulary->vid;
+    return $term;
+  }
+
+  /**
+   * Loads an existing term.
+   */
+  protected function loadTerm($source, $tid) {
+    $term = taxonomy_term_load($tid);
+    if ($this->loadItemInfo($term)) {
+      $this->newItemInfo($term, $source->feed_nid);
+    }
+    return $term;
+  }
 }
diff --git a/plugins/FeedsUserProcessor.inc b/plugins/FeedsUserProcessor.inc
index f2a010cf94d6c3ad222b01631b967645f8e47a0f..2b7c44078c0d2aee3421dc8a7b635895ae1dabde 100644
--- a/plugins/FeedsUserProcessor.inc
+++ b/plugins/FeedsUserProcessor.inc
@@ -10,6 +10,14 @@
  * Feeds processor plugin. Create users from feed items.
  */
 class FeedsUserProcessor extends FeedsProcessor {
+  /**
+   * Define entity type.
+   */
+  protected function __construct($id) {
+    parent::__construct($id);
+    $this->entity_type = 'user';
+  }
+
 
   /**
    * Implements FeedsProcessor::process().
@@ -23,8 +31,14 @@ class FeedsUserProcessor extends FeedsProcessor {
 
       if (!($uid = $this->existingItemId($source, $parser_result)) || $this->config['update_existing']) {
 
-        // Map item to a term.
-        $account = $this->map($source, $parser_result);
+        // Load / create new user and execute mapping.
+        if (empty($uid)) {
+          $account = $this->newUser($source);
+        }
+        else {
+          $account = $this->loadUser($source, $uid);
+        }
+        $account = $this->map($source, $parser_result, $account);
 
         // Check if user name and mail are set, otherwise continue.
         if (empty($account->name) || empty($account->mail) || !valid_email_address($account->mail)) {
@@ -32,11 +46,6 @@ class FeedsUserProcessor extends FeedsProcessor {
           continue;
         }
 
-        // Add term id if available.
-        if (!empty($uid)) {
-          $account->uid = $uid;
-        }
-
         // Save the user.
         user_save($account, (array) $account);
         if ($account->uid && $account->openid) {
@@ -91,21 +100,6 @@ class FeedsUserProcessor extends FeedsProcessor {
     throw new Exception(t('User processor does not support deleting users.'));
   }
 
-  /**
-   * Execute mapping on an item.
-   */
-  protected function map(FeedsSource $source, FeedsParserResult $result) {
-    // Prepare term object.
-    $target_account = new stdClass();
-    $target_account->uid = 0;
-    $target_account->entity_type = 'user';
-    $target_account->roles = array_filter($this->config['roles']);
-    $target_account->status = $this->config['status'];
-
-    // Have parent class do the iterating.
-    return parent::map($source, $result, $target_account);
-  }
-
   /**
    * Override parent::configDefaults().
    */
@@ -153,18 +147,12 @@ class FeedsUserProcessor extends FeedsProcessor {
     return $form;
   }
 
-  /**
-   * Set target element.
-   */
-  public function setTargetElement(&$target_item, $target_element, $value) {
-    $target_item->$target_element = $value;
-  }
-
   /**
    * Return available mapping targets.
    */
   public function getMappingTargets() {
-    $targets = array(
+    $targets = parent::getMappingTargets();
+    $targets += array(
       'name' => array(
         'name' => t('User name'),
         'description' => t('Name of the user.'),
@@ -199,6 +187,9 @@ class FeedsUserProcessor extends FeedsProcessor {
    * Get id of an existing feed item term if available.
    */
   protected function existingItemId(FeedsSource $source, FeedsParserResult $result) {
+    if ($uid = parent::existingItemId($source, $result)) {
+      return $uid;
+    }
 
     // Iterate through all unique targets and try to find a user for the
     // target's value.
@@ -221,4 +212,27 @@ class FeedsUserProcessor extends FeedsProcessor {
     }
     return 0;
   }
+
+  /**
+   * Creates a new user account in memory and returns it.
+   */
+  protected function newUser($source) {
+    $account = new stdClass();
+    $account->uid = 0;
+    $account->roles = array_filter($this->config['roles']);
+    $account->status = $this->config['status'];
+    $this->newItemInfo($account, $source->feed_nid);
+    return $account;
+  }
+
+  /**
+   * Loads an existing user.
+   */
+  protected function loadUser($source, $uid) {
+    $account = user_load($uid);
+    if (!$this->loadItemInfo($account)) {
+      $this->newItemInfo($account, $source->feed_nid);
+    }
+    return $account;
+  }
 }
diff --git a/tests/feeds_parser_sitemap.test b/tests/feeds_parser_sitemap.test
index 449fefafd2092ea7eac3bcba6ebbd77ed653af96..7d16677e5f06099efe6c15ee9fd48626b34023e7 100644
--- a/tests/feeds_parser_sitemap.test
+++ b/tests/feeds_parser_sitemap.test
@@ -83,11 +83,11 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
     $this->assertText('Created 5 Article nodes.');
 
     // Assert DB status.
-    $count = db_query("SELECT COUNT(*) FROM {feeds_node_item}")->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'")->fetchField();
     $this->assertEqual($count, 5, 'Accurate number of items in database.');
 
     // Check items against known content of feed.
-    $items = db_query('SELECT * FROM {feeds_node_item} WHERE feed_nid = :nid ORDER BY nid', array(':nid' => $nid));
+    $items = db_query("SELECT * FROM {feeds_item} WHERE entity_type = 'node' AND feed_nid = :nid ORDER BY nid", array(':nid' => $nid));
 
     // Check first item.
     date_default_timezone_set('GMT');
@@ -96,8 +96,9 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
     $this->assertEqual($node->title, 'monthly', 'Feed item 1 changefreq is correct.');
     $this->assertEqual($node->body, '0.8', 'Feed item 1 priority is correct.');
     $this->assertEqual($node->created, strtotime('2005-01-01'), 'Feed item 1 lastmod is correct.');
-    $this->assertEqual($node->feeds_node_item->url, 'http://www.example.com/', 'Feed item 1 url is correct.');
-    $this->assertEqual($node->feeds_node_item->url, $node->feeds_node_item->guid, 'Feed item 1 guid is correct.');
+    $info = feeds_item_info_load('node', $node->nid);
+    $this->assertEqual($info->url, 'http://www.example.com/', 'Feed item 1 url is correct.');
+    $this->assertEqual($info->url, $info->guid, 'Feed item 1 guid is correct.');
 
     // Check second item.
     $item = $items->fetch();
@@ -105,8 +106,9 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
     $this->assertEqual($node->title, 'weekly', 'Feed item 2 changefreq is correct.');
     $this->assertEqual($node->body, '', 'Feed item 2 priority is correct.');
     // $node->created is... recently
-    $this->assertEqual($node->feeds_node_item->url, 'http://www.example.com/catalog?item=12&desc=vacation_hawaii', 'Feed item 2 url is correct.');
-    $this->assertEqual($node->feeds_node_item->url, $node->feeds_node_item->guid, 'Feed item 2 guid is correct.');
+    $info = feeds_item_info_load('node', $node->nid);
+    $this->assertEqual($info->url, 'http://www.example.com/catalog?item=12&desc=vacation_hawaii', 'Feed item 2 url is correct.');
+    $this->assertEqual($info->url, $info->guid, 'Feed item 2 guid is correct.');
 
     // Check third item.
     $item = $items->fetch();
@@ -114,8 +116,9 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
     $this->assertEqual($node->title, 'weekly', 'Feed item 3 changefreq is correct.');
     $this->assertEqual($node->body, '', 'Feed item 3 priority is correct.');
     $this->assertEqual($node->created, strtotime('2004-12-23'), 'Feed item 3 lastmod is correct.');
-    $this->assertEqual($node->feeds_node_item->url, 'http://www.example.com/catalog?item=73&desc=vacation_new_zealand', 'Feed item 3 url is correct.');
-    $this->assertEqual($node->feeds_node_item->url, $node->feeds_node_item->guid, 'Feed item 3 guid is correct.');
+    $info = feeds_item_info_load('node', $node->nid);
+    $this->assertEqual($info->url, 'http://www.example.com/catalog?item=73&desc=vacation_new_zealand', 'Feed item 3 url is correct.');
+    $this->assertEqual($info->url, $info->guid, 'Feed item 3 guid is correct.');
 
     // Check fourth item.
     $item = $items->fetch();
@@ -123,8 +126,9 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
     $this->assertEqual($node->title, '', 'Feed item 4 changefreq is correct.');
     $this->assertEqual($node->body, '0.3', 'Feed item 4 priority is correct.');
     $this->assertEqual($node->created, strtotime('2004-12-23T18:00:15+00:00'), 'Feed item 4 lastmod is correct.');
-    $this->assertEqual($node->feeds_node_item->url, 'http://www.example.com/catalog?item=74&desc=vacation_newfoundland', 'Feed item 4 url is correct.');
-    $this->assertEqual($node->feeds_node_item->url, $node->feeds_node_item->guid, 'Feed item 1 guid is correct.');
+    $info = feeds_item_info_load('node', $node->nid);
+    $this->assertEqual($info->url, 'http://www.example.com/catalog?item=74&desc=vacation_newfoundland', 'Feed item 4 url is correct.');
+    $this->assertEqual($info->url, $info->guid, 'Feed item 1 guid is correct.');
 
     // Check fifth item.
     $item = $items->fetch();
@@ -132,8 +136,9 @@ class FeedsSitemapParserTestCase extends FeedsWebTestCase {
     $this->assertEqual($node->title, '', 'Feed item 5 changefreq is correct.');
     $this->assertEqual($node->body, '', 'Feed item 5 priority is correct.');
     $this->assertEqual($node->created, strtotime('2004-11-23'), 'Feed item 5 lastmod is correct.');
-    $this->assertEqual($node->feeds_node_item->url, 'http://www.example.com/catalog?item=83&desc=vacation_usa', 'Feed item 5 url is correct.');
-    $this->assertEqual($node->feeds_node_item->url, $node->feeds_node_item->guid, 'Feed item 5 guid is correct.');
+    $info = feeds_item_info_load('node', $node->nid);
+    $this->assertEqual($info->url, 'http://www.example.com/catalog?item=83&desc=vacation_usa', 'Feed item 5 url is correct.');
+    $this->assertEqual($info->url, $info->guid, 'Feed item 5 guid is correct.');
 
     // Check for more items.
     $item = $items->fetch();
diff --git a/tests/feeds_processor_node.test b/tests/feeds_processor_node.test
index e806cbabbd3631256f656a7df8efed3b368a0d91..625b41723697a01ee7353f4221d6acaf5ee7d5a0 100644
--- a/tests/feeds_processor_node.test
+++ b/tests/feeds_processor_node.test
@@ -89,7 +89,7 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
     $this->drupalGet('node/'. $article_nid);
     $this->assertNoRaw('node/'. $article_nid .'/import');
     $this->assertNoRaw('node/'. $article_nid .'/delete-items');
-    $this->assertEqual("Created/updated by FeedsNodeProcessor", db_query("SELECT nr.log FROM {node} n JOIN {node_revision} nr ON n.vid = nr.vid WHERE n.nid = :nid", array(':nid' => $article_nid))->fetchField());
+    $this->assertEqual("Created by FeedsNodeProcessor", db_query("SELECT nr.log FROM {node} n JOIN {node_revision} nr ON n.vid = nr.vid WHERE n.nid = :nid", array(':nid' => $article_nid))->fetchField());
 
     // Assert accuracy of aggregated information.
     $this->drupalGet('node');
@@ -97,7 +97,7 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
     $this->assertDevseedFeedContent();
 
     // Assert DB status.
-    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_node_item} fn ON n.nid = fn.nid")->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id")->fetchField();
     $this->assertEqual($count, 10, 'Accurate number of items in database.');
 
     // Assert default input format on first imported feed node.
@@ -112,12 +112,12 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
     $this->assertText('There is no new content.');
 
     // Assert DB status, there still shouldn't be more than 10 items.
-    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_node_item} fn ON n.nid = fn.nid")->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id")->fetchField();
     $this->assertEqual($count, 10, 'Accurate number of items in database.');
 
     // All of the above tests should have produced published nodes, set default
     // to unpublished, import again.
-    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_node_item} fn ON n.nid = fn.nid WHERE n.status = 1")->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE n.status = 1")->fetchField();
     $this->assertEqual($count, 10, 'All items are published.');
     $edit = array(
       'node_options[status]' => FALSE,
@@ -125,7 +125,7 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
     $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
     $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
     $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
-    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_node_item} fn ON n.nid = fn.nid WHERE n.status = 0")->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {node} n INNER JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id WHERE n.status = 0")->fetchField();
     $this->assertEqual($count, 10, 'No items are published.');
     $edit = array(
       'node_options[status]' => TRUE,
@@ -171,7 +171,7 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
     // Assert author.
     $this->drupalGet('node');
     $this->assertPattern('/<span class="username">' . check_plain($this->auth_user->name) . '<\/span>/');
-    $count = db_query("SELECT COUNT(*) FROM {feeds_node_item} fi JOIN {node} n ON fi.nid = n.nid WHERE n.uid = :uid", array(':uid' => $this->auth_user->uid))->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {feeds_item} fi JOIN {node} n ON fi.entity_type = 'node' AND fi.entity_id = n.nid WHERE n.uid = :uid", array(':uid' => $this->auth_user->uid))->fetchField();
     $this->assertEqual($count, 10, 'Accurate number of items in database.');
 
     // Assert input format.
@@ -183,11 +183,11 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
 
     // Set to update existing, remove authorship of above nodes and import again.
     $this->setSettings('syndication', 'FeedsNodeProcessor', array('update_existing' => 2));
-    db_query("UPDATE {node} n JOIN {feeds_node_item} fi ON n.nid = fi.nid SET n.uid = 0, fi.hash=''");
+    db_query("UPDATE {node} n JOIN {feeds_item} fi ON fi.entity_type = 'node' AND n.nid = fi.entity_id SET n.uid = 0, fi.hash=''");
     $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
     $this->drupalGet('node');
     $this->assertNoPattern('/<span class="username">' . check_plain($this->auth_user->name) . '<\/span>/');
-    $count = db_query("SELECT COUNT(*) FROM {feeds_node_item} fi JOIN {node} n ON fi.nid = n.nid WHERE n.uid = :uid", array(':uid' => $this->auth_user->uid))->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {feeds_item} fi JOIN {node} n ON fi.entity_type = 'node' AND fi.entity_id = n.nid WHERE n.uid = :uid", array(':uid' => $this->auth_user->uid))->fetchField();
     $this->assertEqual($count, 0, 'Accurate number of items in database.');
 
     // Map feed node's author to feed item author, update - feed node's items
@@ -327,11 +327,11 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase {
   }
 
   /**
-   * Check that the total number of entries in the feeds_node_item table is correct.
+   * Check that the total number of entries in the feeds_item table is correct.
    */
   function assertFeedItemCount($num) {
     // Assert DB status, there should be 10 again.
-    $count = db_query("SELECT COUNT(*) FROM {feeds_node_item}")->fetchField();
+    $count = db_query("SELECT COUNT(*) FROM {feeds_item} WHERE entity_type = 'node'")->fetchField();
     $this->assertEqual($count, $num, 'Accurate number of items in database.');
   }
 
diff --git a/views/feeds.views.inc b/views/feeds.views.inc
index 2fc30e9c0281ffc31ab9a5e0ed6ffc35bb6779a9..0f321be3884c5527620dc75959a87bab1fec7ddb 100644
--- a/views/feeds.views.inc
+++ b/views/feeds.views.inc
@@ -67,19 +67,12 @@ function feeds_views_data() {
   /**
    * Expose feeds_node_item table to views.
    */
-  $data['feeds_node_item']['table'] = array(
-    'group' => 'Feeds Item',
-    'join' => array(
-      'node' => array(
-        'left_field' => 'nid',
-        'field' => 'nid',
-        'type' => 'LEFT',
-      ),
-    ),
+  $data['feeds_item']['table'] = array(
+    'group' => 'Feeds item',
   );
-  $data['feeds_node_item']['feed_nid'] = array(
+  $data['feeds_item']['feed_nid'] = array(
     'title' => t('Owner feed nid'),
-    'help' => t('The node id of the owner feed if available.'),
+    'help' => t('The node id of the owner feed node if available.'),
     'field' => array(
       'handler' => 'views_handler_field_numeric',
       'click sortable' => TRUE,
@@ -100,13 +93,13 @@ function feeds_views_data() {
     ),
     'relationship' => array(
       'title' => t('Owner feed'),
-      'help' => t('Relate a node to its owner feed node if available.'),
+      'help' => t('Relate a feed item to its owner feed node if available.'),
       'label' => t('Owner feed'),
       'base' => 'node',
       'base field' => 'nid',
     ),
   );
-  $data['feeds_node_item']['url'] = array(
+  $data['feeds_item']['url'] = array(
     'title' => t('Item URL'),
     'help' => t('Contains the URL of the feed item.'),
     'field' => array(
@@ -128,7 +121,7 @@ function feeds_views_data() {
       'help' => t('Sort on a Feeds Item\'s URL field.'),
     ),
   );
-  $data['feeds_node_item']['guid'] = array(
+  $data['feeds_item']['guid'] = array(
     'title' => t('Item GUID'),
     'help' => t('Contains the GUID of the feed item.'),
     'field' => array(
@@ -149,7 +142,7 @@ function feeds_views_data() {
       'help' => t('Sort on a Feeds Item\'s GUID field.'),
     ),
   );
-  $data['feeds_node_item']['imported'] = array(
+  $data['feeds_item']['imported'] = array(
     'title' => t('Import date'),
     'help' => t('Contains the import date of the feed item.'),
     'field' => array(
@@ -171,6 +164,23 @@ function feeds_views_data() {
       'help' => t('Argument on a Feeds Item\'s import date field.'),
     ),
   );
+
+  foreach (array('node', 'taxonomy_term', 'user') as $entity_type) {
+    $info = entity_get_info($entity_type);
+    $data['feeds_item']['table']['join'][$info['base table']] = array(
+      'left_field' => $info['entity keys']['id'],
+      'field' => 'entity_id',
+      'type' => 'LEFT',
+      'extra' => array(
+        array(
+          'table' => 'feeds_item',
+          'field' => 'entity_type',
+          'value' => $entity_type,
+          'operator' => '=',
+        ),
+      ),
+    );
+  }
   return $data;
 }