diff --git rpx_core.module rpx_core.module
index ddb750e..45869bf 100644
--- rpx_core.module
+++ rpx_core.module
@@ -575,10 +575,21 @@ function _rpx_report_missing_field($entity_type, $field_name, $user_name) {
  * Imports Engage user profile data into profile, profile2 and user entity
  * fields, based on the settings for each mapping.
  */
-function _rpx_import_user_data() {
-  global $user;
-  $map = variable_get('rpx_profile_fields_map', array());
-  $provider = $_SESSION['rpx_last_provider_info']['name'];
+function _rpx_import_user_data($type = 'profile', $provider = NULL, $account = NULL, $data = NULL) {
+  $map = variable_get('rpx_' . $type . '_fields_map', array());
+  
+  if (!isset($provider)) {
+    $provider = $_SESSION['rpx_last_provider_info']['name'];
+  }
+  
+  if (!isset($account)) {
+    global $user;
+    $account = $user;
+  }
+  
+  if (!isset($data)) {
+    $data = $_SESSION['rpx'];
+  }
 
   foreach ($map as $mid => $mapping) {
     // Should we try to update the field at all?
@@ -586,7 +597,7 @@ function _rpx_import_user_data() {
       continue;
     }
 
-    $new_data = _rpx_data_map($_SESSION['rpx'], $mapping['fid']);
+    $new_data = _rpx_data_map($data, $mapping['fid']);
 
     // Only update if provider returned data for the field.
     if($new_data === '') {
@@ -613,7 +624,7 @@ function _rpx_import_user_data() {
       // previous one.
       $prev_provider = db_select('rpx_mapping_provider')
         ->fields('rpx_mapping_provider', array('name'))
-        ->condition('uid', $user->uid)
+        ->condition('uid', $account->uid)
         ->condition('mid', $mid)
         ->execute()
         ->fetchAssoc();
@@ -634,7 +645,7 @@ function _rpx_import_user_data() {
 
       // Check that field still exists.
       if (!$profile) {
-        _rpx_report_missing_field('profile', $mapping['field'], $user->name);
+        _rpx_report_missing_field('profile', $mapping['field'], $account->name);
         continue;
       }
 
@@ -643,7 +654,7 @@ function _rpx_import_user_data() {
         $field = db_select('profile_value')
           ->fields('profile_value', array('value'))
           ->condition('fid', $profile['fid'])
-          ->condition('uid', $user->uid)
+          ->condition('uid', $account->uid)
           ->execute()
           ->fetchAssoc();
         if ($field && $field['value'] !== '') {
@@ -654,7 +665,7 @@ function _rpx_import_user_data() {
       db_merge('profile_value')
         ->key(array(
           'fid' => $profile['fid'],
-          'uid' => $user->uid,
+          'uid' => $account->uid,
           ))
         ->fields(array('value' => $new_data))
         ->execute();
@@ -664,21 +675,29 @@ function _rpx_import_user_data() {
       // types.
       if(module_exists('profile2') && $mapping['set'] == 'profile2') {
         $entity_type = 'profile2';
-        $entity = profile2_load_by_user($user->uid, $mapping['bundle']);
+        $entity = profile2_load_by_user($account->uid, $mapping['bundle']);
       }
       else if ($mapping['set'] == 'user') {
         $entity_type = 'user';
-        $account = user_load($user->uid);
         $entity = new stdClass();
-        $entity->uid = $user->uid;
+        $entity->uid = $account->uid;
         if(isset($account->{$mapping['field']})) {
           $entity->{$mapping['field']} = $account->{$mapping['field']};
         }
       }
+      else {
+      	// @todo: Also use this hook for profile2 and user entities ?
+      	$entities = module_invoke_all('rpx_import_user_data', $type, $provider, $account, $data, $mapping);
+      	$entity = reset($entities);
+      	if (!isset($entity)) {
+	      	continue;
+	      }
+	      $entity_type = $entity->entityType();
+      }
 
       // Check that field still exists.
       if (!isset($entity->{$mapping['field']})) {
-        _rpx_report_missing_field($entity_type, $mapping['field'], $user->name);
+        _rpx_report_missing_field($entity_type, $mapping['field'], $account->name);
         continue;
       }
 
@@ -734,7 +753,7 @@ function _rpx_import_user_data() {
       // Record the provider's name as the last provider used in the mapping.
       db_merge('rpx_mapping_provider')
         ->key(array(
-          'uid' => $user->uid,
+          'uid' => $account->uid,
           'mid' => $mid,
           ))
         ->fields(array('name' => $provider))
diff --git rpx_friends/rpx_friend/rpx_friend.info rpx_friends/rpx_friend/rpx_friend.info
new file mode 100644
index 0000000..e05d865
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.info
@@ -0,0 +1,8 @@
+; $Id$
+name = Janrain Engage Friend entity
+description = Janrain Engage Friend entity.
+files[] = rpx_friend.module
+files[] = rpx_friend.info.inc
+files[] = rpx_friend.views.inc
+package = Janrain Engage
+core = 7.x
diff --git rpx_friends/rpx_friend/rpx_friend.info.inc rpx_friends/rpx_friend/rpx_friend.info.inc
new file mode 100644
index 0000000..f26e58a
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.info.inc
@@ -0,0 +1,69 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Provides Entity metadata integration.
+ */
+
+/**
+ * Extend the defaults.
+ */
+class RPXFriendMetadataController extends EntityDefaultMetadataController {
+
+  public function entityPropertyInfo() {
+    $info = parent::entityPropertyInfo();
+    $properties = &$info[$this->type]['properties'];
+
+    $properties['id'] += array(
+      'validation callback' => 'entity_property_validate_integer_positive',
+    );
+
+    $properties['user'] = array(
+      'label' => t("User"),
+      'type' => 'user',
+      'description' => t("The owner of the friend."),
+      'getter callback' => 'entity_property_getter_method',
+      'setter callback' => 'entity_property_setter_method',
+      'setter permission' => 'administer janrain friends',
+      'required' => TRUE,
+      'clear' => array('uid'),
+    );
+
+    $properties['friend'] = array(
+      'label' => t("Friend"),
+      'type' => 'user',
+      'description' => t("The friend."),
+      'getter callback' => 'entity_property_getter_method',
+      'setter callback' => 'entity_property_setter_method',
+      'setter permission' => 'administer janrain friends',
+      'required' => TRUE,
+      'clear' => array('friend_uid'),
+    );
+
+    $properties['rpxid'] = array(
+      'label' => t('User Engage ID'),
+      'type' => 'text', // Should be uri, but let's keep compatible with current rpx module.
+      'description' => t('The Janrain Engage (3rd party) ID from which the friend list is received.'),
+      'getter callback' => 'entity_property_verbatim_get',
+      'setter callback' => 'entity_property_verbatim_set',
+      'setter permission' => 'administer janrain friends',
+      'required' => TRUE,
+    ) + $properties['rpxid'];
+    
+    $properties['friend_rpxid'] = array(
+      'label' => t('Friend Engage ID'),
+      'type' => 'text', // Should be uri, but let's keep compatible with current rpx module.
+      'description' => t('The Janrain Engage (3rd party) ID of the friend.'),
+      'getter callback' => 'entity_property_verbatim_get',
+      'setter callback' => 'entity_property_verbatim_set',
+      'setter permission' => 'administer janrain friends',
+      'required' => TRUE,
+    ) + $properties['rpxid'];
+
+    unset($properties['uid']);
+    unset($properties['friend_uid']);
+
+    return $info;
+  }
+}
diff --git rpx_friends/rpx_friend/rpx_friend.install rpx_friends/rpx_friend/rpx_friend.install
new file mode 100644
index 0000000..842a70f
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.install
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Install file for Janrain Engage friend entity.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function rpx_friend_schema() {
+  $schema['rpx_friend'] = array(
+    'description' => 'A Janrain Engage friend.',
+    'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'The internal identifier.',
+      ),
+      'uid' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => "The {users}.uid of the associated user.",
+      ),
+      'friend_uid' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'default' => NULL,
+        'description' => "The {users}.uid of the associated friend, if exists.",
+      ),
+      'rpxid' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The user\'s Janrain ID.',
+      ),
+      'friend_rpxid' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The friend\'s Janrain ID.',
+      ),
+    ),
+    'primary key' => array('id'),
+    'indexes' => array(
+      'uid' => array('uid'),
+      'friend_uid' => array('friend_uid'),
+    ),
+    'foreign keys' => array(
+      'uid' => array('users' => 'uid'),
+      'friend_uid' => array('users' => 'uid'),
+      'rpxid' => array('authmap' => 'authname'),
+      'friend_rpxid' => array('authmap' => 'authname'),
+    ),
+    'unique keys' => array(
+      'id' => array('id'), 
+      'uid_rpxid_friend_rpxid' => array('uid', 'rpxid', 'friend_rpxid'),
+    ), 
+  );
+  return $schema;
+}
diff --git rpx_friends/rpx_friend/rpx_friend.module rpx_friends/rpx_friend/rpx_friend.module
new file mode 100644
index 0000000..dadd29f
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.module
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * @file
+ * A Janrain Engage friend entity.
+ */
+
+/**
+ * Implements hook_views_api().
+ */
+function rpx_friend_views_api() {
+  return array(
+    'api' => '2',
+    'path' => drupal_get_path('module', 'rpx_friend'),
+  );
+}
+
+/**
+ * Implements hook_permission().
+ */
+function rpx_friend_permission() {
+  return array(
+    'administer janrain friend entity' =>  array(
+      'title' => t('Administer Janrain Engage friend entity'),
+      'description' => t('Edit and view all Janrain Engage friends entities.'),
+    ),
+    "edit own janrain friend entity" => array(
+      'title' => t('Edit own Janrain Engage friend entity'),
+    ),
+    "edit any janrain friend entity" => array(
+      'title' => t('Edit any Janrain Engage friend entity'),
+    ),
+    "view own janrain friend entity" => array(
+      'title' => t('View own Janrain Engage friend entity'),
+    ),
+    "view any janrain friend entity" => array(
+      'title' => t('View any Janrain Engage friend entity'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function rpx_friend_menu() {
+  $items['admin/structure/rpx_friend'] = array(
+    'title' => 'Janrain friend',
+    'access arguments' => array('administer janrain friend entity'),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rpx_friend_form', 'rpx_friend'),
+  );
+  return $items;
+}
+
+/**
+ * Entity administration form.
+ */
+function rpx_friend_form($form, &$form_state, $bundle = array(), $op = 'edit') {
+  // Since there are no bundles, redirect to fields form.
+  drupal_goto('admin/structure/rpx_friend/edit/fields');
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function rpx_friend_entity_info() {
+  return array(
+    'rpx_friend' => array(
+      'label' => t('Janrain friend'),
+      'entity class' => 'RPXFriend',
+      'controller class' => 'EntityAPIController',
+      'module' => 'rpx_friend',
+      'fieldable' => TRUE,
+      'base table' => 'rpx_friend',
+      'view modes' => array(
+        'friend' => array(
+          'label' => t('Janrain friend'),
+          'custom settings' => FALSE,
+        ),
+      ),
+      'entity keys' => array(
+        'id' => 'id',
+      ),
+      'bundles' => array(
+        'rpx_friend' => array(
+          'label' => t('Janrain friend'),
+          'admin' => array(
+            'path' => 'admin/structure/rpx_friend/edit',
+            'access arguments' => array('administer janrain friend entity'),
+          ),
+        ),
+      ),
+      'label callback' => 'entity_class_label',
+      'uri callback' => 'entity_class_uri',
+      'metadata controller class' => 'RPXFriendMetadataController',
+      'access callback' => 'rpx_friend_access',
+    ),
+  );
+}
+
+/**
+ * Implements hook_user_delete().
+ */
+function rpx_friend_user_delete($account) {
+  foreach (rpx_friend_load_by_user($account) as $friend) {
+    rpx_friend_delete($friend);
+    // @todo: reverse friend deletion.
+  }
+}
+
+/**
+ * Implements hook_user_cancel().
+ */
+function rpx_friend_user_cancel($edit, $account, $method) {
+  switch ($method) {
+    case 'user_cancel_reassign':
+      foreach (rpx_friend_load_by_user($account) as $friend) {
+        rpx_friend_delete($friend);
+        // @todo: reverse friend deletion.
+      }
+      break;
+  }
+}
+
+/**
+ * Implements hook_rpx_import_user_data().
+ */
+function rpx_friend_rpx_import_user_data($type, $provider, $account, $data, $mapping) {
+  if ($type == 'friends' && $mapping['set'] == 'rpx_friend') {
+    $params = array(
+      'uid' => $account->uid,
+      'rpxid' => $data['rpxid'],
+      'friend_rpxid' => $data['friend_rpxid'],
+    );
+
+    $existing = rpx_friend_load_multiple(FALSE, $params);
+    if (!empty($existing)) {
+      return reset($existing);
+    }
+    
+    $entity = rpx_friend_create($params + array(
+      'bundle' => $mapping['bundle'],
+      'user' => $account,
+      'friend' => $data['friend'],
+    ));
+    $entity->{$mapping['field']} = array();
+    $entity->save();
+    return $entity;
+  }
+}
+
+/**
+ * Implements hook_rpx_linked_account().
+ */
+function rpx_friend_rpx_linked_account($op, $info) {
+  $settings = _rpx_friends_settings();
+  
+  if ($op == 'deleted' && $settings['delete']) {
+    $friends = rpx_friend_load_multiple(FALSE, array('uid' => $info['user']->uid, 'rpxid' => $info['id']));
+    rpx_friend_delete_multiple(array_keys($friends));
+  }
+}
+
+/**
+ * Implements hook_rpx_friends_retrieve().
+ */
+function rpx_friend_rpx_friends_retrieve($rpxid, $account, $results) {
+  $settings = _rpx_friends_settings();
+  
+  if ($settings['delete']) {
+    $friends = rpx_friend_load_multiple(FALSE, array('uid' => $account->uid, 'rpxid' => $rpxid));
+  
+    foreach ($results['response']['entry'] as $entry) {
+      $found = FALSE;
+      foreach ($friends as $key => $friend) {
+        if ($entry['id'] == $friend->friend_rpxid) {
+          unset($friends[$key]);
+          break;
+        }
+      }
+    }
+
+    rpx_friend_delete_multiple(array_keys($friends));
+  }
+}
+
+/**
+ * Fetch a friend object.
+ *
+ * @param $id
+ *   Integer specifying the friend id.
+ * @param $reset
+ *   A boolean indicating that the internal cache should be reset.
+ * @return
+ *   A fully-loaded $friend object or FALSE if it cannot be loaded.
+ *
+ * @see rpx_friend_load_multiple()
+ */
+function rpx_friend_load($id, $reset = FALSE) {
+  $friend = rpx_friend_load_multiple(array($id), array(), $reset);
+  return reset($friend);
+}
+
+/**
+ * Load multiple friends based on certain conditions.
+ *
+ * @param $ids
+ *   An array of friend IDs.
+ * @param $conditions
+ *   An array of conditions to match against the {rpx_friend} table.
+ * @param $reset
+ *   A boolean indicating that the internal cache should be reset.
+ * @return
+ *   An array of friend objects, indexed by id.
+ *
+ * @see entity_load()
+ * @see rpx_friend_load()
+ * @see rpx_friend_load_by_user()
+ */
+function rpx_friend_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) {
+  return entity_load('rpx_friend', $ids, $conditions, $reset);
+}
+
+/**
+ * Fetch friends by account.
+ *
+ * @param $account
+ *   The user account to load friends for, or its uid.
+ * @return
+ *   An array of friends owned by the user.
+ *
+ * @see rpx_friend_load_multiple()
+ */
+function rpx_friend_load_by_user($account) {
+  $cache = &drupal_static(__FUNCTION__, array());
+  $uid = is_object($account) ? $account->uid : $account;
+
+  if (!isset($cache[$uid])) {
+    $friends = rpx_friend_load_multiple(FALSE, array('uid' => $uid));
+    $cache[$uid] = array_keys($friends);
+    return $friends;
+  }
+  return rpx_friend_load_multiple($cache[$uid]);
+}
+
+/**
+ * Deletes a friend.
+ *
+ * @param $friend
+ *   The friend object.
+ */
+function rpx_friend_delete(RPXFriend $friend) {
+  $friend->delete();
+}
+
+/**
+ * Delete multiple friends.
+ *
+ * @param $ids
+ *   An array of friend IDs.
+ */
+function rpx_friend_delete_multiple(array $ids) {
+  entity_get_controller('rpx_friend')->delete($ids);
+}
+
+/**
+ * Create a new friend object.
+ *
+ * @param $values
+ *   An array of values to set for the created friend object.
+ */
+function rpx_friend_create(array $values) {
+  return new RPXFriend($values);
+}
+
+/**
+ * Saves a friend to the database.
+ *
+ * @param $friend
+ *   The friend object.
+ */
+function rpx_friend_save(RPXFriend $friend) {
+  return $friend->save();
+}
+
+/**
+ * The class used for friend entities.
+ */
+class RPXFriend extends Entity {
+
+  /**
+   * Constructor.
+   */
+  public function __construct($values = array()) {
+    if (isset($values['user'])) {
+      $this->setUser($values['user']);
+      unset($values['user']);
+    }
+    
+    if (isset($values['friend'])) {
+      $this->setFriend($values['friend']);
+      unset($values['friend']);
+    }
+    
+    parent::__construct($values, 'rpx_friend');
+  }
+
+  /**
+   * Returns the user owning this friend.
+   */
+  public function user() {
+    return user_load($this->uid);
+  }
+
+  /**
+   * Sets a new user owning this friend.
+   */
+  public function setUser($account) {
+    if (is_object($account)){
+      $this->uid = $account->uid;
+    }
+    else {
+      $this->uid = (int) $account;
+    }
+  }
+
+  /**
+   * Returns the friend.
+   */
+  public function friend() {
+    return user_load($this->friend_uid);
+  }
+
+  /**
+   * Sets a new friend.
+   */
+  public function setFriend($account) {
+    if (is_object($account)){
+      $this->friend_uid = $account->uid;
+    }
+    else {
+      $this->friend_uid = (int) $account;
+    }
+  }
+}
diff --git rpx_friends/rpx_friend/rpx_friend.rules.inc rpx_friends/rpx_friend/rpx_friend.rules.inc
new file mode 100644
index 0000000..2e900ff
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.rules.inc
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Hooks and callback functions for rules.module integration.
+ */
+
+/**
+ * Implements hook_rules_condition_info().
+ */
+function rpx_friend_rules_condition_info() {
+  return array(
+    'rpx_friend_rules_condition_callback_friend_exists' => array(
+      'label' => t('Friend entity exists'),
+      'parameter' => array(
+        'rpx_friend_info' => array('type' => 'rpx_friend_info', 'label' => t('Janrain Engage friend info')),
+      ),
+      'group' => t('Janrain Engage'),
+    ),
+  );
+}
+
+/**
+ * Callback for rpx_friend_rules_condition_info().
+ */
+function rpx_friend_rules_condition_callback_friend_exists($info) {
+  $query = db_select('rpx_friend');
+  $query->condition('uid', $info['rpx']->user->uid);
+  $query->condition('rpxid', $info['rpx']->id);
+  $query->condition('profile_url', $info['profile_url']);
+  $exists = $query->range(0, 1)->countQuery()->execute()->fetchField();
+  return ($exists == 1);
+}
diff --git rpx_friends/rpx_friend/rpx_friend.views.inc rpx_friends/rpx_friend/rpx_friend.views.inc
new file mode 100644
index 0000000..2465da3
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.views.inc
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Provides views data.
+ */
+
+/**
+ * Implements hook_views_data_alter().
+ */
+function rpx_friend_views_data_alter(&$data) {
+  $data['rpx_friend']['authmap'] = array(
+    'relationship' => array(
+      'label' => t('Authmap'),
+      'base' => 'authmap',
+      'base field' => 'authname',
+      'relationship field' => 'profile_url',
+      'handler' => 'views_handler_relationship',
+    ),
+    'title' => t('Authmap of the friend'),
+    'help' => t('Relate authmap to user account.'),
+  );
+  $data['rpx_friend']['friend'] = array(
+    'relationship' => array(
+      'label' => t('Friend'),
+      'base' => 'user',
+      'base field' => 'uid',
+      'relationship field' => 'friend_uid',
+      'handler' => 'views_handler_relationship',
+    ),
+    'title' => t('Friend account'),
+    'help' => t('Relate to friend account.'),
+  );
+}
diff --git rpx_friends/rpx_friends.admin.inc rpx_friends/rpx_friends.admin.inc
new file mode 100644
index 0000000..c39b6e0
--- /dev/null
+++ rpx_friends/rpx_friends.admin.inc
@@ -0,0 +1,339 @@
+<?php
+
+/**
+ * @file
+ * Admin page callback file for the rpx_friends module.
+ */
+
+/**
+ * Menu callback: Generate a form to configure friends fields.
+ *
+ * @ingroup forms
+ * @see rpx_mapping_settings()
+ */
+function rpx_friends_mapping_settings_form($form, &$form_state) {
+  $settings = _rpx_friends_mapping();
+  $map = variable_get('rpx_friends_fields_map', array());
+  $rpx_fields = db_query("SELECT fid, path FROM {rpx_profile_field}")->fetchAllAssoc('fid', PDO::FETCH_ASSOC);
+  
+  $form['settings'] = array(
+    '#type' => 'vertical_tabs',
+  );
+  
+  // Existing users.
+  if (module_exists('user_reference') || module_exists('user_relationships') || module_exists('flag')) {
+    $form['#tree'] = TRUE;
+    $form['existing'] = array(
+      '#tree' => TRUE,
+      '#type' => 'fieldset',
+      '#title' => t('Existing users'),
+      '#group' => 'settings',
+    );
+    $form['existing']['module'] = array(
+      '#type' => 'select',
+      '#options' => array(),
+      '#empty_option' => t('- Select a module -'),
+      '#default_value' => $settings['existing']['module'],
+      '#title' => t('Module to use when a friend correponds to an existing user'),
+      '#description' => t('Choose one of the supported modules to relate friends to the user. Currently, <em>User Reference</em>, <em>User Relationships</em> and <em>Flag</em> are supported.'),
+      '#weight' => 10,
+    );
+    
+    // Populate fields catalog if user_reference module exists.
+    if (module_exists('user_reference')) {
+      $form['existing']['module']['#options']['user_reference'] = t('User Reference');
+      
+      // Only profile2 and user modules are supported here since user_reference is
+      // only available on real entities.
+      $catalog = _rpx_drupal_field_catalog(array('profile2', 'user'));
+      foreach ($catalog as $entity_name => $entity) {
+        foreach ($entity['bundles'] as $bundle_name => $bundle) {
+          foreach ($bundle['fields'] as $field_name => $field_label) {
+            $field_info = field_info_field($field_name);
+            if ($field_info['module'] != 'user_reference') {
+              unset($catalog[$entity_name]['bundles'][$bundle_name]['fields'][$field_name]);
+            }
+          }
+        }
+      }
+      
+      // We fake the mid mechanism used by rpx_ui.js.
+      $form['existing']['user_reference'] = array(
+        '#type' => 'fieldset',
+        '#tree' => TRUE,
+        '#title' => t('Field'),
+        '#weight' => 11,
+        '#theme' => 'rpx_friends_mapping_existing_user_reference_form',
+        '#states' => array(
+          'visible' => array('select[name="existing[module]"]' => array('value' => 'user_reference')),
+        ),
+        'field_set' => array(
+          '#type' => 'select',
+          '#title' => t('Fieldset'),
+          '#title_display' => 'invisible',
+          '#options' => _rpx_drupal_field_options($catalog, 'set'),
+          '#default_value' => $settings['existing']['user_reference']['field_set'],
+          '#empty_option' => t('- Select a fieldset -'),
+          '#description' => t('Module or entity.'),
+          '#attributes' => array('class' => array('field-set-select', 'mid-user_reference')),
+        ),
+        'field_bundle' => array(
+          '#type' => 'select',
+          '#title' => t('Fieldset type'),
+          '#title_display' => 'invisible',
+          '#options' => _rpx_drupal_field_options($catalog, 'bundle'),
+          '#default_value' => $settings['existing']['user_reference']['field_bundle'],
+          '#empty_option' => t('- Select a type -'),
+          '#description' => t('Fieldset type.'),
+          '#attributes' => array('class' => array('field-bundle-select', 'mid-user_reference')),
+        ),
+        'field' => array(
+          '#type' => 'select',
+          '#title' => t('Field'),
+          '#title_display' => 'invisible',
+          '#options' => $catalog,
+          '#default_value' => $settings['existing']['user_reference']['field'],
+          '#empty_option' => t('- Select a field -'),
+          '#description' => t('Field.'),
+          '#attributes' => array('class' => array('field-select', 'mid-user_reference')),
+        ),
+        '#attached' => array(
+          'js' => array(
+            drupal_get_path('module', 'rpx_ui') . '/rpx_ui.js',
+            array(
+              'type' => 'setting',
+              'data' => array('catalog' => $catalog, 'map' => array('user_reference' => array()), 'rpx_fields' => array()),
+            ),
+          ),
+        ),
+      );
+    }
+    
+    // Populate relationships if user_relationships module exists.
+    if (module_exists('user_relationships')) {
+      $form['existing']['module']['#options']['user_relationships'] = t('User Relationships');
+      
+      $options = array();
+      $relationship_types = user_relationships_types_load();
+  
+      foreach ($relationship_types as $type) {
+        $options[$type->rtid] = $type->name;
+      }
+  
+      $form['existing']['user_relationships'] = array(
+        '#type' => 'fieldset',
+        '#tree' => TRUE,
+        '#title' => t('User Relationships'),
+        '#weight' => 100,
+        '#states' => array(
+          'visible' => array('select[name="existing[module]"]' => array('value' => 'user_relationships')),
+        ),
+      );
+      $form['existing']['user_relationships']['type'] = array(
+        '#type' => 'radios',
+        '#title' => t('Relationship type'),
+        '#options' => $options,
+        '#default_value' => $settings['existing']['user_relationships']['type'],
+        '#empty_option' => t('- Select a type -'),
+        '#description' => t('Choose a relation type.'),
+      );
+      $form['existing']['user_relationships']['approve'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Automatically approve relationship'),
+        '#default_value' => $settings['existing']['user_relationships']['approve'],
+        '#description' => t('Whether the relation should be automatically approved or not.'),
+      );
+    }
+    
+    // Populate flags if flag module exists.
+    if (module_exists('flag')) {
+      $form['existing']['module']['#options']['flag'] = t('Flag');
+      
+      $options = array();
+      $flags = flag_get_flags();
+  
+      foreach ($flags as $flag) {
+        if ($flag->content_type == 'user') {
+          $options[$flag->fid] = $flag->title;
+        }
+      }
+      
+      $form['existing']['flag'] = array(
+        '#type' => 'fieldset',
+        '#tree' => TRUE,
+        '#title' => t('Flag'),
+        '#weight' => 100,
+        '#states' => array(
+          'visible' => array('select[name="existing[module]"]' => array('value' => 'flag')),
+        ),
+      );
+      $form['existing']['flag']['name'] = array(
+        '#type' => 'radios',
+        '#title' => t('Flag'),
+        '#options' => $options,
+        '#empty_option' => t('- Select a flag -'),
+        '#default_value' => $settings['existing']['flag']['name'],
+        '#description' => t('Choose a user flag.'),
+      );
+      $form['existing']['flag']['skip_permission_check'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Skip permission check'),
+        '#default_value' => $settings['existing']['flag']['skip_permission_check'],
+        '#description' => t('Whether the permission to create this flag for the user should be checked or not.'),
+      );
+    }
+  }
+  
+  // Non existing users.
+  if (module_exists('rpx_friend')) {
+    $form['#tree'] = TRUE;
+    $form['non_existing'] = array(
+      '#tree' => TRUE,
+      '#type' => 'fieldset',
+      '#title' => t('Friend entity'),
+      '#group' => 'settings',
+    );
+    $form['non_existing']['create'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Create an entity for non-existing users'),
+      '#default_value' => $settings['non_existing']['create'],
+      '#weight' => -10,
+      '#description' => t('If checked, a <em>Friend</em> entity will be created.'),
+    );
+    $form['non_existing']['mapping'] = array(
+      '#type' => 'fieldset',
+      '#tree' => TRUE,
+      '#title' => t('Fields'),
+      '#weight' => 0,
+      '#theme' => 'rpx_friends_mapping_non_existing_form',
+      '#states' => array(
+        'visible' => array('input[name="non_existing[create]"]' => array('checked' => TRUE)),
+      ),
+    );
+    
+    foreach ($map as $mid => $mapping) {
+      $form['non_existing']['mapping'][$mid] = array(
+        '#tree' => TRUE,
+        'fid' => array(
+          '#type' => 'select',
+          '#title' => t('Engage field'),
+          '#title_display' => 'invisible',
+          '#options' => _rpx_engage_field_options(),
+          '#empty_option' => t('- Select a data field -'),
+          '#description' => t('Data path.'),
+          '#default_value' => isset($mapping['fid']) ? $mapping['fid'] : NULL,
+        ),
+        'separator' => array(
+          '#markup' => '=>'
+        ),
+        'field' => array(
+          '#type' => 'select',
+          '#title' => t('Field'),
+          '#title_display' => 'invisible',
+          '#options' => array(),
+          '#empty_option' => t('- Select a field -'),
+          '#description' => t('Field.'),
+          '#default_value' => isset($mapping['field']) ? $mapping['field'] : NULL,
+        ),
+        'edit' => array(
+          '#type' => 'link',
+          '#title' => t('edit'),
+          '#href' => "admin/config/people/rpx/mapping/friends/edit/$mid",
+        ),
+        'set' => array(
+          '#type' => 'value',
+          '#value' => 'rpx_friend',
+        ),
+        'bundle' => array(
+          '#type' => 'value',
+          '#value' => 'rpx_friend',
+        ),
+      );
+      foreach (field_info_instances('rpx_friend', 'rpx_friend') as $field_name => $field) {
+        $form['non_existing']['mapping'][$mid]['field']['#options'][$field_name] = $field['label'];
+      }
+    }
+  }
+  
+  if (isset($form)) {
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+    );
+  }
+  else {
+    drupal_set_message(t('There are no module enabled for friends creation. You need at least <em>Janrain Engage Friend entity</em> module for non-existing users or <em>User Reference</em>, <em>User Relationships</em> or <em>Flag</em> modules for existing friends support.'), 'warning');
+  }
+  
+  return $form;
+}
+
+/**
+ * Submit handler for friends mapping.
+ *
+ * @see rpx_friends_mapping_settings_form()
+ */
+function rpx_friends_mapping_settings_form_submit($form, &$form_state) {
+  $values = $form_state['values'];
+  if (isset($values['non_existing']['mapping'])) {
+    $mapping = $values['non_existing']['mapping'];
+    variable_set('rpx_friends_fields_map', $mapping);
+    unset($values['non_existing']['mapping']);
+  }
+  variable_set('rpx_friends_mapping', $values);
+}
+
+/**
+ * Theme User reference field picker.
+ *
+ * @ingroup themeable
+ */
+function theme_rpx_friends_mapping_existing_user_reference_form($variables) {
+  $form = $variables['form'];
+
+  $rows = array();
+  $row = array();
+  foreach (element_children($form) as $key) {
+    $row[] = drupal_render($form[$key]);
+  }
+  $rows[] = array('data' => $row);
+  $header = array(t('Entity'), t('Bundle'), t('Field'));
+
+  $output = drupal_render_children($form);
+  $output .= theme('table', array('header' => $header, 'rows' => $rows));
+
+  return $output;
+}
+
+/**
+ * Theme Engage friend field mapping form.
+ *
+ * @ingroup themeable
+ */
+function theme_rpx_friends_mapping_non_existing_form($variables) {
+  $form = $variables['form'];
+
+  $rows = array();
+  foreach (element_children($form) as $key) {
+      $row = array();
+      foreach (element_children($form[$key]) as $subkey) {
+        if ($subkey == 'set' || $subkey == 'bundle') {
+          continue;
+        }
+        $row[] = drupal_render($form[$key][$subkey]);
+      }
+      $rows[] = array('data' => $row);
+  }
+  
+  $header = array();
+  $header[] = array('data' => t('Engage Data Field'));
+  $header[] = array('data' => '');
+  $header[] = array('data' => t('Friend field'));
+  $header[] = array('data' => t('Operations'));
+  
+  $output = drupal_render_children($form);
+  $output .= theme('table', array('header' => $header, 'rows' => $rows));
+
+  return $output;
+}
diff --git rpx_friends/rpx_friends.info rpx_friends/rpx_friends.info
new file mode 100644
index 0000000..2a756c7
--- /dev/null
+++ rpx_friends/rpx_friends.info
@@ -0,0 +1,7 @@
+; $Id$
+name = Janrain Engage Friends
+description = Janrain Engage Friends support.
+files[] = rpx_friends.module
+dependencies[] = "rpx_core"
+package = Janrain Engage
+core = 7.x
diff --git rpx_friends/rpx_friends.js rpx_friends/rpx_friends.js
new file mode 100644
index 0000000..ee3a245
--- /dev/null
+++ rpx_friends/rpx_friends.js
@@ -0,0 +1,62 @@
+(function ($) {
+
+/**
+ * Attaches the AJAX behavior for Janrain friends.
+ */
+Drupal.behaviors.RPXFriends = {
+  attach: function (context, settings) {
+    // Only apply once per page.
+    if ($('body').hasClass('rpx-friends-ajax-processed')) {
+      return;
+    }
+    
+    $('body').addClass('rpx-friends-ajax-processed');
+    
+    if (Drupal.settings && Drupal.settings.views && Drupal.settings.views.ajaxViews) {
+      var ajax_path = Drupal.settings.views.ajax_path;
+      var rpx_friends_views = Drupal.settings.rpx_friends_ajax_views;
+
+      if (ajax_path.constructor.toString().indexOf("Array") != -1) {
+        ajax_path = ajax_path[0];
+      }
+
+      $.each(Drupal.settings.views.ajaxViews, function(i, settings) {
+        // See if this view should be refreshed.
+        var found = false;
+        for (var j in rpx_friends_views) {
+          var rp
+          if (rpx_friends_views[j].view_name == settings.view_name && rpx_friends_views[j].view_display_id == settings.view_display_id) {
+            found = true;
+            break;
+          }
+        }
+        
+        if (!found) {
+          return;
+        }
+        
+        if (settings.view_name == 'janrain_friends') {
+          var view = '.view-dom-id-' + settings.view_dom_id;
+          var element_settings = {
+            url: '/rpx_friends/ajax/nojs',
+            submit: settings,
+            event: 'load',
+            selector: view,
+            progress: { type: 'throbber' }
+          };
+  
+        $(view)
+          .filter(function() {
+            return !$(this).parents('.view').size();
+          })
+          .each(function() {
+            var ajax = new Drupal.ajax(false, this, element_settings);
+            ajax.eventResponse(ajax, {});
+          });
+        }
+      });
+    }
+  }
+};
+
+})(jQuery);
diff --git rpx_friends/rpx_friends.module rpx_friends/rpx_friends.module
new file mode 100644
index 0000000..8f5aa8c
--- /dev/null
+++ rpx_friends/rpx_friends.module
@@ -0,0 +1,474 @@
+<?php
+
+/**
+ * @file
+ * Janrain Engage friend support.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function rpx_friends_help($path, $arg) {
+  switch ($path) {
+    case 'admin/config/people/rpx/mapping/friends':
+      return '<p>' . t('Configure how friends should be created and configure mapping of friends fields to the 3rd party friends data returned by Engage.') . '</p><p>'. t('To create friends for non-existing users, you need to enable <em>Janrain Engage Friend entity</em> module.') . '<br/>'. t('To relate existing users as friends, you need at least one of the following modules: <em>User Reference</em>, <em>User Relationships</em> or <em>Flag</em>.') . '</p>';
+    return $help;
+  }
+}
+
+/**
+ * Implements hook_permission().
+ */
+function rpx_friends_permission() {
+  return array(
+    'import janrain friends' =>  array(
+      'title' => t('Import Janrain Engage friends'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function rpx_friends_menu() {
+  $items['admin/config/people/rpx/mapping/friends'] = array(
+    'title' => 'Friends',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rpx_friends_mapping_settings_form'),
+    'access arguments' => array('administer janrain engage'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10,
+    'file' => 'rpx_friends.admin.inc',
+  );
+  $items['rpx_friends/ajax'] = array(
+    'page callback' => 'rpx_friends_ajax_callback',
+    'access arguments' => array('import janrain friends'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['rpx_friends/ajax/%'] = array(
+    'page callback' => 'rpx_friends_ajax_callback',
+    'page arguments' => array(2),
+    'access arguments' => array('import janrain friends'),
+    'type' => MENU_CALLBACK,
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function rpx_friends_menu_alter(&$items) {
+  $items['admin/config/people/rpx/mapping/profile']['title'] = 'Profile';
+  $items['admin/config/people/rpx/mapping/profile']['type'] = MENU_DEFAULT_LOCAL_TASK;
+
+  $items['admin/config/people/rpx/mapping/friends/create'] = $items['admin/config/people/rpx/mapping/create'];
+  $items['admin/config/people/rpx/mapping/friends/create']['page arguments'] = array('friends');
+  
+  $items['admin/config/people/rpx/mapping/friends/edit/%'] = $items['admin/config/people/rpx/mapping/edit'];
+  $items['admin/config/people/rpx/mapping/friends/edit/%']['page callback'] = 'rpx_friends_get_form';
+  $items['admin/config/people/rpx/mapping/friends/edit/%']['page arguments'] = array('rpx_friends_mapping_edit_form', 7, 'friends');
+  
+  $items['admin/config/people/rpx/mapping/friends/default'] = $items['admin/config/people/rpx/mapping/default'];
+}
+
+/**
+ * Menu callback: Retrieve wrapped form for friends.
+ */
+function rpx_friends_get_form() {
+  module_load_include('inc', 'rpx_ui', 'rpx_ui.admin');
+  $args = func_get_args();
+  $form = call_user_func_array('drupal_get_form', $args);
+  return $form;
+}
+
+/**
+ * Menu callback: AJAX callback.
+ */
+function rpx_friends_ajax_callback($type = 'ajax') {
+  if ($type == 'ajax') {
+    global $user;
+    $rpxids = db_query('SELECT authname FROM {authmap} WHERE uid = :uid AND module = :module', array('uid' => $user->uid, 'module' => 'rpx_core'))->fetchCol();
+    foreach ($rpxids as $rpxid) {
+      _rpx_friends_retrieve($rpxid, $user);
+    }
+    module_load_include('inc', 'views', 'includes/ajax');
+    $reponse = views_ajax();
+    ajax_deliver($reponse);
+  }
+}
+
+/**
+ * Implements hook_init().
+ */
+function rpx_friends_init() {
+  if (isset($_SESSION['rpx_friends_ajax'])) {
+    unset($_SESSION['rpx_friends_ajax']);
+    global $user;
+    $settings = _rpx_friends_settings();
+
+    $options = array();
+    foreach ($settings['ajax_views'] as $ajax_view) {
+      $explode = explode(':', $ajax_view);
+      $options[] = array(
+        'view_name' => $explode[0],
+        'view_display_id' => $explode[1],
+      );
+    }
+    drupal_add_js(array('rpx_friends_ajax_views' => $options), 'setting');
+    drupal_add_library('system', 'drupal.ajax');
+    drupal_add_js(drupal_get_path('module', 'rpx_friends') . '/rpx_friends.js');
+  }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function rpx_friends_theme() {
+  return array(
+    'rpx_friends_mapping_existing_user_reference_form' => array(
+      'render element' => 'form',
+      'file' => 'rpx_friends.admin.inc',
+    ),
+    'rpx_friends_mapping_non_existing_form' => array(
+      'render element' => 'form',
+      'file' => 'rpx_friends.admin.inc',
+    ),
+  );
+}
+
+/**
+ * Implements hook_forms().
+ */
+function rpx_friends_forms() {
+  $forms = array();
+  $forms['rpx_friends_mapping_edit_form']['callback'] = 'rpx_profile_mapping_edit_form';
+  return $forms;
+}
+
+/**
+ * Implements hook_cron_queue_info().
+ */
+function rpx_friends_cron_queue_info() {
+  $queues = array();
+  $queues['rpx_friends_queue_retrieve'] = array(
+    'worker callback' => 'rpx_friends_queue_retrieve_worker',
+  );
+  $queues['rpx_friends_queue_import'] = array(
+    'worker callback' => 'rpx_friends_queue_import_worker',
+  );
+  return $queues;
+}
+
+/**
+ * Implements hook_cron().
+ */
+function rpx_friends_cron() {
+  // @todo: Retrieve friends for users needing a refresh.
+}
+
+/**
+ * Queue worker: Retrieve friends.
+ */
+function rpx_friends_queue_retrieve_worker($params) {
+  $rpxid = $params['rpxid'];
+  $account = user_load($params['uid']);
+  _rpx_friends_retrieve($rpxid, $account);
+}
+
+/**
+ * Queue worker: Import friends.
+ */
+function rpx_friends_queue_import_worker($params) {
+  $entry = $params['entry'];
+  $account = user_load($params['uid']);
+  $user_entity = _rpx_friends_get_user_entity($account);
+  _rpx_friends_import($rpxid, $account, $entry, $user_entity);
+}
+
+/**
+ * Implements hook_rpx_linked_account().
+ */
+function rpx_friends_rpx_linked_account($op, $info) {
+  if ($op == 'added') {
+    $settings = _rpx_friends_settings();
+
+    if ($settings['use_ajax']) {
+      // RPX module redirects after calling this hook, so use a session variable.
+      $_SESSION['rpx_friends_ajax'] = TRUE;
+    }
+    else {
+      _rpx_friends_retrieve($info['id'], $info['user']);
+    }
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function rpx_friends_form_rpx_admin_settings_alter(&$form, &$form_state) {
+  $settings = _rpx_friends_settings();
+  
+  $form['rpx_friends_settings'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('FRIENDS'),
+    '#group' => 'settings',
+    '#tree' => TRUE,
+  );
+  $form['rpx_friends_settings']['user_login'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Retrieve friends when user logs in'),
+    '#default_value' => $settings['user_login'],
+    '#description' => t('If checked, friends will be retrieved when user logs in.'),
+  );
+  $form['rpx_friends_settings']['linked_account'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Retrieve friends when user links an account'),
+    '#default_value' => $settings['linked_account'],
+    '#description' => t('If checked, friends will be retrieved when user links an account.'),
+  );
+  $form['rpx_friends_settings']['use_ajax'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use AJAX'),
+    '#default_value' => $settings['use_ajax'],
+    '#description' => t('If checked, AJAX will be used to retrieve friends list. <em>get_contacts()</em> call can be long, so retrieving friends on user authentification can slow the process a bit. Using AJAX prevents this from happening.'),
+  );
+  $form['rpx_friends_settings']['ajax_views'] = array(
+    '#type' => 'select',
+    '#multiple' => TRUE,
+    '#title' => t('Views to refresh'),
+    '#default_value' => $settings['ajax_views'],
+    '#options' => array(),
+    '#description' => t('Choose which views should be refreshed when AJAX request is complete.'),
+    '#states' => array(
+      'visible' => array('input[name="rpx_friends_settings[use_ajax]"]' => array('checked' => TRUE)),
+    ),
+  );
+  if (module_exists('views')) {
+    foreach (views_get_enabled_views() as $view_name => $view) {
+      $form['rpx_friends_settings']['ajax_views']['#options'][$view_name] = array();
+      foreach ($view->display as $display_name => $display) {
+        if ($display_name == 'default') {
+          continue;
+        }
+        $form['rpx_friends_settings']['ajax_views']['#options'][$view_name][$view_name . ':' . $display_name] = $display->display_title;
+      }
+    }
+  }
+  $form['rpx_friends_settings']['delete'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Delete friends on provider removal'),
+    '#default_value' => $settings['delete'], //variable_get('rpx_friends_delete', 0) ? 1 : 0,
+    '#description' => t('If checked, friends will be removed on provider removal, otherwise they will be kept in user friends.'),
+  );
+  $form['rpx_friends_settings']['cron'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Refresh friends on cron'),
+    '#default_value' => $settings['cron'],
+    '#description' => t('If checked, friends will be retrieved and imported on cron runs.'),
+  );
+  $form['rpx_friends_settings']['cron_frequency'] = array(
+    '#type' => 'textfield',
+    '#size' => 20,
+    '#title' => t('Frequency'),
+    '#default_value' => $settings['cron_frequency'],
+    '#description' => t('Choose at which frequency friends should be retrieved for a user. Use <em>strtotime()</em> syntax.'),
+    '#states' => array(
+      'visible' => array('input[name="rpx_friends_settings[cron]"]' => array('checked' => TRUE)),
+    ),
+  );
+}
+
+/**
+ * Retrieves friends.
+ */
+function _rpx_friends_retrieve($rpxid, $account) {
+  $settings = _rpx_friends_settings();
+  $mapping = _rpx_friends_mapping();
+
+  // The Engage friend web API library.
+  require_once('rpx_friends.webapi.inc');
+
+  // Retrieve contacts.
+  $results = RPXFriends::get_contacts(variable_get('rpx_apikey', ''), $rpxid);
+
+  if ($results['stat'] != 'ok' || !isset($results['response']['entry'])) {
+    drupal_set_message(t('Impossible to retrieve friends list.'), 'error');
+    watchdog('rpx_friends', 'Failed to retrieve friends list for user ID %id: get_contacts() returned error: %err', array('%id' => $account->uid, '%err' => $results['err']['msg']), WATCHDOG_ERROR);
+    drupal_goto();
+  }
+
+  // Import friends.
+  if ($settings['cron']) {
+    foreach ($entries as $entry) {
+      $queue = DrupalQueue::get('rpx_friends_queue_import');
+      $queue->createItem(array(
+        'rpxid' => $rpxid,
+        'entry' => $entry,
+        'uid' => $account->uid,
+      ));
+    }
+  }
+  else {
+    $user_entity = _rpx_friends_get_user_entity($account);
+    foreach ($results['response']['entry'] as $entry) {
+      _rpx_friends_import($rpxid, $account, $entry, $user_entity);
+    }
+    module_invoke_all('rpx_friends_retrieve', $rpxid, $account, $results);
+  }
+}
+
+/**
+ * Returns mapping.
+ */
+function _rpx_friends_import($rpxid, $account, $entry, $user_entity) {
+  $mapping = _rpx_friends_mapping();
+
+  if ($mapping['existing']['module']) {
+    // Reverse friend query.
+    $query = db_select('authmap', 'am');
+    $query->innerJoin('users', 'u', 'u.uid = am.uid');
+    $query->fields('u', array('uid'));
+    $query->condition('am.authname', $entry['id']);
+    $query->range(0, 1);
+    $friend_uid = $query->execute()->fetchField();
+
+    // A corresponding user exists.
+    if ($friend_uid) {
+      // User reference.
+      if ($mapping['existing']['module'] == 'user_reference' && module_exists('user_reference') && isset($user_entity)) {
+        $exists = FALSE;
+        foreach ($user_entity->{$mapping['existing']['user_reference']['field']}[LANGUAGE_NONE] as $value) {
+          if ($value['uid'] == $friend_uid) {
+            $exists = TRUE;
+            break;
+          }
+        }
+        // Reference does not exist.
+        if (!$exists) {
+          $user_entity->{$mapping['existing']['user_reference']['field']}[LANGUAGE_NONE][] = array(
+            'uid' => $friend_uid,
+          );
+          field_attach_update($mapping['existing']['user_reference']['field']['field_set'], $user_entity);
+        }
+      }
+      // User relationships.
+      else if ($mapping['existing']['module'] == 'user_relationships' && module_exists('user_relationships')) {
+        $rtid = $mapping['existing']['user_relationships']['type'];
+        $approve = $mapping['existing']['user_relationships']['approve'];
+        $existing = user_relationships_load(array('requester_id' => $account->uid, 'requestee_id' => $friend_uid, 'rtid' => $rtid));
+        $relationship = reset($existing);
+        // No relationship exists.
+        if (empty($existing)) {
+          user_relationships_request_relationship($account, user_load($friend_uid), $rtid, $approve);
+        }
+        // Relationship is not approved.
+        else if ($approve && !$relationship->approved) {
+          $relationship->approved = TRUE;
+          user_relationships_save_relationship($relationship);
+        }
+      }
+      // Flag.
+      else if ($mapping['existing']['module'] == 'flag' && module_exists('flag')) {
+        $flag_name = $mapping['existing']['flag']['name'];
+        $skip = $mapping['existing']['flag']['skip_permission_check'];
+        $existing = flag_get_user_flags('user', $friend_uid, $account->uid);
+        // Flag does not exist.
+        if (!isset($existing[$flag_name])) {
+          $flag = flag_get_flag(NULL, $flag_name);
+          $flag->flag('flag', $friend_uid, $account, $skip);
+        }
+      }
+    }
+  }
+
+  if ($mapping['non_existing']['create']) {    
+    $info = array(
+      'rpxid' => $rpxid,
+      'friend' => $friend_uid ? user_load($friend_uid) : NULL,
+      'friend_rpxid' => $entry['id'],
+      'merged_poco' => $entry,
+    );
+    
+    $provider = _rpx_get_identity_provider($rpxid);
+    _rpx_import_user_data('friends', $provider['name'], $account, $info);
+  }
+}
+
+/**
+ * Returns the user profile/account ready for friends import.
+ */
+function _rpx_friends_get_user_entity($account) {
+  $cache = &drupal_static(__FUNCTION__, array());
+
+  if (!isset($cache[$account->uid])) {
+    $mapping = _rpx_friends_mapping();
+    
+    if ($mapping['existing']['module'] == 'user_reference' && module_exists('user_reference')) {
+      switch ($mapping['existing']['user_reference']['field_set']) {
+        // Profile 2.
+        case 'profile2':
+          if (module_exists('profile2')) {
+            $user_entity = profile2_load_by_user($account->uid, $mapping['existing']['user_reference']['field_bundle']);
+          }
+          break;
+        
+        // User.
+        case 'user':
+          $user_entity = new stdClass();
+          $user_entity->uid = $account->uid;
+          break;
+      }
+      if (!isset($user_entity->{$mapping['existing']['user_reference']['field']}[LANGUAGE_NONE])) {
+        $user_entity->{$mapping['existing']['user_reference']['field']}[LANGUAGE_NONE] = array();
+      }
+    }
+    
+    $cache[$account->uid] = isset($user_entity) ? $user_entity : NULL;
+  }
+  return $cache[$account->uid];
+}
+
+/**
+ * Returns settings.
+ */
+function _rpx_friends_settings() {
+  return variable_get('rpx_friends_settings', array(
+    'linked_account' => TRUE,
+    'user_login' => TRUE,
+    'cron' => FALSE,
+    'cron_frequency' => '1 day',
+    'delete' => FALSE,
+    'use_ajax' => TRUE,
+    'ajax_views' => array(),
+  ));
+}
+
+/**
+ * Returns mapping.
+ */
+function _rpx_friends_mapping() {
+  $defaults = array(
+    'existing' => array(
+      'module' => NULL,
+      'user_reference' => array(
+        'field_set' => '',
+        'field_bundle' => '',
+        'field' => '',
+      ),
+      'user_relationships' => array(
+        'type' => '',
+        'approve' => '',
+      ),
+      'flag' => array(
+        'name' => '',
+        'skip_permission_check' => '',
+      ),
+    ),
+    'non_existing' => array(
+      'create' => FALSE,
+      'mapping' => array(),
+    ),
+  );
+  // This ensures all values exist even if the variable is incomplete.
+  return variable_get('rpx_friends_mapping', $defaults) + $defaults;
+}
diff --git rpx_friends/rpx_friends.webapi.inc rpx_friends/rpx_friends.webapi.inc
new file mode 100644
index 0000000..be21739
--- /dev/null
+++ rpx_friends/rpx_friends.webapi.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Engage friends web API for Drupal.
+ *
+ * @see RPX
+ * @see http://rpxnow.com/docs
+ */
+
+class RPXFriends {
+  const get_contacts = 'https://rpxnow.com/api/v2/get_contacts';
+
+  /**
+   * Fetches Engage user friends list for an authenticated user.
+   *
+   * @param string $api_key
+   *   The API key for the Engage app we are using.
+   *
+   * @param string $identifier
+   *   The identifier returned from the auth_info API call.
+   *
+   * @return
+   *   An array of friends. FALSE if unexpected error occured.
+   *
+   * @see rpx_token_handler()
+   * @see http://rpxnow.com/docs
+   */
+  static function get_contacts($api_key, $identifier) {
+    $post_data = '&apiKey=' . $api_key . '&identifier=' . $identifier . '&format=json';
+
+    $options['data'] = $post_data;
+    $options['method'] = 'POST';
+
+    $result = drupal_http_request(RPXFriends::get_contacts, $options);
+
+    if(isset($result->error)) {
+      RPXFriends::report_error($result);
+      return FALSE;
+    }
+
+    return json_decode($result->data, TRUE);
+  }
+
+  /**
+   * Helper function for the Engage web API wrappers.
+   */
+  static function report_error($result) {
+    watchdog('rpx_friends', 'Engage web API seems to be inaccessible due to "%error".', array('%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING);
+    drupal_set_message(t('Engage web API seems to be inaccessible because of error "%error".', array('%error' => $result->code . ' ' . $result->error)), 'error');
+  }
+}
diff --git rpx_ui.admin.inc rpx_ui.admin.inc
index ab95f95..3959272 100644
--- rpx_ui.admin.inc
+++ rpx_ui.admin.inc
@@ -908,11 +908,11 @@ function _rpx_profile_field_forms_submit($form, &$form_state) {
 /**
  * Menu callback: Add a new Engage to Drupal data mapping.
  */
-function rpx_profile_mapping_create() {
-  $map = variable_get('rpx_profile_fields_map', array());
+function rpx_profile_mapping_create($type = 'profile') {
+  $map = variable_get('rpx_' . $type . '_fields_map', array());
   $map[] = array();
-  variable_set('rpx_profile_fields_map', $map);
-  drupal_goto('admin/config/people/rpx/mapping');
+  variable_set('rpx_' . $type . '_fields_map', $map);
+  $type == 'profile' ? drupal_goto('admin/config/people/rpx/mapping') : drupal_goto('admin/config/people/rpx/mapping/' . $type);
 }
 
 /*
@@ -968,8 +968,8 @@ function _rpx_get_sorted_providers($providers) {
  * @see rpx_profile_mapping_edit_form_validate()
  * @see rpx_profile_mapping_edit_form_submit()
  */
-function rpx_profile_mapping_edit_form($form, &$form_state, $arg = NULL) {
-  $map = variable_get('rpx_profile_fields_map', array());
+function rpx_profile_mapping_edit_form($form, &$form_state, $arg = NULL, $type = 'profile') {
+  $map = variable_get('rpx_' . $type . '_fields_map', array());
   if (is_numeric($arg)) {
     $mid = $arg;
 
@@ -991,6 +991,8 @@ function rpx_profile_mapping_edit_form($form, &$form_state, $arg = NULL) {
     drupal_not_found();
     drupal_exit();
   }
+  
+  $form_state['#type'] = $type;
 
   $form['field_update'] = array(
    '#type' => 'fieldset',
@@ -1034,8 +1036,9 @@ linked account is added'),
  * @see rpx_profile_mapping_edit_form()
  */
 function rpx_profile_mapping_edit_form_submit($form, &$form_state) {
+  $type = $form_state['#type'];
   $values = $form_state['values'];
-  $map = variable_get('rpx_profile_fields_map', array());
+  $map = variable_get('rpx_' . $type . '_fields_map', array());
   $mapping = &$map[$values['mid']];
 
   $update = $values['field_update']['options'];
@@ -1045,7 +1048,7 @@ function rpx_profile_mapping_edit_form_submit($form, &$form_state) {
     $mapping['providers'] = _rpx_get_sorted_providers($values['field_update']['providers']);
   }
 
-  variable_set('rpx_profile_fields_map', $map);
+  variable_set('rpx_' . $type . '_fields_map', $map);
 
   // Warn user if he tries to configure the mapping to append new data to a
   // single value field.
@@ -1060,7 +1063,7 @@ function rpx_profile_mapping_edit_form_submit($form, &$form_state) {
   // print a friendlier message.
   $field = db_query("SELECT title, path FROM {rpx_profile_field} WHERE fid = :fid", array(':fid' => $mapping['fid']))->fetchObject();
   drupal_set_message(t('Mapping options for field %title (%path) have been updated.', array('%title' => $field->title, '%path' => $field->path)));
-  $form_state['redirect'] = 'admin/config/people/rpx/mapping';
+  $form_state['redirect'] = $type == 'profile' ? 'admin/config/people/rpx/mapping' : 'admin/config/people/rpx/mapping/' . $type;
 }
 
 /**
@@ -1071,8 +1074,6 @@ function rpx_profile_mapping_edit_form_submit($form, &$form_state) {
  * @see rpx_profile_mapping_default_submit()
  */
 function rpx_profile_mapping_default($form, &$form_state) {
-  $map = variable_get('rpx_profile_fields_map', array());
-
   $form['field_update'] = array(
    '#type' => 'fieldset',
    '#title' => t('Default field update options for new mappings'),
diff --git rpx_ui.module rpx_ui.module
index 273af10..2643c6e 100644
--- rpx_ui.module
+++ rpx_ui.module
@@ -331,63 +331,48 @@ function rpx_get_enabled_provider_array($force_lookup = FALSE) {
   return $display_list;
 }
 
-function _rpx_drupal_field_catalog() {
+function _rpx_drupal_field_catalog($entities = array('profile', 'profile2', 'user')) {
   $catalog = array();
 
-  // Build an array containing the fields defined by the Profile core module.
-  if (module_exists('profile')) {
-    $result = db_query("SELECT fid, title, name FROM {profile_field} WHERE type IN ('textfield', 'textarea', 'url') ORDER BY weight, title", array());
-    $fields = array();
-    while ($row = $result->fetchObject()) {
-      $fields[$row->name] = $row->title;
-    }
-    // The core profile (legacy) module has no notion of bundles, so this
-    // is just for consistency with the new fieldable entities (profile2, user,
-    // etc.)
-    $bundles = array(
-      '' => array(
-        'title' => '',
-        'fields' => $fields,
-      ),
-    );
-    $catalog['profile'] = array(
-      'title' => 'Profile (Core)',
-      'bundles' => $bundles,
-    );
-  }
-  // Add the fields defined by the Profile2 module.
-  // @todo Is there a way to only include those fields that accept text as input
-  // for now? This way it will be less error-prone experience for users.
-  if (module_exists('profile2')) {
-    $catalog['profile2'] = array(
-      'title' => 'Profile 2',
-      'bundles' => array(),
-    );
-    foreach (field_info_bundles('profile2') as $bundle_name => $bundle) {
-      $catalog['profile2']['bundles'][$bundle_name] = array(
-        'title'  => $bundle['label'],
-        'fields' => array(),
-      );
-      foreach (field_info_instances('profile2', $bundle_name) as $field_name => $field) {
-        $catalog['profile2']['bundles'][$bundle_name]['fields'][$field_name] = $field['label'];
-      }
-    }
-  }
-  // Add the fields defined by the User entity.
-  $catalog['user'] = array(
-    'title' => 'User',
-    'bundles' => array(),
-  );
-  foreach (field_info_bundles('user') as $bundle_name => $bundle) {
-    $catalog['user']['bundles'][$bundle_name] = array(
-      'title'  => $bundle['label'],
-      'fields' => array(),
-    );
-    foreach (field_info_instances('user', $bundle_name) as $field_name => $field) {
-      $catalog['user']['bundles'][$bundle_name]['fields'][$field_name] = $field['label'];
-    }
-  }
-
+	foreach ($entities as $entity) {
+		// Handle special case for core "profile" entity.
+		if ($entity == 'profile' && module_exists('profile')) {
+	    $result = db_query("SELECT fid, title, name FROM {profile_field} WHERE type IN ('textfield', 'textarea', 'url') ORDER BY weight, title", array());
+	    $fields = array();
+	    while ($row = $result->fetchObject()) {
+	      $fields[$row->name] = $row->title;
+	    }
+	    // The core profile (legacy) module has no notion of bundles, so this
+	    // is just for consistency with the new fieldable entities (profile2, user,
+	    // etc.)
+	    $bundles = array(
+	      '' => array(
+	        'title' => '',
+	        'fields' => $fields,
+	      ),
+	    );
+	    $catalog['profile'] = array(
+	      'title' => 'Profile (Core)',
+	      'bundles' => $bundles,
+	    );
+	  }
+		else if ($entity && $info = entity_get_info($entity)) {
+		  $catalog[$entity] = array(
+		    'title' => check_plain($info['label']),
+		    'bundles' => array(),
+		  );
+		  foreach (field_info_bundles($entity) as $bundle_name => $bundle) {
+		    $catalog[$entity]['bundles'][$bundle_name] = array(
+		      'title'  => check_plain($bundle['label']),
+		      'fields' => array(),
+		    );
+		    foreach (field_info_instances($entity, $bundle_name) as $field_name => $field) {
+		      $catalog[$entity]['bundles'][$bundle_name]['fields'][$field_name] = $field['label'];
+		    }
+		  }
+		}
+	}
+	
   return $catalog;
 }
 
