? .git ? .gitignore ? 600584-4_batching.patch ? libraries/simplepie.inc Index: README.txt =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/README.txt,v retrieving revision 1.14 diff -u -p -r1.14 README.txt --- README.txt 10 Nov 2009 20:54:50 -0000 1.14 +++ README.txt 30 Nov 2009 14:52:58 -0000 @@ -121,6 +121,10 @@ Default: 200 Drupal Queue is enabled. http://drupal.org/project/drupal_queue +Name: feeds_node_batch_size +Default: 20 + The number of nodes feed node processor creates in one batch. + Glossary ======== Index: feeds.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.install,v retrieving revision 1.4 diff -u -p -r1.4 feeds.install --- feeds.install 20 Oct 2009 20:59:04 -0000 1.4 +++ feeds.install 30 Nov 2009 14:52:58 -0000 @@ -70,6 +70,12 @@ function feeds_schema() { 'not null' => TRUE, 'description' => t('Main source resource identifier. E. g. a path or a URL.'), ), + 'cache' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => t('Flexible cache attached to source. Used e. g. for batching.'), + 'serialize' => TRUE, + ), ), 'primary key' => array('id', 'feed_nid'), 'indexes' => array( @@ -291,4 +297,21 @@ function feeds_update_6006() { db_add_index($ret, 'feeds_schedule', 'feed_nid', array('feed_nid')); return $ret; +} + +/** + * Add cache field to source table. + */ +function feeds_update_6007() { + $ret = array(); + + $spec = array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => t('Flexible cache attached to source. Used e. g. for batching.'), + 'serialize' => TRUE, + ); + db_add_field($ret, 'feeds_source', 'cache', $spec); + + return $ret; } \ No newline at end of file Index: feeds.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.module,v retrieving revision 1.20 diff -u -p -r1.20 feeds.module --- feeds.module 16 Nov 2009 14:52:38 -0000 1.20 +++ feeds.module 30 Nov 2009 14:52:58 -0000 @@ -292,7 +292,7 @@ function feeds_nodeapi(&$node, $op, $for // Refresh feed if import on create is selected and suppress_import is // not set. if ($op == 'insert' && $importer->config['import_on_create'] && !isset($node->feeds['suppress_import'])) { - $importer->import($source); + feeds_batch_set(t('Importing'), 'import', $importer->id, $node->nid); } // Add import to scheduler. feeds_scheduler()->add($importer->id, 'import', $node->nid); @@ -381,6 +381,65 @@ function feeds_scheduler_work($feed_info */ /** + * @defgroup batch Batch functions. + */ + +/** + * Batch callback. + * + * @param $method + * Method to execute on importer; one of 'import', 'clear' or 'expire'. + * @param $id + * Identifier of an importer object. + * @param $feed_nid + * If importer is attached to content type, feed node id identifying the + * source to be imported. + * @param $context + * Batch context. + */ +function feeds_batch($method, $id, $feed_nid = 0, &$context) { + $importer = feeds_importer($id); + $source = feeds_source($importer, $feed_nid); + + switch ($method) { + case 'import': + $batch_status = $importer->import($source); + break; + case 'clear': + $batch_status = $importer->clear($source); + break; + } + $context['finished'] = ($batch_status != FEEDS_BATCH_ACTIVE); +} + +/** + * Batch helper. + * + * @param $title + * Title to show to user when executing batch. + * @param $method + * Method to execute on importer; one of 'import', 'clear' or 'expire'. + * @param $id + * Identifier of an importer object. + * @param $feed_nid + * If importer is attached to content type, feed node id identifying the + * source to be imported. + */ +function feeds_batch_set($title, $method, $id, $feed_nid = 0) { + $batch = array( + 'title' => $title, + 'operations' => array( + array('feeds_batch', array($method, $id, $feed_nid)), + ), + ); + batch_set($batch); +} + +/** + * @} End of "defgroup batch". + */ + +/** * @defgroup utility Utility functions * @{ */ Index: feeds.pages.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.pages.inc,v retrieving revision 1.5 diff -u -p -r1.5 feeds.pages.inc --- feeds.pages.inc 21 Oct 2009 23:18:30 -0000 1.5 +++ feeds.pages.inc 30 Nov 2009 14:52:58 -0000 @@ -87,7 +87,7 @@ function feeds_import_form_submit($form, // Refresh feed if import on create is selected. if ($importer->config['import_on_create']) { - $importer->import($source); + feeds_batch_set(t('Importing'), 'import', $importer->id); } // Add importer to schedule. @@ -111,9 +111,7 @@ function feeds_import_tab_form(&$form_st * Submit handler for feeds_import_tab_form(). */ function feeds_import_tab_form_submit($form, $form_state) { - $importer = feeds_importer($form['#importer_id']); - $source = feeds_source($importer, $form['#feed_nid']); - $importer->import($source); + feeds_batch_set(t('Importing'), 'import', $form['#importer_id'], $form['#feed_nid']); } /** @@ -141,7 +139,5 @@ function feeds_delete_tab_form(&$form_st * Submit handler for feeds_delete_tab_form(). */ function feeds_delete_tab_form_submit($form, &$form_state) { - $importer = feeds_importer($form['#importer_id']); - $source = feeds_source($importer, empty($form['#feed_nid']) ? 0 : $form['#feed_nid']); - $importer->clear($source); + feeds_batch_set(t('Deleting'), 'clear', $form['#importer_id'], empty($form['#feed_nid']) ? 0 : $form['#feed_nid']); } Index: includes/FeedsImporter.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsImporter.inc,v retrieving revision 1.4 diff -u -p -r1.4 FeedsImporter.inc --- includes/FeedsImporter.inc 21 Oct 2009 22:49:47 -0000 1.4 +++ includes/FeedsImporter.inc 30 Nov 2009 14:52:59 -0000 @@ -10,6 +10,10 @@ require_once(dirname(__FILE__) .'/FeedsConfigurable.inc'); require_once(dirname(__FILE__) .'/FeedsSource.inc'); +// Batch status of a FeedsImporter operation. +define('FEEDS_BATCH_ACTIVE', 'active'); +define('FEEDS_BATCH_COMPLETE', 'complete'); + /** * A Feeds result class. * @@ -23,7 +27,7 @@ abstract class FeedsResult { // The type of this result. protected $type; // The value of this result. - protected $value; + public $value; /** * Constructor: create object, validate class variables. @@ -126,32 +130,54 @@ class FeedsImporter extends FeedsConfigu /** * Import a feed: execute, fetching, parsing and processing stage. * + * @todo: Add source-level locking to avoid race conditions between users / + * cron updating a source. + * @todo: Support batching. + * * @throws Exception * If a problem with fetching, parsing or processing occured. * @todo: Iron out and document potential Exceptions. - * @todo: Support batching. * @todo: catch exceptions outside of import(), clear() and expire(). + * + * @return + * FEEDS_BATCH_COMPLETE if all items have been imported, FEEDS_BATCH_ACTIVE + * if there are more to import. + * */ public function import(FeedsSource $source) { try { - $result = $this->fetcher->fetch($source); - $result = $this->parser->parse($result, $source); - $this->processor->process($result, $source); + // Only fetch and parse fresh data if there is no result in the batch + // cache. + $result = NULL; + if (FEEDS_BATCH_COMPLETE == $this->processor->batchStatus($source)) { + $result = $this->fetcher->fetch($source); + $result = $this->parser->parse($result, $source); + } + + // Process result of parsing stage. The processor may support batching in + // which case process() may return FEEDS_BATCH_ACTIVE. + $batch_status = $this->processor->process($result, $source); } catch (Exception $e) { drupal_set_message($e->getMessage(), 'error'); } module_invoke_all('feeds_after_import', $this, $source); + + return $batch_status; } /** * Remove all items from a feed. + * + * @return + * FEEDS_BATCH_COMPLETE if all items have been cleared, FEEDS_BATCH_ACTIVE + * if there are more to clear. */ public function clear(FeedsSource $source) { try { $this->fetcher->clear($source); $this->parser->clear($source); - $this->processor->clear($source); + return $this->processor->clear($source); } catch (Exception $e) { drupal_set_message($e->getMessage(), 'error'); @@ -161,10 +187,14 @@ class FeedsImporter extends FeedsConfigu /** * Remove items older than $time. If $time is not given, processor settings * will be used. + * + * @return + * FEEDS_BATCH_COMPLETE if all items have been cleared, FEEDS_BATCH_ACTIVE + * if there are more to clear. */ public function expire($time = NULL) { try { - $this->processor->expire($time); + return $this->processor->expire($time); } catch (Exception $e) { drupal_set_message($e->getMessage(), 'error'); Index: includes/FeedsScheduler.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsScheduler.inc,v retrieving revision 1.7 diff -u -p -r1.7 FeedsScheduler.inc --- includes/FeedsScheduler.inc 25 Nov 2009 20:41:41 -0000 1.7 +++ includes/FeedsScheduler.inc 30 Nov 2009 14:52:59 -0000 @@ -208,7 +208,14 @@ class FeedsScheduler implements FeedsSch // There are 2 possible callbacks: expire or 'import'. if ($feed_info['callback'] == 'expire') { try { - $importer->expire(); + // @todo: support batching on cron by unflagging after importing. + // - unflag after import + // - set last_scheduled_time only if batch is complete. + // - will require to rename last_scheduled_time to last_import_time. + do { + $result = $importer->expire(); + } + while ($result != FEEDS_BATCH_COMPLETE); } catch (Exception $e) { watchdog('FeedsScheduler', $e->getMessage(), array(), WATCHDOG_ERROR); @@ -222,7 +229,11 @@ class FeedsScheduler implements FeedsSch return; } try { - $importer->import($source); + // @todo: support batching on cron by unflagging after importing. + do { + $result = $importer->import($source); + } + while ($result != FEEDS_BATCH_COMPLETE); } catch (Exception $e) { watchdog('FeedsScheduler', $e->getMessage(), array(), WATCHDOG_ERROR); Index: includes/FeedsSource.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsSource.inc,v retrieving revision 1.1 diff -u -p -r1.1 FeedsSource.inc --- includes/FeedsSource.inc 20 Oct 2009 21:01:35 -0000 1.1 +++ includes/FeedsSource.inc 30 Nov 2009 14:52:59 -0000 @@ -80,6 +80,10 @@ class FeedsSource extends FeedsConfigura // The FeedsImporter object that this source is expected to be used with. protected $importer; + // Cache source-related information between page loads. Used for batching. + // For an example, see FeedsImporter::import(). + protected $cache; + /** * Instantiate a unique object per class/id/feed_nid. Don't use * directly, use feeds_source() instead. @@ -126,6 +130,7 @@ class FeedsSource extends FeedsConfigura 'feed_nid' => $this->feed_nid, 'config' => $config, 'source' => $source, + 'cache' => $this->cache, ); // Make sure a source record is present at all time, try to update first, // then insert. @@ -139,13 +144,14 @@ class FeedsSource extends FeedsConfigura * Load configuration and unpack. */ public function load() { - if ($config = db_result(db_query('SELECT config FROM {feeds_source} WHERE id = "%s" AND feed_nid = %d', $this->id, $this->feed_nid))) { + if ($record = db_fetch_object(db_query('SELECT config, cache FROM {feeds_source} WHERE id = "%s" AND feed_nid = %d', $this->id, $this->feed_nid))) { // While FeedsSource cannot be exported, we still use CTool's export.inc // export definitions. // @todo: patch CTools to move constants from export.inc to ctools.module. ctools_include('export'); $this->export_type = EXPORT_IN_DATABASE; - $this->config = unserialize($config); + $this->config = unserialize($record->config); + $this->cache = unserialize($record->cache); } } @@ -158,6 +164,13 @@ class FeedsSource extends FeedsConfigura } /** + * Set cache at key to given value. + */ + public function setCache($key, $value) { + $this->cache[$key] = $value; + } + + /** * Convenience function. Returns the configuration for a specific class. * * @param FeedsSourceInterface $client Index: plugins/FeedsDataProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsDataProcessor.inc,v retrieving revision 1.4 diff -u -p -r1.4 FeedsDataProcessor.inc --- plugins/FeedsDataProcessor.inc 2 Nov 2009 20:18:35 -0000 1.4 +++ plugins/FeedsDataProcessor.inc 30 Nov 2009 14:52:59 -0000 @@ -14,7 +14,7 @@ class FeedsDataProcessor extends FeedsPr /** * Implementation of FeedsProcessor::process(). */ - public function process(FeedsParserResult $parserResult, FeedsSource $source) { + public function process(FeedsParserResult $parserResult = NULL, FeedsSource $source) { // Count number of created and updated nodes. $inserted = $updated = 0; @@ -50,6 +50,8 @@ class FeedsDataProcessor extends FeedsPr else { drupal_set_message(t('There are no new items.')); } + + return FEEDS_BATCH_COMPLETE; } /** @@ -63,6 +65,8 @@ class FeedsDataProcessor extends FeedsPr ); $num = $this->handler()->delete($clause); drupal_set_message(t('Deleted !number items.', array('!number' => $num))); + + return FEEDS_BATCH_COMPLETE; } /** @@ -83,6 +87,8 @@ class FeedsDataProcessor extends FeedsPr ); $num = $this->handler()->delete($clause); drupal_set_message(t('Expired !number records from !table.', array('!number' => $num, '!table' => $this->tableName()))); + + return FEEDS_BATCH_COMPLETE; } /** Index: plugins/FeedsFeedNodeProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsFeedNodeProcessor.inc,v retrieving revision 1.4 diff -u -p -r1.4 FeedsFeedNodeProcessor.inc --- plugins/FeedsFeedNodeProcessor.inc 18 Nov 2009 16:53:48 -0000 1.4 +++ plugins/FeedsFeedNodeProcessor.inc 30 Nov 2009 14:52:59 -0000 @@ -15,7 +15,7 @@ class FeedsFeedNodeProcessor extends Fee /** * Implementation of FeedsProcessor::process(). */ - public function process(FeedsParserResult $parserResult, FeedsSource $source) { + public function process(FeedsParserResult $parserResult = NULL, FeedsSource $source) { // Count number of created and updated nodes. $created = $updated = 0; @@ -57,6 +57,8 @@ class FeedsFeedNodeProcessor extends Fee else { drupal_set_message(t('There is no new content.')); } + + return FEEDS_BATCH_COMPLETE; } /** Index: plugins/FeedsNodeProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsNodeProcessor.inc,v retrieving revision 1.16 diff -u -p -r1.16 FeedsNodeProcessor.inc --- plugins/FeedsNodeProcessor.inc 24 Nov 2009 23:20:26 -0000 1.16 +++ plugins/FeedsNodeProcessor.inc 30 Nov 2009 14:52:59 -0000 @@ -6,6 +6,8 @@ * Class definition of FeedsNodeProcessor. */ +define('FEEDS_NODE_DEFAULT_BATCH_SIZE', 1); + /** * Creates nodes from feed items. */ @@ -14,12 +16,23 @@ class FeedsNodeProcessor extends FeedsPr /** * Implementation of FeedsProcessor::process(). */ - public function process(FeedsParserResult $parserResult, FeedsSource $source) { + public function process(FeedsParserResult $parserResult = NULL, FeedsSource $source) { - // Count number of created and updated nodes. - $created = $updated = 0; + // If there is no result passed in we assume that there is an active batch + // to work off. + if (empty($parserResult)) { + // @todo: the approach of using the result as batch context does not work + // for clearing and expiring. Further, caching state dependent data with + // the source begins to break the model of a stateless source. + $parserResult = $source->cache['FeedsNodeProcessor']; + if (!($parserResult instanceOf FeedsParserResult)) { + throw new Exception(t('Invalid result type.')); + } + } + + $count = 0; + foreach ($parserResult->value['items'] as $k => $item) { - foreach ($parserResult->value['items'] as $item) { // 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']) { @@ -42,40 +55,68 @@ class FeedsNodeProcessor extends FeedsPr node_save($node); if ($nid) { - $updated++; + $parserResult->updated++; } else { - $created++; + $parserResult->created++; } } + + // Unset items that have been processed. + unset($parserResult->value['items'][$k]); + + // Return if batch size was reached. + $count++; + if ($count >= variable_get('feeds_node_batch_size', FEEDS_NODE_DEFAULT_BATCH_SIZE)) { + $source->setCache('FeedsNodeProcessor', $parserResult); + $source->save(); + return $parserResult->status; + } } // Set messages. - if ($created) { - drupal_set_message(t('Created !number !type nodes.', array('!number' => $created, '!type' => node_get_types('name', $this->config['content_type'])))); + if ($parserResult->created) { + drupal_set_message(t('Created !number !type nodes.', array('!number' => $parserResult->created, '!type' => node_get_types('name', $this->config['content_type'])))); } - elseif ($updated) { - drupal_set_message(t('Updated !number !type nodes.', array('!number' => $updated, '!type' => node_get_types('name', $this->config['content_type'])))); + elseif ($parserResult->updated) { + drupal_set_message(t('Updated !number !type nodes.', array('!number' => $parserResult->updated, '!type' => node_get_types('name', $this->config['content_type'])))); } else { drupal_set_message(t('There is no new content.')); } + + // Cache batch with source. + $source->setCache('FeedsNodeProcessor', NULL); + $source->save(); + + return FEEDS_BATCH_COMPLETE; } /** * Implementation of FeedsProcessor::clear(). - * @todo: use batch API. */ public function clear(FeedsSource $source) { + // Count number of deleted nodes. - $deleted = 0; + $cache = $source->cache; + $deleted = empty($cache['deleted']) ? 0 : $cache['deleted']; + $items_left = FALSE; - $result = db_query('SELECT nid FROM {feeds_node_item} WHERE feed_nid = %d', $source->feed_nid); + $result = db_query_range('SELECT nid FROM {feeds_node_item} WHERE feed_nid = %d', $source->feed_nid, 0, variable_get('feeds_node_batch_size', FEEDS_NODE_DEFAULT_BATCH_SIZE)); while ($node = db_fetch_object($result)) { _feeds_node_delete($node->nid); + $items_left = TRUE; $deleted++; } + if ($items_left) { + $source->setCache('deleted', $deleted); + return FEEDS_BATCH_ACTIVE; + } + + // Reset cache. + $source->setCache('deleted', 0); + // Set message. if ($deleted) { drupal_set_message(t('Deleted !number nodes.', array('!number' => $deleted))); @@ -83,6 +124,8 @@ class FeedsNodeProcessor extends FeedsPr else { drupal_set_message(t('There is no content to be deleted.')); } + + return FEEDS_BATCH_COMPLETE; } /** @@ -103,6 +146,20 @@ class FeedsNodeProcessor extends FeedsPr while ($node = db_fetch_object($result)) { _feeds_node_delete($node->nid); } + + return FEEDS_BATCH_COMPLETE; + } + + /** + * Return the batch status. + * + * @return + * FEEDS_BATCH_ACTIVE if there is an active batch, FEEDS_BATCH_COMPLETE + * otherwise. + */ + public function batchStatus($source) { + $parserResult = $source->cache['FeedsNodeProcessor']; + return $parserResult->status == FEEDS_BATCH_ACTIVE ? FEEDS_BATCH_ACTIVE : FEEDS_BATCH_COMPLETE; } /** Index: plugins/FeedsParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsParser.inc,v retrieving revision 1.1 diff -u -p -r1.1 FeedsParser.inc --- plugins/FeedsParser.inc 20 Oct 2009 21:03:08 -0000 1.1 +++ plugins/FeedsParser.inc 30 Nov 2009 14:52:59 -0000 @@ -10,10 +10,20 @@ class FeedsParserResult extends FeedsRes // 'simple' and no special case for 'syndication'. protected $valid_types = array('simple', 'syndication'); + // A FeedsParserResult may be batched by a processor. + public $created, $updated, $deleted; + public $batch_status; + /** - * Override constructor to define a default type. + * Override constructor to define a default type and to set default values for + * batch variables. */ public function __construct($value, $type = 'simple') { + $this->created = 0; + $this->updated = 0; + $this->deleted = 0; + $this->status = FEEDS_BATCH_ACTIVE; + parent::__construct($value, $type); } } Index: plugins/FeedsProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsProcessor.inc,v retrieving revision 1.4 diff -u -p -r1.4 FeedsProcessor.inc --- plugins/FeedsProcessor.inc 31 Oct 2009 14:30:27 -0000 1.4 +++ plugins/FeedsProcessor.inc 30 Nov 2009 14:52:59 -0000 @@ -17,9 +17,13 @@ abstract class FeedsProcessor extends Fe * @param FeedsSource $source * Source information about this import. * + * @return + * FEEDS_BATCH_COMPLETE if all items have been processed, FEEDS_BATCH_ACTIVE + * if not. + * * @todo: Should it be execute()? */ - public abstract function process(FeedsParserResult $parserResult, FeedsSource $source); + public abstract function process(FeedsParserResult $parserResult = NULL, FeedsSource $source); /** * Remove all stored results or stored results up to a certain time for this @@ -32,6 +36,10 @@ abstract class FeedsProcessor extends Fe * processor's responsibility to store the feed_nid of an imported item in * the processing stage. * @todo: pass in feed_nid instead of source? + * + * @return + * FEEDS_BATCH_COMPLETE if all items have been cleared, FEEDS_BATCH_ACTIVE + * if not. */ public abstract function clear(FeedsSource $source); @@ -44,8 +52,28 @@ abstract class FeedsProcessor extends Fe * If implemented, all items produced by this configuration that are older * than FEEDS_REQUEST_TIME - $time * If $time === NULL processor should use internal configuration. + * + * @return + * FEEDS_BATCH_COMPLETE if all items have been expired, FEEDS_BATCH_ACTIVE + * if not. + */ + public function expire($time = NULL) { + return FEEDS_BATCH_COMPLETE; + } + + /** + * Return the batch status. If a batch is active, process() may be called + * with $parserResult = NULL. + * + * @return + * FEEDS_BATCH_ACTIVE if there is an active batch, FEEDS_BATCH_COMPLETE + * otherwise. + * + * @see FeedsNodeProcessor::batchStatus(). */ - public function expire($time = NULL) {} + public function batchStatus(FeedsSource $source) { + return FEEDS_BATCH_COMPLETE; + } /** * Execute mapping on an item. Index: plugins/FeedsTermProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsTermProcessor.inc,v retrieving revision 1.2 diff -u -p -r1.2 FeedsTermProcessor.inc --- plugins/FeedsTermProcessor.inc 2 Nov 2009 20:48:52 -0000 1.2 +++ plugins/FeedsTermProcessor.inc 30 Nov 2009 14:52:59 -0000 @@ -14,7 +14,7 @@ class FeedsTermProcessor extends FeedsPr /** * Implementation of FeedsProcessor::process(). */ - public function process(FeedsParserResult $parserResult, FeedsSource $source) { + public function process(FeedsParserResult $parserResult = NULL, FeedsSource $source) { if (empty($this->config['vocabulary'])) { throw new Exception(t('You must define a vocabulary for Taxonomy term processor before importing.')); @@ -69,6 +69,8 @@ class FeedsTermProcessor extends FeedsPr else { drupal_set_message(t('There are no new terms.')); } + + return FEEDS_BATCH_COMPLETE; } /** @@ -97,6 +99,8 @@ class FeedsTermProcessor extends FeedsPr else { drupal_set_message(t('No terms to be deleted.')); } + + return FEEDS_BATCH_COMPLETE; } /** Index: plugins/FeedsUserProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsUserProcessor.inc,v retrieving revision 1.2 diff -u -p -r1.2 FeedsUserProcessor.inc --- plugins/FeedsUserProcessor.inc 2 Nov 2009 20:26:57 -0000 1.2 +++ plugins/FeedsUserProcessor.inc 30 Nov 2009 14:52:59 -0000 @@ -14,7 +14,7 @@ class FeedsUserProcessor extends FeedsPr /** * Implementation of FeedsProcessor::process(). */ - public function process(FeedsParserResult $parserResult, FeedsSource $source) { + public function process(FeedsParserResult $parserResult = NULL, FeedsSource $source) { // Count number of created and updated nodes. $created = $updated = $failed = 0;