Index: feeds.api.php =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.api.php,v retrieving revision 1.10 diff -u -p -r1.10 feeds.api.php --- feeds.api.php 24 Feb 2010 01:19:33 -0000 1.10 +++ feeds.api.php 5 Jun 2010 00:48:51 -0000 @@ -108,6 +108,19 @@ function hook_feeds_after_import(FeedsIm */ /** + * Alter mapping targets for users. Use this hook to add additional target + * options to the mapping form of User processors. + * + * For an example implementation, see mappers/location.inc + * + * @param: &$targets + * Array containing the targets to be offered to the user. Add to this array + * to expose additional options. Remove from this array to suppress options. + */ +function hook_feeds_user_processor_targets_alter(&$targets) { +} + +/** * Alter mapping targets for nodes. Use this hook to add additional target * options to the mapping form of Node processors. * Index: mappers/location.inc =================================================================== RCS file: mappers/location.inc diff -N mappers/location.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mappers/location.inc 5 Jun 2010 00:48:51 -0000 @@ -0,0 +1,155 @@ + 0) { + _location_feeds_location($targets, $settings); + } +} + +/** + * Implementation of feeds_node_processor_target_alter + * + * For sub elements we use the '][' construct + * + * @param $targets array of target fields + * @param $content_type + */ +function location_feeds_node_processor_targets_alter(&$targets, $content_type) { + + // location.module logic + $settings = variable_get('location_settings_node_'. $content_type, array()); + // Only add fields when collecting locations + if (isset($settings['multiple']['max']) && $settings['multiple']['max'] > 0) { + _location_feeds_location($targets, $settings); + } + + // location_cck.module logic + $info = content_types($content_type); + if (isset($info['fields']) && count($info['fields'])) { + foreach ($info['fields'] as $field_name => $field) { + $location_fields = _location_feeds_fields(); + if (in_array($field['type'], array('location'))) { + _location_feeds_fill_targets($targets, 'location_cck', $field_name, $location_fields); + } + } + } +} + +/** + * Helper function to handle node and user locations + * + * @param $targets + * @param $settings + */ +function _location_feeds_location(&$targets, $settings) { + $location_fields= _location_feeds_fields(); + + // We need the collect settings + $fields = $settings['form']['fields']; + // locpick is a compound field. So split it + $fields['locpick][user_latitude'] = $fields['locpick']; + $fields['locpick][user_longitude'] = $fields['locpick']; + unset($fields['locpick']); + + foreach ($fields as $field => $values) { + if (! $values['collect']) { + unset($location_fields[$field]); + } + } + + _location_feeds_fill_targets($targets, 'location', 'locations', $location_fields); +} + +/** + * Helper function to add target fields + * + * @param $targets + * @param $module + * Module name providing the field. + * @param $field_name + * Field name where values are supposed to get stored to. + * @param $sub_fields + * Location elements for the given $field_name + */ +function _location_feeds_fill_targets(&$targets, $module, $field_name, $sub_fields) { + foreach ($sub_fields as $sub_field => $value) { + $targets[ $field_name . ':' . $sub_field] = array( + 'name' => $module . " module: " . $field_name . '.' . t('@label', array('@label' => $value)), + 'callback' => 'location_feeds_set_target', + 'description' => t('The @label for the location of the node.', array('@label' => $sub_field)), + ); + } +} + +/** + * Helper function to get to manage target fields + * + * @return array of key/value field name/field label + */ +function _location_feeds_fields() { + static $fields; + if (isset($fields)) { + return $fields; + } + + $fields= location_field_names(TRUE); + unset($fields['locpick']); + $fields['locpick][user_latitude'] = t("Latitude"); + $fields['locpick][user_longitude'] = t("Longitude"); + + // province_name / country_name / map_link are display fields + unset($fields['province_name']); + unset($fields['country_name']); + unset($fields['map_link']); + + return $fields; +} + +/** + * Implementation of feed_set_target + * + * @param object $object + * Either a user or node object dpending on where this is called + * @param string $target + * When targeting sub arrays the '][' is used to drill down. + * Note that this implementation is lazy ... we assume just depth=2 + * @param $value + * @return object + */ +function location_feeds_set_target($object, $target, $value) { + list($field_name, $sub_field) = split(':', $target); + if (strpos($sub_field, '][') > 0) { + // TODO : make this algorithm recursive. + list($sub_field, $last_field) = split('\]\[', $sub_field, 2); + } + + if (!is_array($value)) { + $value = array($value); + } + + foreach ($value as $i => $val) { + if (isset($last_field)) { + $object->{$field_name}[$i][$sub_field][$last_field] = $val; + } + else { + $object->{$field_name}[$i][$sub_field] = $val; + } + } + return $object; +} + Index: plugins/FeedsUserProcessor.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/feeds/plugins/FeedsUserProcessor.inc,v retrieving revision 1.10 diff -u -p -r1.10 FeedsUserProcessor.inc --- plugins/FeedsUserProcessor.inc 28 Apr 2010 22:18:30 -0000 1.10 +++ plugins/FeedsUserProcessor.inc 5 Jun 2010 00:48:51 -0000 @@ -94,6 +94,26 @@ class FeedsUserProcessor extends FeedsPr } /** + * Loads on-behalf implementations from mappers/ + */ + 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; + } + + /** * Execute mapping on an item. */ protected function map($source_item) { @@ -188,6 +208,11 @@ class FeedsUserProcessor extends FeedsPr 'optional_unique' => TRUE, ); } + + // Let other modules expose mapping targets. + self::loadMappers(); + drupal_alter('feeds_user_processor_targets', $targets); + return $targets; } Index: tests/feeds_mapper_location.test =================================================================== RCS file: tests/feeds_mapper_location.test diff -N tests/feeds_mapper_location.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/feeds_mapper_location.test 5 Jun 2010 00:48:51 -0000 @@ -0,0 +1,370 @@ +location mapper. + */ +class FeedsMapperLocationTestCase extends FeedsMapperTestCase { + + public static function getInfo() { + return array( + 'name' => t('Mapper: Location'), + 'description' => t('Test Feeds Mapper support for Location.'), + 'group' => t('Feeds'), + ); + } + + /** + * Set up the test. + */ + public function setUp() { + // Call parent setup with required modules. + parent::setUp('feeds', 'feeds_ui', 'ctools', 'location', 'location_user', 'content'); + + // Create user and login. + $this->drupalLogin($this->drupalCreateUser( + array( + 'administer content types', + 'administer feeds', + 'administer nodes', + 'administer site configuration', + 'administer taxonomy', + 'administer users', + ) + )); + } + + /** + * Basic test loading an RSS feed for nodes with locations. + */ + public function testNode() { + $settings = $this->_location_defaults(); + $type = $this->drupalCreateContentType($settings); + + // Taken from _location_node_type_save_submit() to correctly + // set the location variables + variable_set('location_settings_node_'. $type->type, $settings['location_settings']); + + // @@@ Backwards compatibility variables. + // There are a few places in contrib where these variables are checked. + variable_set('location_maxnum_'. $type->type, $settings['location_settings']['multiple']['max']); + variable_set('location_defaultnum_'. $type->type, $settings['location_settings']['multiple']['add']); + + $this->refreshVariables(); + + // Test constants + $loc = array( + 0 => array( + 'street' => "93 Rue de Rivoli", + 'city' => "Paris", + 'province' => "", + 'country' => "fr", + 'postal_code' => 75001 + ), + 1 => array( + 'street' => "1600 Amphitheatre Parkway", + 'city' => "Mountain View", + 'province' => "CA", + 'country' => "us", + 'postal_code' => 94043 + ), + ); + + // Create and configure importer. + // Create a feed. + $this->createFeedConfiguration('Location import', 'location_import'); + + // Set and configure plugins. + $this->setPlugin('location_import', 'FeedsFileFetcher'); + $this->setPlugin('location_import', 'FeedsCSVParser'); + $this->setSettings('location_import', 'FeedsNodeProcessor', array('content_type' => $type->type)); + + // Go to mapping page and create a couple of mappings. + $mappings = array( + array( + 'source' => 'title', + 'target' => 'title', + 'unique' => FALSE, + ), + array( + 'source' => 'description', + 'target' => 'body', + 'unique' => FALSE, + ), + array( + 'source' => 'timestamp', + 'target' => 'created', + 'unique' => FALSE, + ), + array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => TRUE, + ), + array( + 'source' => 'street', + 'target' => 'locations:street', + ), + array( + 'source' => 'city', + 'target' => 'locations:city', + ), + array( + 'source' => 'state', + 'target' => 'locations:province', + ), + array( + 'source' => 'zip', + 'target' => 'locations:postal_code', + ), + array( + 'source' => 'country', + 'target' => 'locations:country', + ), + ); + $this->addMappings('location_import', $mappings); + + // Change some of the basic configuration. + $edit = array( + 'content_type' => '', + 'import_period' => FEEDS_SCHEDULE_NEVER, + ); + $this->drupalPost('admin/build/feeds/edit/location_import/settings', $edit, 'Save'); + + // Import CSV file. + $this->importFile('location_import', $this->absolutePath() .'/tests/feeds/location_nodes.csv'); + $this->assertText('Created 2 '. $type->type .' nodes.'); + + // Check the imported locations + $x = 0; + $res = db_query("SELECT nid FROM {node} WHERE type='%s'", $type->type); + while ($node = db_fetch_object($res)) { + $node = node_load($node->nid); + $this->assertEqual( + $loc[$x]['street'], + $node->locations[0]['street'], + t('Testing street import, expected: @e, found: @f', + array('@e' => $loc[$x]['street'], '@f' => $node->locations[0]['street'])) + ); + $this->assertEqual( + $loc[$x]['city'], + $node->locations[0]['city'], + t('Testing city import, expected: @e, found: @f', array('@e' => $loc[$x]['city'], '@f' => $node->locations[0]['city'])) + ); + $this->assertEqual( + $loc[$x]['province'], + $node->locations[0]['province'], + t('Testing province import, expected: @e, found: @f', array('@e' => $loc[$x]['province'], '@f' => $node->locations[0]['province'])) + ); + $this->assertEqual( + $loc[$x]['country'], + $node->locations[0]['country'], + t('Testing country import, expected: @e, found: @f', array('@e' => $loc[$x]['country'], '@f' => $node->locations[0]['country'])) + ); + $this->assertEqual( + $loc[$x]['postal_code'], + $node->locations[0]['postal_code'], + t('Testing country import, expected: @e, found: @f', array('@e' => $loc[$x]['postal_code'], '@f' => $node->locations[0]['postal_code'])) + ); + $x++; + } + } + + /** + * Basic test loading an RSS feed for users with locations. + */ + public function testUser() { + // expected import values: + $loc = array( + 0 => array( + 'street' => "405 Hilgard Ave.", + 'city' => "Los Angeles", + 'province' => "CA", + 'postal_code' => 90095, + 'country' => "us", + ), + 1 => array( + 'street' => "2400 Inner Campus Drive", + 'city' => "Austin", + 'province' => "TX", + 'postal_code' => 78712, + 'country' => "us", + ), + ); + + $settings = $this->_location_defaults(); + variable_set('location_settings_user', $settings['location_settings']); + + // Create a feed. + $this->createFeedConfiguration('User location import', 'user_location_import'); + + // Set and configure plugins. + $this->setPlugin('user_location_import', 'FeedsFileFetcher'); + $this->setPlugin('user_location_import', 'FeedsCSVParser'); + $this->setPlugin('user_location_import', 'FeedsUserProcessor'); + + // Go to mapping page and create a couple of mappings. + $mappings = array( + '0' => array( + 'source' => 'name', + 'target' => 'name', + 'unique' => 0, + ), + '1' => array( + 'source' => 'mail', + 'target' => 'mail', + 'unique' => 1, + ), + '2' => array( + 'source' => 'street', + 'target' => 'locations:street', + 'unique' => FALSE, + ), + '3' => array( + 'source' => 'city', + 'target' => 'locations:city', + 'unique' => FALSE, + ), + '4' => array( + 'source' => 'state', + 'target' => 'locations:province', + 'unique' => FALSE, + ), + '5' => array( + 'source' => 'country', + 'target' => 'locations:country', + 'unique' => FALSE, + ), + '6' => array( + 'source' => 'zip', + 'target' => 'locations:postal_code', + 'unique' => FALSE, + ), + ); + $this->addMappings('user_location_import', $mappings); + + // Change some of the basic configuration. + $edit = array( + 'content_type' => '', + 'import_period' => FEEDS_SCHEDULE_NEVER, + ); + $this->drupalPost('admin/build/feeds/edit/user_location_import/settings', $edit, 'Save'); + + // Import CSV file. + $this->importFile('user_location_import', $this->absolutePath() .'/tests/feeds/location_users.csv'); + + // Assert result. + $this->assertText('Created 2 users.'); + $this->drupalGet('admin/user/user'); + $this->assertText('blue'); + $this->assertText('drop'); + + $blue = user_load(array('name' => 'blue')); + $this->assertEqual( + $loc[0]['street'], + $blue->locations[0]['street'], + t('Testing street import, expected: @e, found: @f', + array('@e' => $loc[0]['street'], '@f' => $blue->locations[0]['street'])) + ); + $this->assertEqual( + $loc[0]['city'], + $blue->locations[0]['city'], + t('Testing city import, expected: @e, found: @f', array('@e' => $loc[0]['city'], '@f' => $blue->locations[0]['city'])) + ); + $this->assertEqual( + $loc[0]['province'], + $blue->locations[0]['province'], + t('Testing province import, expected: @e, found: @f', array('@e' => $loc[0]['province'], '@f' => $blue->locations[0]['province'])) + ); + $this->assertEqual( + $loc[0]['country'], + $blue->locations[0]['country'], + t('Testing country import, expected: @e, found: @f', array('@e' => $loc[0]['country'], '@f' => $blue->locations[0]['country'])) + ); + $this->assertEqual( + $loc[0]['postal_code'], + $blue->locations[0]['postal_code'], + t('Testing country import, expected: @e, found: @f', array('@e' => $loc[0]['postal_code'], '@f' => $blue->locations[0]['postal_code'])) + ); + + $drop = user_load(array('name' => 'drop')); + $this->assertEqual( + $loc[1]['street'], + $drop->locations[0]['street'], + t('Testing street import, expected: @e, found: @f', + array('@e' => $loc[1]['street'], '@f' => $drop->locations[0]['street'])) + ); + $this->assertEqual( + $loc[1]['city'], + $drop->locations[0]['city'], + t('Testing city import, expected: @e, found: @f', array('@e' => $loc[1]['city'], '@f' => $drop->locations[0]['city'])) + ); + $this->assertEqual( + $loc[1]['province'], + $drop->locations[0]['province'], + t('Testing province import, expected: @e, found: @f', array('@e' => $loc[1]['province'], '@f' => $drop->locations[0]['province'])) + ); + $this->assertEqual( + $loc[1]['country'], + $drop->locations[0]['country'], + t('Testing country import, expected: @e, found: @f', array('@e' => $loc[1]['country'], '@f' => $drop->locations[0]['country'])) + ); + $this->assertEqual( + $loc[1]['postal_code'], + $drop->locations[0]['postal_code'], + t('Testing country import, expected: @e, found: @f', array('@e' => $loc[1]['postal_code'], '@f' => $drop->locations[0]['postal_code'])) + ); + } + + private function _location_defaults() { + // Get the (settable) defaults. + $defaults = array(); + $hide = array(); + $d = location_invoke_locationapi($location, 'defaults'); + $fields = location_field_names(); + foreach ($fields as $k => $v) { + $defaults[$k] = $d[$k]; + $hide[$k] = 0; + } + + foreach ($defaults as $k => $v) { + // Change collection to allow. + $defaults[$k]['collect'] = 1; + } + + $settings = array( + 'location_settings' => array( + 'multiple' => array( + 'min' => 1, + 'max' => 1, + 'add' => 1, + ), + 'form' => array( + 'weight' => 0, + 'collapsible' => 0, + 'collapsed' => 0, + 'fields' => $defaults, + ), + 'display' => array( + 'weight' => 0, + 'hide' => $hide, + 'teaser' => 1, + 'full' => 1, + ), + 'rss' => array( + 'mode' => 'simple', + ), + ), + ); + + return $settings; + } +} + Index: tests/feeds/location_nodes.csv =================================================================== RCS file: tests/feeds/location_nodes.csv diff -N tests/feeds/location_nodes.csv --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/feeds/location_nodes.csv 5 Jun 2010 00:48:51 -0000 @@ -0,0 +1,3 @@ +Title,Body,published,GUID,street,city,state,country,zip +"Ut wisi enim ad minim veniam", "Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.",205200720,2,"93 Rue de Rivoli","Paris",,"fr",75001 +"Duis autem vel eum iriure dolor", "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.",428112720,3,"1600 Amphitheatre Parkway","Mountain View","California","us",94043 Index: tests/feeds/location_users.csv =================================================================== RCS file: tests/feeds/location_users.csv diff -N tests/feeds/location_users.csv --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/feeds/location_users.csv 5 Jun 2010 00:48:51 -0000 @@ -0,0 +1,3 @@ +name,mail,street,city,state,zip,country +"blue","blue@drupal.org","405 Hilgard Ave.","Los Angeles","California",90095,"us" +"drop","drop@drupal.org","2400 Inner Campus Drive","Austin","Texas",78712,"us"