Index: plugins/FeedsFeedNodeProcessor.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsFeedNodeProcessor.inc,v
retrieving revision 1.12
diff -u -p -r1.12 FeedsFeedNodeProcessor.inc
--- plugins/FeedsFeedNodeProcessor.inc 28 Apr 2010 22:18:30 -0000 1.12
+++ plugins/FeedsFeedNodeProcessor.inc 1 May 2010 09:56:08 -0000
@@ -10,52 +10,7 @@
* Creates *feed* nodes from feed items. The difference to FeedsNodeProcessor is
* that this plugin only creates nodes that are feed nodes themselves.
*/
-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.'));
- }
-
- return FEEDS_BATCH_COMPLETE;
- }
+class FeedsFeedNodeProcessor extends FeedsNodeProcessor {
/**
* Implementation of FeedsProcessor::clear().
@@ -70,20 +25,17 @@ class FeedsFeedNodeProcessor extends Fee
/**
* Execute mapping on an item.
*/
- protected function map($source_item) {
-
+ protected function map($source_item, $target_node) {
// Prepare node object.
static $included;
if (!$included) {
module_load_include('inc', 'node', 'node.pages');
$included = TRUE;
}
- $target_node = new stdClass();
- $target_node->type = $this->config['content_type'];
$target_node->feeds = array();
// Suppress auto import, we may be creating many feeds
$target_node->feeds['suppress_import'] = TRUE;
- node_object_prepare($target_node);
+ ($target_node);
/*
Assign an aggregated node always to current user.
@@ -103,17 +55,17 @@ class FeedsFeedNodeProcessor extends Fee
* Override parent::configDefaults().
*/
public function configDefaults() {
- return array(
- 'content_type' => '',
- 'update_existing' => 0,
- 'mappings' => array(),
- );
+ $defaults = parent::configDefaults();
+ $defaults['content_type'] = ''; // reset content type
+ return $defaults;
}
/**
* Override parent::configForm().
*/
public function configForm(&$form_state) {
+ $form = parent::configForm($form_state);
+
$feeds = feeds_importer_load_all();
$types = array();
foreach ($feeds as $feed) {
@@ -133,16 +85,10 @@ class FeedsFeedNodeProcessor extends Fee
$form['content_type'] = array(
'#type' => 'select',
'#title' => t('Content type'),
- '#description' => t('Choose node type to create from this feed. Only node types with attached importer configurations are listed here. Note: Users with "import !feed_id feeds" permissions will be able to import 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 default node type to create from this feed. Only node types with attached importer configurations are listed here. Note: Users with "import !feed_id feeds" permissions will be able to import 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)),
'#options' => $types,
'#default_value' => $this->config['content_type'],
);
- $form['update_existing'] = array(
- '#type' => 'checkbox',
- '#title' => t('Update existing items'),
- '#description' => t('Check if existing items should be updated from the feed.'),
- '#default_value' => $this->config['update_existing'],
- );
return $form;
}
@@ -150,18 +96,22 @@ class FeedsFeedNodeProcessor extends Fee
* Override setTargetElement to operate on a target item that is a node.
*/
public function setTargetElement($target_node, $target_element, $value) {
- if ($target_element == 'source') {
+ parent::setTargetElement($target_node, $target_element, $value);
+ if ($target_element == 'url') {
// Get the class of the feed node importer's fetcher and set the source
// property. See feeds_nodeapi() how $node->feeds gets stored.
$class = get_class($this->feedNodeImporter()->fetcher);
$target_node->feeds[$class]['source'] = $value;
}
- 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'];
+ }
}
}
@@ -169,44 +119,14 @@ class FeedsFeedNodeProcessor extends Fee
* Return available mapping targets.
*/
public function getMappingTargets() {
- $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();
}
- return 0;
}
/**
Index: tests/feeds.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/tests/feeds.test,v
retrieving revision 1.15
diff -u -p -r1.15 feeds.test
--- tests/feeds.test 28 Apr 2010 20:45:37 -0000 1.15
+++ tests/feeds.test 1 May 2010 09:56:16 -0000
@@ -841,3 +841,182 @@ class FeedsSyndicationParserTestCase ext
);
}
}
+
+/**
+ * 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 =
+'
+
+
+ OPML
+ Fri, 16 Oct 2009 02:53:17 GMT
+ Nuvole
+
+
+
+
+
+
+
+';
+
+ // 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,
+ ),
+ )
+ );
+ }
+}