? .git ? .gitignore ? 632920-204_inherit.patch ? 632920-205_inherit.patch ? 632920_192.patch ? 632920_inheritance.patch ? 755556-11_local_files.patch ? 755556-13_local_files.patch ? libraries/simplepie.inc Index: feeds.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.api.php,v retrieving revision 1.12 diff -u -p -r1.12 feeds.api.php --- feeds.api.php 8 Jul 2010 17:19:16 -0000 1.12 +++ feeds.api.php 6 Sep 2010 16:56:38 -0000 @@ -108,6 +108,47 @@ function hook_feeds_after_import(FeedsIm */ /** + * Alter mapping sources. + * + * Use this hook to add additional mapping sources for any parser. Allows for + * registering a callback to be invoked at mapping time. + * + * my_callback(FeedsImportBatch $batch, $key) + * + * @see my_source_callback(). + * @see locale_feeds_parser_sources_alter(). + */ +function hook_feeds_parser_sources_alter(&$sources, $content_type) { + $sources['my_source'] = array( + 'name' => t('Images in description element'), + 'description' => t('Images occuring in the description element of a feed item.'), + 'callback' => 'my_source_get_source', + ); +} + +/** + * Callback specified in hook_feeds_parser_sources_alter(). + * + * To be invoked on mapping time. + * + * @param $batch + * The FeedsImportBatch object being mapped from. + * @param $key + * The key specified in the $sources array in + * hook_feeds_parser_sources_alter(). + * + * @return + * The value to be extracted from the source. + * + * @see hook_feeds_parser_sources_alter(). + * @see locale_feeds_get_source(). + */ +function my_source_get_source(FeedsImportBatch $batch, $key) { + $item = $batch->currentItem(); + return my_source_parse_images($item['description']); +} + +/** * Alter mapping targets for users. Use this hook to add additional target * options to the mapping form of User processors. * Index: includes/FeedsBatch.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/includes/FeedsBatch.inc,v retrieving revision 1.14 diff -u -p -r1.14 FeedsBatch.inc --- includes/FeedsBatch.inc 27 Jul 2010 20:27:40 -0000 1.14 +++ includes/FeedsBatch.inc 6 Sep 2010 16:56:38 -0000 @@ -147,10 +147,12 @@ class FeedsImportBatch extends FeedsBatc protected $link; protected $items; protected $raw; + protected $feed_nid; + protected $current_item; public $created; public $updated; - public function __construct($raw = '') { + public function __construct($raw = '', $feed_nid = 0) { parent::__construct(); $this->progress = array( FEEDS_FETCHING => FEEDS_BATCH_COMPLETE, @@ -162,6 +164,7 @@ class FeedsImportBatch extends FeedsBatc $this->link = ''; $this->items = array(); $this->raw = $raw; + $this->feed_nid = $feed_nid; $this->created = 0; $this->updated = 0; } @@ -200,6 +203,15 @@ class FeedsImportBatch extends FeedsBatc } /** + * Return the feed node related to this batch object. + */ + public function feedNode() { + if ($this->feed_nid) { + return node_load($this->feed_nid); + } + } + + /** * @return * A string that is the feed's title. */ @@ -225,12 +237,25 @@ class FeedsImportBatch extends FeedsBatc } /** + * @todo Move to a nextItem() based approach, not consuming the item array. + * Can only be done once we don't cache the entire batch object between page + * loads for batching anymore. + * * @return * Next available item or NULL if there is none. Every returned item is * removed from the internal array. */ public function shiftItem() { - return array_shift($this->items); + $this->current_item = array_shift($this->items); + return $this->current_item; + } + + /** + * @return + * Current feed item. + */ + public function currentItem() { + return empty($this->current_item) ? NULL : $this->current_item; } /** Index: mappers/locale.inc =================================================================== RCS file: mappers/locale.inc diff -N mappers/locale.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mappers/locale.inc 6 Sep 2010 16:56:38 -0000 @@ -0,0 +1,53 @@ + t('Feed node: Language'), + 'description' => t('Language of the feed node.'), + 'callback' => 'locale_feeds_get_source', + ); + } +} + +/** + * Callback, returns specific locale settings of the parent feed node. + */ +function locale_feeds_get_source(FeedsImportBatch $batch, $key) { + if ($node = $batch->feedNode()) { + return $node->language; + } +} + +/** + * Implementation of hook_feeds_node_processor_targets_alter(). + */ +function locale_feeds_node_processor_targets_alter(&$targets, $content_type) { + if (variable_get('language_content_type_'. $content_type, FALSE)) { + $targets['language'] = array( + 'name' => t('Language'), + 'callback' => 'language_feeds_set_target', + 'description' => t('The language of the node.'), + ); + } +} + +/** + * Callback for mapping. + */ +function language_feeds_set_target($node, $key, $language) { + $node->language = $language; +} Index: mappers/og.inc =================================================================== RCS file: mappers/og.inc diff -N mappers/og.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mappers/og.inc 6 Sep 2010 16:56:38 -0000 @@ -0,0 +1,56 @@ + t('Feed node: Organic group(s)'), + 'description' => t('One or more organic groups that the feed node is part of or the organic group that the feed node represents.'), + 'callback' => 'og_feeds_get_source', + ); + } +} + +/** + * Callback, returns OG of feed node. + */ +function og_feeds_get_source(FeedsImportBatch $batch, $key) { + if ($node = $batch->feedNode()) { + if (in_array($node->type, og_get_types('group'))) { + return array( + $node->nid => $node->nid, + ); + } + else { + return isset($node->og_groups) ? $node->og_groups : NULL; + } + } +} + +/** + * Implementation of hook_feeds_node_processor_targets_alter(). + */ +function og_feeds_node_processor_targets_alter(&$targets, $content_type) { + if (in_array($content_type, og_get_types('group_post'))) { + $targets['og_groups'] = array( + 'name' => t('Organic group(s)'), + 'callback' => 'og_feeds_set_target', + 'description' => t('One or more organic groups that the node is part of.'), + ); + } +} + +/** + * Callback for mapping. + */ +function og_feeds_set_target($node, $key, $groups) { + $node->og_groups = $groups; +} Index: mappers/taxonomy.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/mappers/taxonomy.inc,v retrieving revision 1.3 diff -u -p -r1.3 taxonomy.inc --- mappers/taxonomy.inc 3 Sep 2010 21:22:44 -0000 1.3 +++ mappers/taxonomy.inc 6 Sep 2010 16:56:38 -0000 @@ -7,6 +7,56 @@ */ /** + * Encapsulates a taxonomy style term object. + */ +class FeedsTermElement extends FeedsElement { + public $tid, $vid; + public function __construct($term) { + parent::__construct($term->name); + $this->tid = $term->tid; + $this->vid = $term->vid; + } +} + +/** + * Implementation of hook_feeds_parser_sources_alter(). + */ +function taxonomy_feeds_parser_sources_alter(&$sources, $content_type) { + if (!empty($content_type)) { + //options to inherit terms for all vocabs I have + $vocabs = taxonomy_get_vocabularies($content_type); + foreach($vocabs as $voc) { + $vid = $voc->vid; + if (strpos($voc->module, 'features_') === 0) { + $vid = $voc->module; + } + $sources['parent:taxonomy:'. $vid] = array( + 'name' => t('Feed node: Taxonomy: @vocabulary', array('@vocabulary' => $voc->name)), + 'description' => t('Taxonomy terms from feed node in given vocabulary.'), + 'callback' => 'taxonomy_feeds_get_source', + ); + } + } +} + +/** + * Callback, returns taxonomy from feed node. + */ +function taxonomy_feeds_get_source(FeedsImportBatch $batch, $key) { + if ($node = $batch->feedNode()) { + $terms = taxonomy_node_get_terms($node); + $vocab_id = (int) str_replace('parent:taxonomy:', '', $key); + $result = array(); + foreach ($terms as $tid => $term) { + if ($term->vid == $vocab_id) { + $result[] = new FeedsTermElement($term); + } + } + return $result; + } +} + +/** * Implementation of hook_feeds_node_processor_targets_alter(). * * @see FeedsNodeProcessor::getMappingTargets(). @@ -62,9 +112,12 @@ function taxonomy_feeds_set_target(&$nod $node->taxonomy['tags'][$vocab->vid] = implode(',', $terms); } else { - foreach ($terms as $term_name) { + foreach ($terms as $term) { + if ($term instanceof FeedsTermElement) { + $node->taxonomy[$vocab->vid][$term->tid] = $term->tid; + } // Check if a term already exists. - if ($terms_found = taxonomy_get_term_by_name_vid($term_name, $vocab->vid)) { + elseif ($terms_found = taxonomy_get_term_by_name_vid($term, $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; @@ -72,11 +125,10 @@ function taxonomy_feeds_set_target(&$nod break; } } - // If the vocab is not for multiple tags break after the first hit. - if (!$vocab->multiple) { - break; - } - + } + // If the vocab is not for multiple tags break after the first hit. + if (!$vocab->multiple) { + break; } } } Index: plugins/FeedsCSVParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsCSVParser.inc,v retrieving revision 1.13 diff -u -p -r1.13 FeedsCSVParser.inc --- plugins/FeedsCSVParser.inc 27 Jul 2010 17:55:44 -0000 1.13 +++ plugins/FeedsCSVParser.inc 6 Sep 2010 16:56:38 -0000 @@ -41,11 +41,18 @@ class FeedsCSVParser extends FeedsParser } /** + * Override parent::getMappingSources(). + */ + public function getMappingSources() { + return FALSE; + } + + /** * Override parent::getSourceElement() to use only lower keys. */ - public function getSourceElement($item, $element_key) { + public function getSourceElement($item, $element_key, $feed_nid = 0) { $element_key = drupal_strtolower($element_key); - return isset($item[$element_key]) ? $item[$element_key] : ''; + return parent::getSourceElement($item, $element_key, $feed_nid); } /** Index: plugins/FeedsDataProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsDataProcessor.inc,v retrieving revision 1.16 diff -u -p -r1.16 FeedsDataProcessor.inc --- plugins/FeedsDataProcessor.inc 27 Jul 2010 20:27:41 -0000 1.16 +++ plugins/FeedsDataProcessor.inc 6 Sep 2010 16:56:38 -0000 @@ -21,13 +21,13 @@ class FeedsDataProcessor extends FeedsPr $expiry_time = $this->expiryTime(); while ($item = $batch->shiftItem()) { - $id = $this->existingItemId($item, $source); + $id = $this->existingItemId($batch, $source); if ($id === FALSE || $this->config['update_existing']) { // Map item to a data record, feed_nid and timestamp are mandatory. $data = array(); $data['feed_nid'] = $source->feed_nid; - $data = $this->map($item, $data); + $data = $this->map($batch, $data); if (!isset($data['timestamp'])) { $data['timestamp'] = FEEDS_REQUEST_TIME; } @@ -237,8 +237,8 @@ class FeedsDataProcessor extends FeedsPr * Iterate through unique targets and try to load existing records. * Return id for the first match. */ - protected function existingItemId($source_item, FeedsSource $source) { - foreach ($this->uniqueTargets($source_item) as $target => $value) { + protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) { + foreach ($this->uniqueTargets($batch) as $target => $value) { if ($records = $this->handler()->load(array('feed_nid' => $source->feed_nid, $target => $value))) { return $records[0]['id']; } Index: plugins/FeedsFeedNodeProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsFeedNodeProcessor.inc,v retrieving revision 1.14 diff -u -p -r1.14 FeedsFeedNodeProcessor.inc --- plugins/FeedsFeedNodeProcessor.inc 27 Jul 2010 20:27:41 -0000 1.14 +++ plugins/FeedsFeedNodeProcessor.inc 6 Sep 2010 16:56:38 -0000 @@ -20,10 +20,10 @@ class FeedsFeedNodeProcessor extends Fee // 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']) { + if (!$nid = $this->existingItemId($batch, $source) || $this->config['update_existing']) { // Map item to a node. - $node = $this->map($item); + $node = $this->map($batch); // If updating populate nid and vid avoiding an expensive node_load(). if (!empty($nid)) { @@ -68,7 +68,7 @@ class FeedsFeedNodeProcessor extends Fee /** * Execute mapping on an item. */ - protected function map($source_item) { + protected function map(FeedsImportBatch $batch) { // Prepare node object. static $included; @@ -94,7 +94,7 @@ class FeedsFeedNodeProcessor extends Fee $target_node->uid = $user->uid; // Have parent class do the iterating. - return parent::map($source_item, $target_node); + return parent::map($batch, $target_node); } /** @@ -197,10 +197,10 @@ class FeedsFeedNodeProcessor extends Fee /** * Get nid of an existing feed item node if available. */ - protected function existingItemId($source_item, FeedsSource $source) { + protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) { // We only support one unique target: source - foreach ($this->uniqueTargets($source_item) as $target => $value) { + foreach ($this->uniqueTargets($batch) 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)); } Index: plugins/FeedsFileFetcher.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsFileFetcher.inc,v retrieving revision 1.13 diff -u -p -r1.13 FeedsFileFetcher.inc --- plugins/FeedsFileFetcher.inc 3 Sep 2010 21:01:18 -0000 1.13 +++ plugins/FeedsFileFetcher.inc 6 Sep 2010 16:56:38 -0000 @@ -16,9 +16,9 @@ class FeedsFileBatch extends FeedsImport /** * Constructor. */ - public function __construct($file_path) { + public function __construct($file_path, $feed_nid = 0) { $this->file_path = $file_path; - parent::__construct(); + parent::__construct('', $feed_nid); } /** @@ -49,7 +49,7 @@ class FeedsFileFetcher extends FeedsFetc */ public function fetch(FeedsSource $source) { $source_config = $source->getConfigFor($this); - return new FeedsFileBatch($source_config['source']); + return new FeedsFileBatch($source_config['source'], $source->feed_nid); } /** Index: plugins/FeedsHTTPFetcher.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsHTTPFetcher.inc,v retrieving revision 1.23 diff -u -p -r1.23 FeedsHTTPFetcher.inc --- plugins/FeedsHTTPFetcher.inc 1 Jul 2010 14:19:18 -0000 1.23 +++ plugins/FeedsHTTPFetcher.inc 6 Sep 2010 16:56:38 -0000 @@ -19,9 +19,9 @@ class FeedsHTTPBatch extends FeedsImport /** * Constructor. */ - public function __construct($url = NULL) { + public function __construct($url = NULL, $feed_nid) { $this->url = $url; - parent::__construct(); + parent::__construct('', $feed_nid); } /** @@ -48,9 +48,9 @@ class FeedsHTTPFetcher extends FeedsFetc public function fetch(FeedsSource $source) { $source_config = $source->getConfigFor($this); if ($this->config['use_pubsubhubbub'] && ($raw = $this->subscriber($source->feed_nid)->receive())) { - return new FeedsImportBatch($raw); + return new FeedsImportBatch($raw, $source->feed_nid); } - return new FeedsHTTPBatch($source_config['source']); + return new FeedsHTTPBatch($source_config['source'], $source->feed_nid); } /** Index: plugins/FeedsNodeProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsNodeProcessor.inc,v retrieving revision 1.49 diff -u -p -r1.49 FeedsNodeProcessor.inc --- plugins/FeedsNodeProcessor.inc 3 Sep 2010 21:44:03 -0000 1.49 +++ plugins/FeedsNodeProcessor.inc 6 Sep 2010 16:56:38 -0000 @@ -34,7 +34,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'] != FEEDS_SKIP_EXISTING)) { + if (!($nid = $this->existingItemId($batch, $source)) || ($this->config['update_existing'] != FEEDS_SKIP_EXISTING)) { // Only proceed if item has actually changed. $hash = $this->hash($item); if (!empty($nid) && $hash == $this->getHash($nid)) { @@ -46,7 +46,7 @@ class FeedsNodeProcessor extends FeedsPr // Map and save node. If errors occur don't stop but report them. try { - $this->map($item, $node); + $this->map($batch, $node); node_save($node); if (!empty($nid)) { $batch->updated++; @@ -131,14 +131,6 @@ class FeedsNodeProcessor extends FeedsPr } /** - * Override parent::map() to load all available add-on mappers. - */ - protected function map($source_item, $target_node) { - self::loadMappers(); - return parent::map($source_item, $target_node); - } - - /** * Return expiry time. */ public function expiryTime() { @@ -239,7 +231,7 @@ class FeedsNodeProcessor extends FeedsPr $target_node->teaser = node_teaser($value); $target_node->body = $value; } - elseif (in_array($target_element, array('title', 'status', 'created', 'nid'))) { + elseif (in_array($target_element, array('title', 'status', 'created', 'nid', 'uid'))) { $target_node->$target_element = $value; } } @@ -303,11 +295,11 @@ class FeedsNodeProcessor extends FeedsPr /** * Get nid of an existing feed item node if available. */ - protected function existingItemId($source_item, FeedsSource $source) { + protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) { // Iterate through all unique targets and test whether they do already // exist in the database. - foreach ($this->uniqueTargets($source_item) as $target => $value) { + foreach ($this->uniqueTargets($batch) as $target => $value) { switch ($target) { case 'nid': $nid = db_result(db_query("SELECT nid FROM {node} WHERE nid = %d", $value)); Index: plugins/FeedsOPMLParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsOPMLParser.inc,v retrieving revision 1.5 diff -u -p -r1.5 FeedsOPMLParser.inc --- plugins/FeedsOPMLParser.inc 29 Mar 2010 02:55:50 -0000 1.5 +++ plugins/FeedsOPMLParser.inc 6 Sep 2010 16:56:38 -0000 @@ -34,6 +34,6 @@ class FeedsOPMLParser extends FeedsParse 'name' => t('Feed URL'), 'description' => t('URL of the feed.'), ), - ); + ) + parent::getMappingSources(); } } Index: plugins/FeedsParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsParser.inc,v retrieving revision 1.17 diff -u -p -r1.17 FeedsParser.inc --- plugins/FeedsParser.inc 28 Jul 2010 21:12:43 -0000 1.17 +++ plugins/FeedsParser.inc 6 Sep 2010 16:56:39 -0000 @@ -47,7 +47,17 @@ abstract class FeedsParser extends Feeds * @endcode */ public function getMappingSources() { - return FALSE; + self::loadMappers(); + $sources = array(); + drupal_alter('feeds_parser_sources', $sources, feeds_importer($this->id)->config['content_type']); + if (!feeds_importer($this->id)->config['content_type']) { + return $sources; + } + $sources['parent:uid'] = array( + 'name' => t('Feed node: User ID'), + 'description' => t('The feed node author uid.'), + ); + return $sources; } /** @@ -60,10 +70,12 @@ abstract class FeedsParser extends Feeds * * @ingroup mappingapi * - * @param $item - * The source item that the source element should be retrieved from. + * @param $batch + * FeedsImportBatch object containing the sources to be mapped from. * @param $element_key * The key identifying the element that should be retrieved from $source + * @param $feed_nid + * The feed node's nid if the importer is attached to a content type. * * @return * The source element from $item identified by $element_key. @@ -71,7 +83,11 @@ abstract class FeedsParser extends Feeds * @see FeedsProcessor::map() * @see FeedsCSVParser::getSourceElement(). */ - public function getSourceElement($item, $element_key) { + public function getSourceElement(FeedsImportBatch $batch, $element_key) { + if (($node = $batch->feedNode()) && $element_key == 'parent:uid') { + return $node->uid; + } + $item = $batch->currentItem(); return isset($item[$element_key]) ? $item[$element_key] : ''; } } @@ -95,6 +111,8 @@ class FeedsElement { } /** + * @todo Make value public and deprecate use of getValue(). + * * @return * Standard value of this FeedsElement. */ Index: plugins/FeedsPlugin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsPlugin.inc,v retrieving revision 1.5 diff -u -p -r1.5 FeedsPlugin.inc --- plugins/FeedsPlugin.inc 29 Mar 2010 02:55:50 -0000 1.5 +++ plugins/FeedsPlugin.inc 6 Sep 2010 16:56:39 -0000 @@ -74,6 +74,31 @@ abstract class FeedsPlugin extends Feeds * A source is being deleted. */ public function sourceDelete(FeedsSource $source) {} + + /** + * Loads on-behalf implementations from mappers/ directory. + * + * FeedsProcessor::map() does not load from mappers/ as only node and user + * processor ship with on-behalf implementations. + * + * @see FeedsNodeProcessor::map() + * @see FeedsUserProcessor::map() + */ + protected static function loadMappers() { + static $loaded = FALSE; + if (!$loaded) { + $path = drupal_get_path('module', 'feeds') .'/mappers'; + $files = drupal_system_listing('.*\.inc$', $path, 'name', 0); + foreach ($files as $file) { + if (strstr($file->filename, '/mappers/')) { + require_once("./$file->filename"); + } + } + // Rebuild cache. + module_implements('', FALSE, TRUE); + } + $loaded = TRUE; + } } /** Index: plugins/FeedsProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsProcessor.inc,v retrieving revision 1.15 diff -u -p -r1.15 FeedsProcessor.inc --- plugins/FeedsProcessor.inc 27 Jul 2010 20:27:41 -0000 1.15 +++ plugins/FeedsProcessor.inc 6 Sep 2010 16:56:39 -0000 @@ -81,9 +81,13 @@ abstract class FeedsProcessor extends Fe * @see hook_feeds_term_processor_targets_alter() * @see hook_feeds_user_processor_targets_alter() */ - protected function map($source_item, $target_item = NULL) { + protected function map(FeedsImportBatch $batch, $target_item = NULL) { // Static cache $targets as getMappingTargets() may be an expensive method. + static $sources; + if (!isset($sources[$this->id])) { + $sources[$this->id] = feeds_importer($this->id)->parser->getMappingSources(); + } static $targets; if (!isset($targets[$this->id])) { $targets[$this->id] = $this->getMappingTargets(); @@ -121,10 +125,23 @@ abstract class FeedsProcessor extends Fe If the mapping specifies a callback method, use the callback instead of setTargetElement(). */ + self::loadMappers(); foreach ($this->config['mappings'] as $mapping) { - $value = $parser->getSourceElement($source_item, $mapping['source']); + // Retrieve source element's value from parser. + if (is_array($sources[$this->id][$mapping['source']]) && + isset($sources[$this->id][$mapping['source']]['callback']) && + function_exists($sources[$this->id][$mapping['source']]['callback'])) { + $callback = $sources[$this->id][$mapping['source']]['callback']; + $value = $callback($batch, $mapping['source']); + } + else { + $value = $parser->getSourceElement($batch, $mapping['source']); + } - if (is_array($targets[$this->id][$mapping['target']]) && isset($targets[$this->id][$mapping['target']]['callback']) && function_exists($targets[$this->id][$mapping['target']]['callback'])) { + // Map the source element's value to the target. + if (is_array($targets[$this->id][$mapping['target']]) && + isset($targets[$this->id][$mapping['target']]['callback']) && + function_exists($targets[$this->id][$mapping['target']]['callback'])) { $callback = $targets[$this->id][$mapping['target']]['callback']; $callback($target_item, $mapping['target'], $value); } @@ -233,12 +250,12 @@ abstract class FeedsProcessor extends Fe * * @ingroup mappingapi * - * @param $source_item - * A single item that has been aggregated from a feed. + * @param $batch + * A FeedsImportBatch object. * @param FeedsSource $source * The source information about this import. */ - protected function existingItemId($source_item, FeedsSource $source) { + protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) { return 0; } @@ -246,48 +263,23 @@ abstract class FeedsProcessor extends Fe * Utility function that iterates over a target array and retrieves all * sources that are unique. * - * @param $source_item - * A feed item from a FeedsImportBatch. + * @param $batch + * A FeedsImportBatch. * * @return * An array where the keys are target field names and the values are the * elements from the source item mapped to these targets. */ - protected function uniqueTargets($source_item) { + protected function uniqueTargets(FeedsImportBatch $batch) { $parser = feeds_importer($this->id)->parser; $targets = array(); foreach ($this->config['mappings'] as $mapping) { if ($mapping['unique']) { // Invoke the parser's getSourceElement to retrieve the value for this // mapping's source. - $targets[$mapping['target']] = $parser->getSourceElement($source_item, $mapping['source']); + $targets[$mapping['target']] = $parser->getSourceElement($batch, $mapping['source']); } } return $targets; } - - /** - * Loads on-behalf implementations from mappers/ directory. - * - * FeedsProcessor::map() does not load from mappers/ as only node and user - * processor ship with on-behalf implementations. - * - * @see FeedsNodeProcessor::map() - * @see FeedsUserProcessor::map() - */ - protected static function loadMappers() { - static $loaded = FALSE; - if (!$loaded) { - $path = drupal_get_path('module', 'feeds') .'/mappers'; - $files = drupal_system_listing('.*\.inc$', $path, 'name', 0); - foreach ($files as $file) { - if (strstr($file->filename, '/mappers/')) { - require_once("./$file->filename"); - } - } - // Rebuild cache. - module_implements('', FALSE, TRUE); - } - $loaded = TRUE; - } } Index: plugins/FeedsSimplePieParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsSimplePieParser.inc,v retrieving revision 1.12 diff -u -p -r1.12 FeedsSimplePieParser.inc --- plugins/FeedsSimplePieParser.inc 29 Mar 2010 02:55:50 -0000 1.12 +++ plugins/FeedsSimplePieParser.inc 6 Sep 2010 16:56:39 -0000 @@ -183,7 +183,7 @@ class FeedsSimplePieParser extends Feeds 'name' => t('Enclosures'), 'description' => t('An array of enclosures attached to the feed item.'), ), - ); + ) + parent::getMappingSources(); } /** Index: plugins/FeedsSitemapParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsSitemapParser.inc,v retrieving revision 1.2 diff -u -p -r1.2 FeedsSitemapParser.inc --- plugins/FeedsSitemapParser.inc 3 Sep 2010 20:59:03 -0000 1.2 +++ plugins/FeedsSitemapParser.inc 6 Sep 2010 16:56:39 -0000 @@ -51,6 +51,6 @@ class FeedsSitemapParser extends FeedsPa 'name' => t('Priority'), 'description' => t('The priority of this URL relative to other URLs on the site.'), ), - ); + ) + parent::getMappingSources(); } } Index: plugins/FeedsSyndicationParser.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsSyndicationParser.inc,v retrieving revision 1.14 diff -u -p -r1.14 FeedsSyndicationParser.inc --- plugins/FeedsSyndicationParser.inc 29 Mar 2010 02:55:50 -0000 1.14 +++ plugins/FeedsSyndicationParser.inc 6 Sep 2010 16:56:39 -0000 @@ -59,6 +59,6 @@ class FeedsSyndicationParser extends Fee 'name' => t('Categories'), 'description' => t('An array of categories that have been assigned to the feed item.'), ), - ); + ) + parent::getMappingSources(); } } Index: plugins/FeedsTermProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsTermProcessor.inc,v retrieving revision 1.12 diff -u -p -r1.12 FeedsTermProcessor.inc --- plugins/FeedsTermProcessor.inc 27 Jul 2010 20:27:41 -0000 1.12 +++ plugins/FeedsTermProcessor.inc 6 Sep 2010 16:56:39 -0000 @@ -25,7 +25,7 @@ class FeedsTermProcessor extends FeedsPr while ($item = $batch->shiftItem()) { - if (!($tid = $this->existingItemId($item, $source)) || $this->config['update_existing'] != FEEDS_SKIP_EXISTING) { + if (!($tid = $this->existingItemId($batch, $source)) || $this->config['update_existing'] != FEEDS_SKIP_EXISTING) { // Map item to a term. $term = array(); @@ -33,7 +33,7 @@ class FeedsTermProcessor extends FeedsPr $term = (array) taxonomy_get_term($tid, TRUE); $term = module_invoke_all('feeds_taxonomy_load', $term); } - $term = $this->map($item, $term); + $term = $this->map($item, $term, $source->feed_nid); // Check if term name is set, otherwise continue. if (empty($term['name'])) { @@ -111,10 +111,13 @@ class FeedsTermProcessor extends FeedsPr /** * Execute mapping on an item. */ - protected function map($source_item, $target_term = array()) { + protected function map(FeedsImportBatch $batch, $target_term = NULL) { // Prepare term object, have parent class do the iterating. + if (!$target_term) { + $target_term = array(); + } $target_term['vid'] = $this->config['vocabulary']; - $target_term = parent::map($source_item, $target_term); + $target_term = parent::map($batch, $target_term); // Taxonomy module expects synonyms to be supplied as a single string. if (isset($target_term['synonyms']) && is_array(isset($target_term['synonyms']))) { $target_term['synonyms'] = implode("\n", $target_term['synonyms']); @@ -190,10 +193,10 @@ class FeedsTermProcessor extends FeedsPr /** * Get id of an existing feed item term if available. */ - protected function existingItemId($source_item, FeedsSource $source) { + protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) { // The only possible unique target is name. - foreach ($this->uniqueTargets($source_item) as $target => $value) { + foreach ($this->uniqueTargets($batch) as $target => $value) { if ($target == 'name') { if ($tid = db_result(db_query("SELECT tid FROM {term_data} WHERE name = '%s' AND vid = %d", $value, $this->config["vocabulary"]))) { return $tid; Index: plugins/FeedsUserProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsUserProcessor.inc,v retrieving revision 1.18 diff -u -p -r1.18 FeedsUserProcessor.inc --- plugins/FeedsUserProcessor.inc 27 Jul 2010 20:27:41 -0000 1.18 +++ plugins/FeedsUserProcessor.inc 6 Sep 2010 16:56:39 -0000 @@ -21,10 +21,10 @@ class FeedsUserProcessor extends FeedsPr while ($item = $batch->shiftItem()) { - if (!($uid = $this->existingItemId($item, $source)) || $this->config['update_existing']) { + if (!($uid = $this->existingItemId($batch, $source)) || $this->config['update_existing']) { // Map item to a term. - $account = $this->map($item); + $account = $this->map($batch); // Check if user name and mail are set, otherwise continue. if (empty($account->name) || empty($account->mail) || !valid_email_address($account->mail)) { @@ -94,7 +94,7 @@ class FeedsUserProcessor extends FeedsPr /** * Execute mapping on an item. */ - protected function map($source_item) { + protected function map(FeedsImportBatch $batch) { // Prepare term object. $target_account = new stdClass(); $target_account->uid = 0; @@ -102,8 +102,7 @@ class FeedsUserProcessor extends FeedsPr $target_account->status = $this->config['status']; // Have parent class do the iterating. - self::loadMappers(); - return parent::map($source_item, $target_account); + return parent::map($batch, $target_account); } /** @@ -198,11 +197,11 @@ class FeedsUserProcessor extends FeedsPr /** * Get id of an existing feed item term if available. */ - protected function existingItemId($source_item, FeedsSource $source) { + protected function existingItemId(FeedsImportBatch $batch, FeedsSource $source) { // Iterate through all unique targets and try to find a user for the // target's value. - foreach ($this->uniqueTargets($source_item) as $target => $value) { + foreach ($this->uniqueTargets($batch) as $target => $value) { switch ($target) { case 'name': $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $value)); Index: tests/feeds.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/tests/feeds.test,v retrieving revision 1.24 diff -u -p -r1.24 feeds.test --- tests/feeds.test 3 Sep 2010 20:59:03 -0000 1.24 +++ tests/feeds.test 6 Sep 2010 16:56:39 -0000 @@ -226,6 +226,23 @@ class FeedsRSStoNodesTest extends FeedsW $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item} fi JOIN {node} n ON fi.nid = n.nid WHERE n.uid = %d", $author->uid)); $this->assertEqual($count, 0, 'Accurate number of items in database.'); + // Map feed node's author to feed item author, update - feed node's items + // should now be assigned to feed node author. + $this->addMappings('syndication', + array( + array( + 'source' => 'parent:uid', + 'target' => 'uid', + ), + ) + ); + $this->drupalPost('node/'. $nid .'/import', array(), 'Import'); + $this->drupalGet('node'); + $this->assertNoPattern('/(.*?)'. check_plain($author->name) .'<\/span>/'); + $uid = db_result(db_query("SELECT uid FROM {node} WHERE nid = %d", $nid)); + $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE uid = %d", $uid)); + $this->assertEqual($count, 11, 'All feed item nodes are assigned to feed node author.'); + // Login with new user with only access content permissions. $this->drupalLogin( $this->drupalCreateUser() Index: tests/feeds.test.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/tests/feeds.test.inc,v retrieving revision 1.12 diff -u -p -r1.12 feeds.test.inc --- tests/feeds.test.inc 18 Jul 2010 16:56:54 -0000 1.12 +++ tests/feeds.test.inc 6 Sep 2010 16:56:39 -0000 @@ -171,11 +171,7 @@ class FeedsWebTestCase extends DrupalWeb $this->assertText('has been created.'); // Get the node id from URL. - $url = $this->getUrl(); - $matches = array(); - preg_match('/node\/(\d+?)$/', $this->getUrl(), $matches); - $nid = $matches[1]; - $this->assertTrue(is_numeric($nid), 'Found node id ('. $nid .').'); + $nid = $this->getNid($this->getUrl()); // Check whether feed got recorded in feeds_source table. $this->assertEqual(1, db_result(db_query("SELECT COUNT(*) FROM {feeds_source} WHERE id = '%s' AND feed_nid = %d", $id, $nid))); @@ -315,4 +311,17 @@ class FeedsWebTestCase extends DrupalWeb } } } + + /** + * Helper function, retrieves node id from a URL. + */ + public function getNid($url) { + $matches = array(); + preg_match('/node\/(\d+?)$/', $url, $matches); + $nid = $matches[1]; + if (!is_numeric($nid)) { + $this->error(t('Could not find node id, found @nid instead.', array('@nid' => $nid))); + } + return $nid; + } } Index: tests/feeds_mapper_locale.test =================================================================== RCS file: tests/feeds_mapper_locale.test diff -N tests/feeds_mapper_locale.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/feeds_mapper_locale.test 6 Sep 2010 16:56:39 -0000 @@ -0,0 +1,116 @@ +locale mapper. + */ +class FeedsMapperLocaleTestCase extends FeedsMapperTestCase { + + public static function getInfo() { + return array( + 'name' => t('Mapper: Locale'), + 'description' => t('Test Feeds Mapper support for Locale (Language).'), + 'group' => t('Feeds'), + ); + } + + /** + * Set up the test. + */ + function setUp() { + // Call parent setup with required modules. + parent::setUp('feeds', 'feeds_ui', 'ctools', 'locale'); + + // Create user and login. + $this->drupalLogin($this->drupalCreateUser( + array( + 'administer content types', + 'administer feeds', + 'administer nodes', + 'administer site configuration', + 'administer languages', + ) + )); + + // Add an additional language and enable it for page and story. + $edit = array( + 'langcode' => 'zh-hans', + ); + $this->drupalPost('admin/settings/language/add', $edit, t('Add language')); + $this->assertText('The language Chinese, Simplified has been created and can now be used.'); + $edit = array( + 'language_content_type' => TRUE, + ); + foreach (array('story', 'page') as $type) { + $this->drupalPost("admin/content/node-type/$type", $edit, t('Save content type')); + } + + // Create an importer configuration with basic mapping. + $this->createFeedConfiguration('Syndication', 'syndication'); + $this->addMappings('syndication', + 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, + ), + ) + ); + } + + /** + * Test inheriting language from the feed node. + */ + function testInheritLanguage() { + // Map feed node's language to feed item node's language. + $this->addMappings('syndication', + array( + array( + 'source' => 'parent:language', + 'target' => 'language', + ), + ) + ); + // Turn off import on create, create feed node, add language, import. + $edit = array( + 'import_on_create' => FALSE, + ); + $this->drupalPost('admin/build/feeds/edit/syndication/settings', $edit, 'Save'); + $this->assertText('Do not import on create'); + $nid = $this->createFeedNode(); + $edit = array( + 'language' => 'zh-hans', + ); + $this->drupalPost("node/$nid/edit", $edit, t('Save')); + $this->drupalPost('node/'. $nid .'/import', array(), 'Import'); + $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE language = 'zh-hans'", $nid)); + $this->assertEqual(11, $count, 'Found correct number of nodes.'); + } +} Index: tests/feeds_mapper_og.test =================================================================== RCS file: tests/feeds_mapper_og.test diff -N tests/feeds_mapper_og.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/feeds_mapper_og.test 6 Sep 2010 16:56:39 -0000 @@ -0,0 +1,138 @@ +locale mapper. + */ +class FeedsMapperOGTestCase extends FeedsMapperTestCase { + + public static function getInfo() { + return array( + 'name' => t('Mapper: Organic Groups'), + 'description' => t('Test Feeds Mapper support for Organic Groups (Language). Requires Organic Groups module.'), + 'group' => t('Feeds'), + ); + } + + /** + * Set up the test. + */ + function setUp() { + // Call parent setup with required modules. + parent::setUp('feeds', 'feeds_ui', 'ctools', 'og'); + + // Create user and login. + $this->drupalLogin($this->drupalCreateUser( + array( + 'administer content types', + 'administer feeds', + 'administer nodes', + 'administer site configuration', + 'administer organic groups', + ) + )); + + // Add and configure a group content type, configure story, page type. + $edit = array( + 'name' => 'Group', + 'type' => 'group', + 'og_content_type_usage' => 'group', + ); + $this->drupalPost('admin/content/types/add', $edit, t('Save content type')); + $edit = array( + 'og_content_type_usage' => 'group_post_standard', + ); + foreach (array('story', 'page') as $type) { + $this->drupalPost("admin/content/node-type/$type", $edit, t('Save content type')); + } + + // Create an importer configuration with basic mapping. + $this->createFeedConfiguration('Syndication', 'syndication'); + $this->addMappings('syndication', + 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, + ), + ) + ); + } + + /** + * Test inheriting groups from the feed node. + */ + function testInheritOG() { + // Map feed node's group to feed item node's language. + $this->addMappings('syndication', + array( + array( + 'source' => 'parent:og_groups', + 'target' => 'og_groups', + ), + ) + ); + + // Create a group node. + $edit = array( + 'title' => 'Group A', + 'og_description' => 'Test group.', + ); + $this->drupalPost('node/add/group', $edit, 'Save'); + $group_nid = $this->getNid($this->getUrl()); + + // Create a feed node, add to group created above. + $edit = array( + 'feeds[FeedsHTTPFetcher][source]' => $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/developmentseed.rss2', + 'og_groups[1]' => TRUE, + ); + $this->drupalPost('node/add/page', $edit, 'Save'); + + // Count number of items for this group, should be 11. + $count = db_result(db_query("SELECT COUNT(*) FROM {og_ancestry} WHERE group_nid = %d", $group_nid)); + $this->assertEqual(11, $count, 'Found correct number of nodes in group.'); + + // Make page (= feed content type) itself a group content type, test again. + $edit = array( + 'og_content_type_usage' => 'group', + ); + $this->drupalPost('admin/content/node-type/page', $edit, 'Save content type'); + $edit = array( + 'feeds[FeedsHTTPFetcher][source]' => $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/developmentseed.rss2', + 'og_description' => 'This is a feed node that is an organic group at the same time.', + ); + $this->drupalPost('node/add/page', $edit, 'Save'); + $group_nid = $this->getNid($this->getUrl()); + + // Count number of items for this group, should be 10. + $count = db_result(db_query("SELECT COUNT(*) FROM {og_ancestry} WHERE group_nid = %d", $group_nid)); + $this->assertEqual(10, $count, 'Found correct number of nodes in group.'); + } +} Index: tests/feeds_mapper_taxonomy.test =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/tests/feeds_mapper_taxonomy.test,v retrieving revision 1.2 diff -u -p -r1.2 feeds_mapper_taxonomy.test --- tests/feeds_mapper_taxonomy.test 30 Mar 2010 20:06:36 -0000 1.2 +++ tests/feeds_mapper_taxonomy.test 6 Sep 2010 16:56:39 -0000 @@ -38,22 +38,16 @@ class FeedsMapperTaxonomyTestCase extend 'administer taxonomy', ) )); - } - - /** - * Basic test loading an RSS feed. - */ - function test() { // Add a new taxonomy vocabulary, add to story content type. $edit = array( 'name' => 'Tags', 'tags' => TRUE, 'nodes[story]' => TRUE, + 'nodes[page]' => TRUE, ); $this->drupalPost('admin/content/taxonomy/add/vocabulary', $edit, 'Save'); - - // Create a feed, include mapping to taxonomy. + // Create an importer configuration with basic mapping. $this->createFeedConfiguration('Syndication', 'syndication'); $this->addMappings('syndication', array( @@ -82,6 +76,50 @@ class FeedsMapperTaxonomyTestCase extend 'target' => 'guid', 'unique' => TRUE, ), + ) + ); + } + + /** + * Test inheriting taxonomy from the feed node. + */ + function testInheritTaxonomy() { + // Map feed node's taxonomy to feed item node's taxonomy. + $this->addMappings('syndication', + array( + array( + 'source' => 'parent:taxonomy:1', + 'target' => 'taxonomy:1', + ), + ) + ); + // Turn off import on create, create feed node, tag, import. + $edit = array( + 'import_on_create' => FALSE, + ); + $this->drupalPost('admin/build/feeds/edit/syndication/settings', $edit, 'Save'); + $this->assertText('Do not import on create'); + $nid = $this->createFeedNode(); + $terms = array('testterm1', 'testterm2', 'testterm3'); + $edit = array( + 'taxonomy[tags][1]' => implode(',', $terms), + ); + $this->drupalPost("node/$nid/edit", $edit, t('Save')); + $this->drupalPost('node/'. $nid .'/import', array(), 'Import'); + $count = db_result(db_query("SELECT COUNT(*) FROM {term_node}", $nid)); + $this->assertEqual(33, $count, 'Found correct number of tags for all feed nodes and feed items.'); + foreach ($terms as $term) { + $this->assertTaxonomyTerm($term); + } + } + + /** + * Test aggregating RSS categories to taxonomy. + */ + function testRSSCategoriesToTaxonomy() { + // Add mapping to tags vocabulary. + $this->addMappings('syndication', + array( array( 'source' => 'tags', 'target' => 'taxonomy:1', @@ -132,8 +170,7 @@ class FeedsMapperTaxonomyTestCase extend 'World Bank', ); foreach ($terms as $term) { - $term = check_plain($term); - $this->assertPattern('/'. $term .'<\/a>/', 'Found '. $term); + $this->assertTaxonomyTerm($term); } // Delete all items, all associations are gone. @@ -183,4 +220,12 @@ class FeedsMapperTaxonomyTestCase extend $this->assertEqual(29, db_result(db_query("SELECT count(*) FROM {term_data}")), "Found correct number of terms."); $this->assertEqual(39, db_result(db_query("SELECT count(*) FROM {term_node}")), "Found correct number of term-node relations.". db_result(db_query("SELECT count(*) FROM {term_node}"))); } + + /** + * Helper, finds node style taxonomy term markup in DOM. + */ + public function assertTaxonomyTerm($term) { + $term = check_plain($term); + $this->assertPattern('/'. $term .'<\/a>/', 'Found '. $term); + } }