? 753426_21.patch
? tests/feeds/.cvsignore
Index: mappers/content.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/mappers/content.inc,v
retrieving revision 1.5
diff -u -p -r1.5 content.inc
--- mappers/content.inc	16 May 2010 21:15:53 -0000	1.5
+++ mappers/content.inc	24 Jun 2010 13:58:09 -0000
@@ -39,7 +39,7 @@ function content_feeds_node_processor_ta
  */
 function content_feeds_set_target($node, $target, $value) {
 
-  $field = isset($node->$target) ? $node->$target : array();
+  $field = array();
 
   // Handle multiple value fields.
   if (is_array($value)) {
Index: mappers/filefield.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/mappers/filefield.inc,v
retrieving revision 1.4
diff -u -p -r1.4 filefield.inc
--- mappers/filefield.inc	29 Mar 2010 02:55:50 -0000	1.4
+++ mappers/filefield.inc	24 Jun 2010 13:58:09 -0000
@@ -58,7 +58,7 @@ function filefield_feeds_set_target($nod
   }
 
   // Map enclosures.
-  $items = isset($node->$field_name) ? $node->$field_name : array();
+  $items = array();
   foreach ($enclosures as $enclosure) {
     if ($file = $enclosure->getFile()) {
       $field = content_fields($field_name, $node->type);
Index: mappers/taxonomy.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/mappers/taxonomy.inc,v
retrieving revision 1.1
diff -u -p -r1.1 taxonomy.inc
--- mappers/taxonomy.inc	5 Dec 2009 01:38:27 -0000	1.1
+++ mappers/taxonomy.inc	24 Jun 2010 13:58:09 -0000
@@ -50,6 +50,15 @@ function taxonomy_feeds_set_target(&$nod
   $vocab_id = (int) str_replace('taxonomy:', '', $key);
   $vocab = taxonomy_vocabulary_load($vocab_id);
 
+  if (isset($node->taxonomy)) {
+    // Remove target vocabulary terms from node before adding new ones
+    foreach ($node->taxonomy as $term) {
+      if ($term->vid == $vocab->vid) {
+        unset($node->taxonomy[$term->tid]);
+      }
+    }
+  }
+
   // Cast a given single string to an array so we can use it.
   if (!is_array($terms)) {
     $terms = array($terms);
@@ -65,7 +74,7 @@ function taxonomy_feeds_set_target(&$nod
       if ($terms_found = taxonomy_get_term_by_name_vid($term_name, $vocab->vid)) {
         // If any terms are found add them to the node's taxonomy by found tid.
         foreach ($terms_found AS $term_found) {
-          $node->taxonomy[$vocab->vid][$term_found->tid] = $term_found->tid;
+          $node->taxonomy[$term_found->tid] = $term_found->tid;
           if (!$vocab->multiple) {
             break;
           }
Index: plugins/FeedsNodeProcessor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsNodeProcessor.inc,v
retrieving revision 1.38
diff -u -p -r1.38 FeedsNodeProcessor.inc
--- plugins/FeedsNodeProcessor.inc	19 Jun 2010 18:03:41 -0000	1.38
+++ plugins/FeedsNodeProcessor.inc	24 Jun 2010 13:58:09 -0000
@@ -9,6 +9,11 @@
 // Create or delete FEEDS_NODE_BATCH_SIZE at a time.
 define('FEEDS_NODE_BATCH_SIZE', 50);
 
+// Updating mode for existing nodes
+define('FEEDS_NODE_SKIP_EXISTING', 0);
+define('FEEDS_NODE_REPLACE_EXISTING', 1);
+define('FEEDS_NODE_UPDATE_EXISTING', 2);
+
 /**
  * Creates nodes from feed items.
  */
@@ -25,7 +30,7 @@ class FeedsNodeProcessor extends FeedsPr
     while ($item = $batch->shiftItem()) {
 
       // Create/update if item does not exist or update existing is enabled.
-      if (!($nid = $this->existingItemId($item, $source)) || $this->config['update_existing']) {
+      if (!($nid = $this->existingItemId($item, $source)) || ($this->config['update_existing'] != FEEDS_NODE_SKIP_EXISTING)) {
         $node = $this->buildNode($nid, $source->feed_nid);
 
         // Only proceed if item has actually changed.
@@ -142,7 +147,7 @@ class FeedsNodeProcessor extends FeedsPr
     $type = isset($types['story']) ? 'story' : key($types);
     return array(
       'content_type' => $type,
-      'update_existing' => 0,
+      'update_existing' => FEEDS_NODE_SKIP_EXISTING,
       'expire' => FEEDS_EXPIRE_NEVER,
       'mappings' => array(),
       'author' => 0,
@@ -179,9 +184,14 @@ class FeedsNodeProcessor extends FeedsPr
       '#default_value' => $this->config['expire'],
     );
     $form['update_existing'] = array(
-      '#type' => 'checkbox',
+      '#type' => 'radios',
       '#title' => t('Update existing items'),
-      '#description' => t('Check if existing items should be updated from the feed.'),
+      '#description' => t('Choose how existing items should be updated from the feed.'),
+      '#options' => array(
+        FEEDS_NODE_SKIP_EXISTING => 'Do not update existing item nodes',
+        FEEDS_NODE_REPLACE_EXISTING => 'Replace existing item nodes',
+        FEEDS_NODE_UPDATE_EXISTING => 'Update existing item nodes (slower than replacing them)',
+      ),
       '#default_value' => $this->config['update_existing'],
     );
     return $form;
@@ -294,8 +304,15 @@ class FeedsNodeProcessor extends FeedsPr
   protected function buildNode($nid, $feed_nid) {
     $node = new stdClass();
     if (!empty($nid)) {
-      $node->nid = $nid;
-      $node->vid = db_result(db_query("SELECT vid FROM {node} WHERE nid = %d", $nid));
+      if ($this->config['update_existing'] == FEEDS_NODE_REPLACE_EXISTING) {
+        // Replace existing item nodes
+        $node->nid = $nid;
+        $node->vid = db_result(db_query("SELECT vid FROM {node} WHERE nid = %d", $nid));
+      }
+      else {
+        // Update existing item nodes (slower than replacing them)
+        $node = node_load($nid, NULL, TRUE);
+      }
     }
     else {
       $node->created = FEEDS_REQUEST_TIME;
Index: tests/feeds.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/tests/feeds.test,v
retrieving revision 1.18
diff -u -p -r1.18 feeds.test
--- tests/feeds.test	20 Jun 2010 16:46:12 -0000	1.18
+++ tests/feeds.test	24 Jun 2010 13:58:09 -0000
@@ -144,8 +144,13 @@ class FeedsRSStoNodesTest extends FeedsW
     $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
     $this->assertEqual($count, 10, 'Accurate number of items in database.');
 
-    // Enable update existing and import updated feed file.
-    $this->setSettings('syndication', 'FeedsNodeProcessor', array('update_existing' => TRUE));
+    // We don't actually need the plugin we just want to make sure we have
+    // included FeedsNodeProcessor.inc to get the constants
+    feeds_include('FeedsImporter');
+    ctools_include('plugins');
+    ctools_plugin_load_class('feeds', 'plugins', 'FeedsNodeProcessor', 'handler');
+    // Enable FEEDS_NODE_REPLACE_EXISTING and import updated feed file.
+    $this->setSettings('syndication', 'FeedsNodeProcessor', array('update_existing' => FEEDS_NODE_REPLACE_EXISTING));
     $feed_url = $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/developmentseed_changes.rss2';
     $this->editFeedNode($nid, $feed_url);
     $this->drupalPost('node/' . $nid . '/import', array(), 'Import');
@@ -289,8 +294,13 @@ class FeedsRSStoNodesTest extends FeedsW
     $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
     $this->assertEqual($count, 10, 'Accurate number of items in database.');
 
-    // Enable update existing and import updated feed file.
-    $this->setSettings('syndication_standalone', 'FeedsNodeProcessor', array('update_existing' => TRUE));
+    // We don't actually need the plugin we just want to make sure we have
+    // included FeedsNodeProcessor.inc to get the constants
+    feeds_include('FeedsImporter');
+    ctools_include('plugins');
+    ctools_plugin_load_class('feeds', 'plugins', 'FeedsNodeProcessor', 'handler');
+    // Enable FEEDS_NODE_REPLACE_EXISTING and import updated feed file.
+    $this->setSettings('syndication_standalone', 'FeedsNodeProcessor', array('update_existing' => FEEDS_NODE_REPLACE_EXISTING));
     $feed_url = $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed_changes.rss2';
     $this->importURL('syndication_standalone', $feed_url);
     $this->assertText('Updated 2 Story nodes.');
@@ -977,3 +987,191 @@ class FeedsSitemapParserTestCase extends
     $this->assertFalse($item, 'Correct number of feed items recorded.');
   }
 }
+
+/**
+ * Test Updating an existing node
+ */
+class FeedsUpdateExistingNodeTestCase extends FeedsWebTestCase {
+
+  /**
+   * Set up test.
+   */
+  public function setUp() {
+    parent::setUp('feeds', 'feeds_ui', 'ctools');
+
+    $this->drupalLogin(
+      $this->drupalCreateUser(
+        array(
+          'administer feeds', 'administer nodes',
+        )
+      )
+    );
+  }
+
+  /**
+   * Describe this test.
+   */
+  public function getInfo() {
+    return array(
+      'name' => t('Update existing nodes'),
+      'description' => t('Regression tests to make sure nodes update properly'),
+      'group' => t('Feeds'),
+    );
+  }
+
+  /**
+   * Run tests.
+   */
+  public function test() {
+    // We don't actually need the plugin we just want to make sure we have
+    // included FeedsNodeProcessor.inc to get the constants
+    feeds_include('FeedsImporter');
+    ctools_include('plugins');
+    ctools_plugin_load_class('feeds', 'plugins', 'FeedsNodeProcessor', 'handler');
+
+    // Set the teaser length to unlimited otherwise tests looking for text on
+    // nodes will fail.
+    $edit = array(
+      'teaser_length' => 0,
+    );
+    $this->drupalPost('admin/content/node-settings', $edit, 'Save configuration');
+
+    // Create a feed.
+    $this->createFeedConfiguration('Nodes CSV', 'nodes_csv');
+    // Change some of the basic configuration.
+    $edit = array(
+      'content_type' => '',
+      'import_period' => FEEDS_SCHEDULE_NEVER,
+    );
+    $this->drupalPost('admin/build/feeds/edit/nodes_csv/settings', $edit, 'Save');
+
+    $this->setPlugin('nodes_csv', 'FeedsFileFetcher');
+    $this->setPlugin('nodes_csv', 'FeedsCSVParser');
+    // Enable FEEDS_NODE_REPLACE_EXISTING and import updated feed file.
+    $this->setSettings('nodes_csv', 'FeedsNodeProcessor', array('update_existing' => FEEDS_NODE_REPLACE_EXISTING));
+
+    $this->addMappings('nodes_csv',
+      array(
+        array(
+          'source' => 'Title',
+          'target' => 'title',
+          'unique' => FALSE,
+        ),
+        array(
+          'source' => 'Body',
+          'target' => 'body',
+          'unique' => FALSE,
+        ),
+        array(
+          'source' => 'published',
+          'target' => 'created',
+          'unique' => FALSE,
+        ),
+        array(
+          'source' => 'GUID',
+          'target' => 'guid',
+          'unique' => TRUE,
+        ),
+      )
+    );
+
+    // Import file.
+    $this->importFile('nodes_csv', $this->absolutePath() .'/tests/feeds/nodes.csv');
+
+    // Assert returning page.
+    $this->assertText('Created 8 Story nodes.');
+    $this->assertRaw('feeds/nodes.csv');
+
+    // Assert created nodes.
+    $this->drupalGet('node');
+    $this->assertText('Typi non habent');
+    $this->assertText('Eodem modo typi');
+    $this->assertText('Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.');
+    $this->assertText('Lorem ipsum');
+    $this->assertText('Ut wisi enim ad minim veniam');
+    $this->assertText('1976');
+
+    // Assert DB status as is
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
+    $this->assertEqual($count, 8, 'Found correct number of items.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1 AND uid = 0"));
+    $this->assertEqual($count, 8, 'Found correct number of items.');
+    // Do not filter on type intentionally. There shouldn't be more than 8 nodes total.
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node_revisions}"));
+    $this->assertEqual($count, 8, 'Found correct number of items.');
+
+    // Import file with changes and no titles.
+    $this->importFile('nodes_csv', $this->absolutePath() .'/tests/feeds/nodes_changes_1.csv');
+
+    // Assert returning page.
+    $this->assertText('Created 1 Story node.');
+    $this->assertRaw('feeds/nodes_changes_1.csv');
+
+    $this->drupalGet('node');
+    // We created 1 node with no title. Make sure it's there
+    $this->assertText('Eodem modo typi 2,');
+    // Since we are updating, even though our CSV doesn't have titles we should
+    // still see those titles that existed.
+    $this->assertText('Ut wisi enim ad minim veniam');
+    $this->assertText('Duis autem vel eum iriure dolor');
+    $this->assertText('Claritas est etiam');
+    $this->assertText('Eodem modo typi');
+    // Nam liber tempor has the same GUID as Lorem ipsum.
+    $this->assertNoText('Nam liber tempor');
+    $this->assertText('Lorem ipsum');
+
+    // We have made some changes to the descriptions that should show too
+    $this->assertText('CHANGE Ut wisi enim ad minim veniam');
+    $this->assertText('Typi CHANGE non habent claritatem insitam; est usus legentis');
+
+    // Assert DB status after second import
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1 AND uid = 0"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+    // Do not filter on type intentionally. There shouldn't be more than 9 nodes total.
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node_revisions}"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+
+    // Import once again.
+    $this->drupalPost('import/nodes_csv/import', array(), 'Import');
+    // There should be 2 updates (the ones updating GUID 1 to Nam Liber and
+    // then back to Lorem ipsum.
+    $this->assertText('Updated 2 Story nodes.');
+
+    // Assert DB status after third import
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1 AND uid = 0"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+    // Do not filter on type intentionally. There shouldn't be more than 9 nodes total.
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node_revisions}"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+
+    // Remove all nodes.
+    $this->drupalPost('import/nodes_csv/delete-items', array(), 'Delete');
+    $this->assertText('Deleted 9 nodes.');
+
+    // Assert DB status after deletion
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
+    $this->assertEqual($count, 0, 'Found correct number of items.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1 AND uid = 0"));
+    $this->assertEqual($count, 0, 'Found correct number of items.');
+    // Do not filter on type intentionally. There shouldn't be more than 9 nodes total.
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node_revisions}"));
+    $this->assertEqual($count, 0, 'Found correct number of items.');
+
+    // Import once again.
+    $this->drupalPost('import/nodes_csv/import', array(), 'Import');
+    $this->assertText('Created 9 Story nodes.');
+
+    // Assert DB status after new creation
+    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1 AND uid = 0"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+    // Do not filter on type intentionally. There shouldn't be more than 9 nodes total.
+    $count = db_result(db_query("SELECT COUNT(*) FROM {node_revisions}"));
+    $this->assertEqual($count, 9, 'Found correct number of items.');
+  }
+}
\ No newline at end of file
Index: tests/feeds/nodes_changes_1.csv
===================================================================
RCS file: tests/feeds/nodes_changes_1.csv
diff -N tests/feeds/nodes_changes_1.csv
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/feeds/nodes_changes_1.csv	24 Jun 2010 13:58:09 -0000
@@ -0,0 +1,11 @@
+Body,published,GUID
+"CHANGE Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.",205200720,2
+"Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.",428112720,3
+"Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum.",1151766000,1
+"Typi CHANGE non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem.",1256326995,4
+"Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.",1251936720,1
+"Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius.",946702800,5
+"Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum.",438112720,6
+"Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima.",1151066000,7
+"Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.",1201936720,8
+"Eodem modo typi 2, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.",1201936720,9
