Index: feeds.plugins.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.plugins.inc,v
retrieving revision 1.6
diff -r1.6 feeds.plugins.inc
149c149
<       'parent' => 'FeedsProcessor',
---
>       'parent' => 'FeedsNodeProcessor',
Index: plugins/FeedsFeedNodeProcessor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsFeedNodeProcessor.inc,v
retrieving revision 1.14
diff -r1.14 FeedsFeedNodeProcessor.inc
13,56c13
< class FeedsFeedNodeProcessor extends FeedsProcessor {
< 
<   /**
<    * Implementation of FeedsProcessor::process().
<    */
<   public function process(FeedsImportBatch $batch, FeedsSource $source) {
<     while ($item = $batch->shiftItem()) {
< 
<       // If the target item does not exist OR if update_existing is enabled,
<       // map and save.
<       if (!$nid = $this->existingItemId($item, $source) || $this->config['update_existing']) {
< 
<         // Map item to a node.
<         $node = $this->map($item);
< 
<         // If updating populate nid and vid avoiding an expensive node_load().
<         if (!empty($nid)) {
<           $node->nid = $nid;
<           $node->vid = db_result(db_query("SELECT vid FROM {node} WHERE nid = %d", $nid));
<         }
< 
<         // Save the node.
<         node_save($node);
< 
<         if ($nid) {
<           $batch->updated++;
<         }
<         else {
<           $batch->created++;
<         }
<       }
<     }
< 
<     // Set messages.
<     if ($batch->created) {
<       drupal_set_message(format_plural($batch->created, 'Created @number @type node.', 'Created @number @type nodes.', array('@number' => $batch->created, '@type' => $this->config['content_type'])));
<     }
<     elseif ($batch->updated) {
<       drupal_set_message(format_plural($batch->updated, 'Updated @number @type node.', 'Updated @number @type nodes.', array('@number' => $batch->updated, '@type' => $this->config['content_type'])));
<     }
<     else {
<       drupal_set_message(t('There is no new content.'));
<     }
<   }
---
> class FeedsFeedNodeProcessor extends FeedsNodeProcessor {
71,72c28
<   protected function map($source_item) {
< 
---
>   protected function map($source_item, $target_node) {
79,80d34
<     $target_node = new stdClass();
<     $target_node->type = $this->config['content_type'];
84d37
<     node_object_prepare($target_node);
104,108c57,59
<     return array(
<       'content_type' => '',
<       'update_existing' => 0,
<       'mappings' => array(),
<     );
---
>     $defaults = parent::configDefaults();
>     $defaults['content_type'] = ''; // reset content type
>     return $defaults;
114a66,67
>     $form = parent::configForm($form_state);
>     
134c87
<       '#description' => t('Choose node type to create from this feed. Only node types with attached importer configurations are listed here. <strong>Note:</strong> Users with "import !feed_id feeds" permissions will be able to <strong>import</strong> nodes of the content type selected here regardless of the node level permissions. However, users with "clear !feed_id permissions" need to have sufficient node level permissions to delete the imported nodes.', array('!feed_id' => $this->id)),
---
>       '#description' => t('Choose <strong>default</strong> node type to create from this feed. Only node types with attached importer configurations are listed here. <strong>Note:</strong> Users with "import !feed_id feeds" permissions will be able to <strong>import</strong> nodes of the content type selected here regardless of the node level permissions. However, users with "clear !feed_id permissions" need to have sufficient node level permissions to delete the imported nodes.', array('!feed_id' => $this->id)),
138,144d90
<     // @todo Implement real updating.
<     $form['update_existing'] = array(
<       '#type' => 'checkbox',
<       '#title' => t('Replace existing feed nodes'),
<       '#description' => t('If an existing node is found for an imported node, replace it. Existing nodes will be determined using mappings that are a "unique target".'),
<       '#default_value' => $this->config['update_existing'],
<     );
152c98,99
<     if ($target_element == 'source') {
---
>     parent::setTargetElement($target_node, $target_element, $value);
>     if ($target_element == 'url') {
158,163c105,113
<     elseif ($target_element == 'body') {
<       $target_node->teaser = $value;
<       $target_node->body = $value;
<     }
<     elseif (in_array($target_element, array('title', 'status', 'created'))) {
<       $target_node->$target_element = $value;
---
>     elseif ($target_element == 'type') {
>       if (feeds_get_importer_id($value)) {
>         // only accept node types with attached import configuration
>         $target_node->type = $value;
>       } 
>       else {
>         // set default value
>         $target_node->type = $this->config['content_type'];  
>       }
171,206c121,127
<     $targets = array(
<       'title' => array(
<         'name' => t('Title'),
<         'description' => t('The title of the feed node.'),
<        ),
<       'status' => array(
<         'name' => t('Published status'),
<         'description' => t('Whether a feed node is published or not. 1 stands for published, 0 for not published.'),
<       ),
<       'created' => array(
<         'name' => t('Published date'),
<         'description' => t('The UNIX time when a node has been published.'),
<       ),
<       'body' => array(
<         'name' => t('Body'),
<         'description' => t('The body of the node. The teaser will be the same as the entire body.'),
<       ),
<       'source' => array(
<         'name' => t('Feed source'),
<         'description' => t('Depending on the selected fetcher, this could be for example a URL or a path to a file.'),
<         'optional_unique' => TRUE,
<       ),
<     );
<     return $targets;
<   }
< 
<   /**
<    * Get nid of an existing feed item node if available.
<    */
<   protected function existingItemId($source_item, FeedsSource $source) {
< 
<     // We only support one unique target: source
<     foreach ($this->uniqueTargets($source_item) as $target => $value) {
<       if ($target == 'source') {
<         return db_result(db_query("SELECT fs.feed_nid FROM {node} n JOIN {feeds_source} fs ON n.nid = fs.feed_nid WHERE fs.id = '%s' AND fs.source = '%s'", $this->feedNodeImporter()->id, $value));
<       }
---
>     if (empty($this->config['content_type'])) {
>       // issue warning
>       drupal_set_message(t('No default content type selected'), 'warning');
>       return array();
>     } 
>     else {
>       return parent::getMappingTargets();
208d128
<     return 0;
Index: tests/feeds.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/tests/feeds.test,v
retrieving revision 1.23
diff -r1.23 feeds.test
1033a1034,1212
> 
> /**
>  * Test importing OPML that creates feed nodes.
>  */
> class FeedsOPMLToFeedNodesTest extends FeedsWebTestCase {
>   
>   /**
>    * Describe this test.
>    */
>   public function getInfo() {
>     return array(
>       'name' => t('OPML import to feed nodes.'),
>       'description' => t('Tests a feed configuration with file import, uses OPML parser and a feed node processor.'),
>       'group' => t('Feeds'),
>     );
>   }
> 
>   /**
>    * Set up test.
>    */
>   public function setUp() {
>     parent::setUp('feeds', 'feeds_ui', 'ctools');
>     $this->drupalLogin(
>       $this->drupalCreateUser(
>         array(
>           'administer feeds', 'administer nodes',
>         )
>       )
>     );
>   }
>   
>   /**
>    * Generate an OPML test feed that points to RSS feeds.
>    */
>   public function generateOPML() {
>     $path = $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/';
> 
>     $output =
> '<?xml version="1.0" encoding="utf-8"?>
> <opml version="1.1">
> <head>
>     <title>OPML</title>
>     <dateCreated>Fri, 16 Oct 2009 02:53:17 GMT</dateCreated>
>     <ownerName>Nuvole</ownerName>
> </head>
> <body>
>   <outline text="Feeds test group" >
>        <outline title="Devseed News" text="" xmlUrl="'. $path .'developmentseed.rss2" />
>        <outline title="Drupal Planet" text="" xmlUrl="'. $path .'drupalplanet.rss2" />
>     </outline>
> </body>
> </opml>';    
> 
>     // UTF 8 encode output string and write it to disk
>     $output = utf8_encode($output);
>     $file = $this->absolute() .'/'. file_directory_path() .'/test-opml-'. $this->randomName() .'.opml';
>     $handle = fopen($file, 'w');
>     fwrite($handle, $output);
>     fclose($handle);
>     return $file;
>   }
>   
>   /**
>    * Test feedfeed node and feed node creation.
>    */
>   public function test() {
>     $this->createFeed('feed');
>     $this->createFeedFeed('feedfeed', 'feed');
>     
>     // Import OPML and assert.
>     $file = $this->generateOPML();
>     $this->importFile('feedfeed', $file);
>     $count = db_result(db_query('SELECT COUNT(*) FROM {feeds_source}'));
>     // Check that we have 3 feeds added (the feedfeed + two created feeds)
>     $this->assertEqual($count, 3, 'Found '. $count .' number of items.');
>     
>     // Assert DB status for feed nodes
>     $count = db_result(db_query('SELECT COUNT(*) FROM {feeds_node_item} WHERE id="%s"', 'feedfeed'));
>     $this->assertEqual($count, 2, 'Found '. $count .' number of items.');
>     
>     // run cron so that feeds import nodes
>     $this->drupalGet('cron.php');
>     
>      // Assert DB status for feed nodes.
>     $count = db_result(db_query('SELECT COUNT(*) FROM {feeds_node_item} WHERE id="%s"', 'feed'));
>     $this->assertEqual($count, 35, 'Accurate number of items in database: '. $count);
>   }
>   
>   public function createFeedFeed($id, $feed_id) {
>     // Create our master feed that will import a listing of feeds.
>     $this->createFeedConfiguration('Import OPML files generating feed node types.', $id);
>     
>     // Set and configure plugins.
>     $this->setPlugin($id, 'FeedsFileFetcher');
>     $this->setPlugin($id, 'FeedsOPMLParser');
>     $this->setPlugin($id, 'FeedsFeedNodeProcessor');
>     
>     // Change feed node type to recently created 'feed'.
>     $edit = array(
>       'content_type' => $feed_id,
>     );
>     $this->drupalPost('admin/build/feeds/edit/' . $id .'/settings/FeedsFeedNodeProcessor', $edit, 'Save');
>     
>     // Change some of the basic configuration.
>     $edit = array(
>       'content_type' => '', // don't attach
>       'import_period' => FEEDS_SCHEDULE_NEVER,
>     );
>     $this->drupalPost('admin/build/feeds/edit/'. $id .'/settings', $edit, 'Save');
>     
>     // Add mappings
>     $this->addMappings($id,
>       array(
>         array(
>           'source' => 'title',
>           'target' => 'title',
>           'unique' => FALSE,
>         ),
>         array(
>           'source' => 'xmlurl',
>           'target' => 'url',
>           'unique' => TRUE,
>         )
>       )
>     );
>   }
>   
>   public function createFeed($id) {
>     // Create content type
>     $this->drupalCreateContentType(array('type' => $id));
>     
>     // Create feed.
>     $this->createFeedConfiguration('Feed', $id);
>     
>     // Set and configure plugins.
>     $this->setPlugin($id, 'FeedsHTTPFetcher');
>     $this->setPlugin($id, 'FeedsSyndicationParser');
>     $this->setPlugin($id, 'FeedsNodeProcessor');
>     
>     // Change some of the basic configuration.
>     $edit = array(
>       'content_type' => $id,
>       'import_period' => 0, // we need to schedule import as often as possible, as import on create is disabled here
>       'import_on_create' => 1,
>     );
>     $this->drupalPost('admin/build/feeds/edit/' . $id .'/settings', $edit, 'Save');    
> 
>     // Add mappings
>     $this->addMappings($id,
>       array(
>         array(
>           'source' => 'title',
>           'target' => 'title',
>           'unique' => FALSE,
>         ),
>         array(
>           'source' => 'description',
>           'target' => 'body',
>           'unique' => FALSE,
>         ),
>         array(
>           'source' => 'timestamp',
>           'target' => 'created',
>           'unique' => FALSE,
>         ),
>         array(
>           'source' => 'url',
>           'target' => 'url',
>           'unique' => TRUE,
>         ),
>         array(
>           'source' => 'guid',
>           'target' => 'guid',
>           'unique' => TRUE,
>         ),
>       )
>     ); 
>   } 
> }
