diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index 932b994..451b916 100644 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -763,6 +763,7 @@ abstract class FeedsProcessor extends FeedsPlugin { 'update_non_existent' => FEEDS_SKIP_NON_EXISTENT, 'input_format' => NULL, 'skip_hash_check' => FALSE, + 'guid_unique_per_feed' => TRUE, 'bundle' => $bundle, ); } @@ -826,6 +827,14 @@ abstract class FeedsProcessor extends FeedsPlugin { '#description' => t('Force update of items even if item source data did not change.'), '#default_value' => $this->config['skip_hash_check'], ); + + $form['guid_unique_per_feed'] = array( + '#type' => 'checkbox', + '#title' => t('GUID is unique per Feed source'), + '#description' => t('GUID in Feeds module is unique per feed source by default, however in some situations it may be useful to consider the GUID globally unique so as to be able to update the same entity using more than one feed importer.'), + '#default_value' => $this->config['guid_unique_per_feed'], + ); + $form['input_format'] = array( '#type' => 'select', '#title' => t('Text format'), @@ -947,14 +956,18 @@ abstract class FeedsProcessor extends FeedsPlugin { // the database. foreach ($this->uniqueTargets($source, $result) as $target => $value) { if ($target === 'guid' || $target === 'url') { - $entity_id = db_select('feeds_item') + $query = db_select('feeds_item') ->fields('feeds_item', array('entity_id')) - ->condition('feed_nid', $source->feed_nid) ->condition('entity_type', $this->entityType()) - ->condition('id', $source->id) - ->condition($target, $value) - ->execute() - ->fetchField(); + ->condition($target, $value); + + if ($this->config['guid_unique_per_feed']) { + // GUID is unique per Feed Source. + $query->condition('feed_nid', $source->feed_nid) + ->condition('id', $source->id); + } + + $entity_id = $query->execute()->fetchField(); } if (!$entity_id && !empty($targets[$target]['unique_callbacks'])) { diff --git a/tests/feeds_mapper_unique.test b/tests/feeds_mapper_unique.test index bdc6a10..a36e3d1 100644 --- a/tests/feeds_mapper_unique.test +++ b/tests/feeds_mapper_unique.test @@ -75,4 +75,107 @@ class FeedsMapperUniqueTestCase extends FeedsMapperTestCase { $this->assertEqual('Lorem ipsum', $node2->title, 'Node 2 has the expected title.'); } + /** + * Tests processor setting "guid_unique_per_feed". + * + * The setting "guid_unique_per_feed" controls whether the GUID is unique per + * Feeds importer (the default) or unique in general. + */ + public function testGUIDGlobalUnique() { + // Create content type. + $typename = $this->createContentType(); + + // Create and configure first importer. + $this->createImporterConfiguration('Syndication', 'syndication'); + $this->setSettings('syndication', 'FeedsNodeProcessor', array('bundle' => $typename, 'update_existing' => 2)); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'title', + 'target' => 'title', + ), + )); + + // Create and configure second importer. + $this->createImporterConfiguration('Syndication2', 'syndication2'); + $this->setSettings('syndication2', 'FeedsNodeProcessor', array('bundle' => $typename, 'update_existing' => 2)); + $this->addMappings('syndication2', array( + 0 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + 1 => array( + 'source' => 'title', + 'target' => 'title', + ), + )); + + // First import two RSS feeds that have 10 items each, where one feed is + // imported with the first importer (called "syndication") and the other + // feed is imported with the second importer (called "syndication2"). Since + // the setting "guid_unique_per_feed" is expected to be enabled, the + // importers are expected to NOT overwrite each others nodes and thus 20 + // nodes should be created in total. + + // Import the first feed and assert that 10 nodes are created. + $this->importURL('syndication', $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2'); + $this->assertText('Created 10 nodes.'); + $this->assertEqual(10, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + + // Import the second feed and assert that 10 nodes are created and that + // there are 20 nodes in total. + $this->importURL('syndication2', $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed_changes.rss2'); + $this->assertText('Created 10 nodes.'); + $this->assertEqual(20, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + + // Secondly, the setting "guid_unique_per_feed" for the first importer will + // be disabled, so we can test if we can update nodes that were created with + // the second importer. Only two items of the feed that will be imported + // with the first importer differ from the feed that was imported with the + // second importer. Expected is thus that two nodes will be updated. + // Prior in changing the setting, nodes imported with the first importer are + // cleaned up, in order to have a good starting point. + + // Delete imported items from the first importer. + $this->drupalPost('import/syndication/delete-items', array(), 'Delete'); + $this->assertText('Deleted 10 nodes'); + $this->assertEqual(10, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + + // Change unique per feed to be unique globally. + $this->setSettings('syndication', 'FeedsNodeProcessor', array('guid_unique_per_feed' => FALSE)); + + // Two nodes that were imported with the second importer should be updated. + $this->importURL('syndication', $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2'); + $this->assertText('Updated 2 nodes.'); + + // Now when we delete them we should only delete the nodes we updated. + $this->drupalPost('import/syndication/delete-items', array(), 'Delete'); + $this->assertText('Deleted 2 nodes'); + $this->assertEqual(8, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + + // So there are only 8 nodes now in syndication2. + // So when we import we should only create 2. + $this->setSettings('syndication2', 'FeedsNodeProcessor', array('guid_unique_per_feed' => FALSE)); + $this->importURL('syndication', $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2'); + $this->assertText('Created 2 nodes.'); + $this->assertEqual(10, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + + // Import syndication2 to regain control of all imported nodes + $this->importURL('syndication2', $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed_changes.rss2'); + + // Now syndication will have no imported nodes. + $this->drupalPost('import/syndication/delete-items', array(), 'Delete'); + $this->assertText('There are no Nodes to be deleted.'); + + // Now syndication2 will delete all 10 nodes. + $this->drupalPost('import/syndication2/delete-items', array(), 'Delete'); + $this->assertText('Deleted 10 nodes'); + $this->assertEqual(0, db_query("SELECT COUNT(*) FROM {node}")->fetchField()); + } + }