diff --git rpx_core.module rpx_core.module
index 7d72b2a..681d2d1 100644
--- rpx_core.module
+++ rpx_core.module
@@ -581,10 +581,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?
@@ -592,7 +603,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 === '') {
@@ -619,7 +630,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();
@@ -640,7 +651,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;
       }
 
@@ -649,7 +660,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'] !== '') {
@@ -660,7 +671,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();
@@ -670,21 +681,27 @@ 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 if (module_exists($mapping['set'])) {
+      	$entity = module_invoke($mapping['set'], 'rpx_import_user_data_entity', $account, $data, $mapping);
+      	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;
       }
 
@@ -740,7 +757,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_core.webapi.inc rpx_core.webapi.inc
index a140242..8ec33f7 100644
--- rpx_core.webapi.inc
+++ rpx_core.webapi.inc
@@ -13,6 +13,8 @@ class RPX {
   const auth_api = 'https://rpxnow.com/api/v2/auth_info';
   const map_api = 'https://rpxnow.com/api/v2/map';
   const unmap_api = 'https://rpxnow.com/api/v2/unmap';
+  const get_contacts = 'https://rpxnow.com/api/v2/get_contacts';
+  const set_status = 'https://rpxnow.com/api/v2/set_status';
 
   /**
    * Performs a lookup_rp request.
@@ -195,6 +197,71 @@ class RPX {
     return $data['enabled_providers'];
   }
 
+  /**
+   * 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(RPX::get_contacts, $options);
+
+    if(isset($result->error)) {
+      RPX::report_error($result);
+      return FALSE;
+    }
+
+    return json_decode($result->data, TRUE);
+  }
+
+  /**
+   * Set the status message for the account corresponding to an identifier.
+   *
+   * @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.
+   *
+   * @param string $status
+   *   The status message to set.
+   *
+   * @param string $truncate
+   *   Truncate status.
+   *
+   * @see rpx_token_handler()
+   * @see http://rpxnow.com/docs
+   */
+  static function set_status($api_key, $identifier, $status, $truncate = TRUE) {
+    $post_data = '&apiKey=' . $api_key . '&identifier=' . $identifier . '&status=' . $status . '&truncate=' . ($truncate ? 'true' : 'false') . '&format=json';
+
+    $options['data'] = $post_data;
+    $options['method'] = 'POST';
+
+    $result = drupal_http_request(RPX::set_status, $options);
+
+    if(isset($result->error)) {
+      RPX::report_error($result);
+      return FALSE;
+    }
+
+    return json_decode($result->data, TRUE);
+  }
+
   static function locales() {
     return array('pt-BR', 'vi', 'zh', 'nl', 'sr', 'ko', 'ru', 'sv-SE', 'ro', 'pt', 'it', 'hu', 'fr', 'ja', 'en', 'bg', 'es', 'el', 'pl', 'de', 'cs', 'da');
   }
diff --git rpx_friends/rpx_friend/rpx_friend.info rpx_friends/rpx_friend/rpx_friend.info
new file mode 100644
index 0000000..701fd21
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.info
@@ -0,0 +1,12 @@
+; $Id$
+name = Janrain Engage Friend Entity
+description = Provides an entity for Janrain Engage Friend.
+files[] = rpx_friend.module
+files[] = rpx_friend.info.inc
+files[] = views/rpx_friend.views.inc
+files[] = views/rpx_friend_handler_field_invite.inc
+dependencies[] = entity
+dependencies[] = rpx_core
+configure = admin/structure/rpx_friend/edit/fields
+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..572ba45
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.info.inc
@@ -0,0 +1,66 @@
+<?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 friend entity',
+      'required' => TRUE,
+      'clear' => array('uid'),
+    );
+
+    $properties['friend'] = array(
+      'label' => t("Friend"),
+      'type' => 'user',
+      'description' => t("The friend account, if exists."),
+      'getter callback' => 'entity_property_getter_method',
+      'setter callback' => 'entity_property_setter_method',
+      'setter permission' => 'administer janrain friend entity',
+      '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 friend entity',
+      '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 friend entity',
+      'required' => TRUE,
+    ) + $properties['friend_rpxid'];
+
+    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..4e880be
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.module
@@ -0,0 +1,491 @@
+<?php
+
+/**
+ * @file
+ * A Janrain Engage friend entity.
+ */
+
+/**
+ * Implements hook_views_api().
+ */
+function rpx_friend_views_api() {
+  return array(
+    'api' => '3',
+    'path' => drupal_get_path('module', 'rpx_friend') . '/views',
+  );
+}
+
+/**
+ * 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'),
+  );
+  $items['admin/config/people/rpx/mapping/friends/create'] = array(
+    'title' => 'Create new mapping',
+    'page callback' => 'rpx_profile_mapping_create',
+    'access arguments' => array('administer janrain engage'),
+    'type' => MENU_LOCAL_ACTION,
+    'file' => 'rpx_ui.admin.inc',
+    'file path' => drupal_get_path('module', 'rpx_ui'),
+    'options' => array('query' => array('destination' => 'admin/config/people/rpx/mapping/friends')), // Does not work ?
+  );
+  $items['admin/config/people/rpx/mapping/friends/edit'] = array(
+    'title' => 'Edit mapping options',
+    'page callback' => 'rpx_friend_get_form',
+    'page arguments' => array('rpx_friend_mapping_edit_form', 7, 'friend'),
+    'access arguments' => array('administer janrain engage'),
+    'type' => MENU_CALLBACK,
+    'file' => 'rpx_friend.pages.inc',
+    'file path' => drupal_get_path('module', 'rpx_friend'),
+  );
+  $items['admin/config/people/rpx/mapping/friends/default'] = array(
+    'title' => 'Edit default mapping options',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rpx_profile_mapping_default'),
+    'access arguments' => array('administer janrain engage'),
+    'type' => MENU_LOCAL_ACTION,
+    'file' => 'rpx_ui.admin.inc',
+    'file path' => drupal_get_path('module', 'rpx_ui'),
+  );
+  $items['rpx_friend/invite'] = array(
+    'page callback' => 'rpx_friend_invite',
+    'page arguments' => array(2),
+    'access arguments' => array('import janrain friends'),
+    'type' => MENU_CALLBACK,
+    'file' => 'rpx_friend.pages.inc',
+  );
+  return $items;
+}
+
+/**
+ * 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.'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_theme().
+ */
+function rpx_friend_theme() {
+  return array(
+    'rpx_friend_form' => array(
+      'render element' => 'form',
+    ),
+  );
+}
+
+/**
+ * Implements hook_forms().
+ */
+function rpx_friend_forms() {
+  $forms = array();
+  $forms['rpx_friend_mapping_edit_form']['callback'] = 'rpx_profile_mapping_edit_form';
+  return $forms;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function rpx_friend_form_rpx_friends_mapping_settings_form_alter(&$form, &$form_state) {
+  $map = variable_get('rpx_friend_fields_map', array());
+  
+  $form['rpx_friend_fields_map'] = array(
+    '#type' => 'fieldset',
+    '#tree' => TRUE,
+    '#title' => t('Friend entity'),
+    '#group' => 'settings',
+    '#weight' => -10,
+    '#theme' => 'rpx_friend_form',
+  );
+  
+  foreach ($map as $mid => $mapping) {
+    $form['rpx_friend_fields_map'][$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',
+      ),
+      'update' => array(
+        '#type' => 'value',
+        '#value' => isset($mapping['update']) ? $mapping['update'] : NULL,
+      ),
+    );
+    foreach (field_info_instances('rpx_friend', 'rpx_friend') as $field_name => $field) {
+      $form['rpx_friend_fields_map'][$mid]['field']['#options'][$field_name] = $field['label'];
+    }
+  }
+}
+
+/**
+ * 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_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($account, $rpxid, $entries) {
+  $settings = _rpx_friends_settings();
+  $provider = _rpx_get_identity_provider($rpxid);
+  
+  if ($settings['delete']) {
+    $friends = rpx_friend_load_multiple(FALSE, array('uid' => $account->uid, 'rpxid' => $rpxid));
+  }
+  
+  foreach ($entries as $entry) {
+    $friend = user_external_load($entry['id']);
+
+    $info = array(
+      'rpxid' => $rpxid,
+      'friend' => (is_object($friend) && $friend->uid > 0) ? $friend : NULL,
+      'friend_rpxid' => $entry['id'],
+      'merged_poco' => $entry,
+    );
+    _rpx_import_user_data('friend', $provider['name'], $account, $info);
+    
+    if ($settings['delete']) {
+      foreach ($friends as $key => $friend) {
+        if ($entry['id'] == $friend->friend_rpxid) {
+          unset($friends[$key]);
+          break;
+        }
+      }
+      rpx_friend_delete_multiple(array_keys($friends));
+    }
+  }
+}
+
+/**
+ * Implements hook_rpx_import_user_data().
+ */
+function rpx_friend_rpx_import_user_data_entity($account, $data, $mapping) {
+  $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;
+}
+
+/**
+ * Theme Engage friend field mapping form.
+ *
+ * @ingroup themeable
+ */
+function theme_rpx_friend_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' || $subkey == 'update') {
+          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;
+}
+
+/**
+ * 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;
+  }
+}
+
+/**
+ * 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.pages.inc rpx_friends/rpx_friend/rpx_friend.pages.inc
new file mode 100644
index 0000000..f3b9660
--- /dev/null
+++ rpx_friends/rpx_friend/rpx_friend.pages.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Pages and admin callbacks for the rpx_friend module.
+ */
+
+/**
+ * Menu callback: Retrieve wrapped form for friends.
+ */
+function rpx_friend_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: Invite a friend.
+ */
+function rpx_friends_invite() {
+  global $user;
+  
+  $rpxid = $_GET['rpxid'];
+
+  // Set status.
+  $results = RPX::set_status(variable_get('rpx_apikey', ''), $rpxid, variable_get('rpx_friends_invite_message', t('You have been invited to join @site-name at @link by @name', array('@link' => url('<front>', array('absolute' => TRUE)), '@site-name' => variable_get('site_name', 'Drupal'), '@name' => format_username($user)))));
+
+  if ($results['stat'] != 'ok') {
+    drupal_set_message(t('Impossible to send an invitation.'), 'error');
+    watchdog('rpx_friends', 'Failed to send an invitation to %rpxid for user ID %id', array('%rpxid' => $rpxid, '%id' => $user->uid), WATCHDOG_ERROR);
+    drupal_goto();
+  }
+  
+  drupal_set_message(t('An invitation has been sent.'));
+  drupal_goto();
+}
diff --git rpx_friends/rpx_friend/views/rpx_friend.views.inc rpx_friends/rpx_friend/views/rpx_friend.views.inc
new file mode 100644
index 0000000..2b13597
--- /dev/null
+++ rpx_friends/rpx_friend/views/rpx_friend.views.inc
@@ -0,0 +1,52 @@
+<?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']['user'] = array(
+    'relationship' => array(
+      'label' => t('User'),
+      'base' => 'users',
+      'base field' => 'uid',
+      'relationship field' => 'uid',
+      'handler' => 'views_handler_relationship',
+    ),
+    'title' => t('User account'),
+    'help' => t('Relate to user account.'),
+  );
+  $data['rpx_friend']['friend'] = array(
+    'relationship' => array(
+      'label' => t('Friend'),
+      'base' => 'users',
+      'base field' => 'uid',
+      'relationship field' => 'friend_uid',
+      'handler' => 'views_handler_relationship',
+    ),
+    'title' => t('Friend account'),
+    'help' => t('Relate to friend account.'),
+  );
+  $data['rpx_friend']['invite'] = array(
+    'field' => array(
+      'title' => t('Invite'),
+      'help' => t('Provide a link to invite a friend.'),
+      'handler' => 'rpx_friend_handler_field_invite',
+    ),
+  );
+}
diff --git rpx_friends/rpx_friend/views/rpx_friend_handler_field_invite.inc rpx_friends/rpx_friend/views/rpx_friend_handler_field_invite.inc
new file mode 100644
index 0000000..ee4b764
--- /dev/null
+++ rpx_friends/rpx_friend/views/rpx_friend_handler_field_invite.inc
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Views field handler for Janrain Friend.
+ */
+
+/**
+ * Field handler to present Janrain Friend links.
+ */
+class rpx_friend_handler_field_invite extends views_handler_field {
+  function construct() {
+    parent::construct();
+    $this->additional_fields['friend_rpxid'] = 'friend_rpxid';
+  }
+
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['text'] = array('default' => '', 'translatable' => FALSE);
+    return $options;
+  }
+
+  function options_form(&$form, &$form_state) {
+    $form['text'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Text to display'),
+      '#default_value' => $this->options['text'],
+    );
+    parent::options_form($form, $form_state);
+  }
+
+  function query() {
+    $this->ensure_my_table();
+    $this->add_additional_fields();
+  }
+
+  function render($values) {
+    $id = $this->get_value($values, 'friend_rpxid');
+    return $this->render_link($this->sanitize_value($id), $values);
+  }
+
+  function render_link($rpxid, $values) {
+    $this->options['alter']['make_link'] = TRUE;
+    $this->options['alter']['absolute'] = TRUE;
+    // @todo : destination.
+    $this->options['alter']['path'] = url('rpx_friend/invite', array('query' => array('rpxid' => $rpxid, 'destination' => ''), 'absolute' => TRUE));
+    $text = !empty($this->options['text']) ? $this->options['text'] : t('invite');
+    return $text;
+  }
+}
diff --git rpx_friends/rpx_friend_flag/rpx_friend_flag.info rpx_friends/rpx_friend_flag/rpx_friend_flag.info
new file mode 100644
index 0000000..0a84d24
--- /dev/null
+++ rpx_friends/rpx_friend_flag/rpx_friend_flag.info
@@ -0,0 +1,9 @@
+; $Id$
+name = Janrain Engage Friend Flag integration
+description = Provides integration of Janrain Engage Friend with Flag module.
+dependencies[] = rpx_friends
+dependencies[] = flag
+files[] = rpx_friend_flag.module
+configure = admin/config/people/rpx/mapping/friends
+package = Janrain Engage
+core = 7.x
diff --git rpx_friends/rpx_friend_flag/rpx_friend_flag.module rpx_friends/rpx_friend_flag/rpx_friend_flag.module
new file mode 100644
index 0000000..1b95d19
--- /dev/null
+++ rpx_friends/rpx_friend_flag/rpx_friend_flag.module
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Integrates Janrain Engage Friend with Flag module.
+ */
+
+/**
+ * Implements hook_rpx_friends_retrieve().
+ */
+function rpx_friend_flag_rpx_friends_retrieve($account, $rpxid, $entries) {
+  $settings = variable_get('rpx_friend_flag', array(
+    'name' => NULL,
+    'skip_permission_check' => TRUE,
+  ));
+
+  foreach ($entries as $key => $entry) {
+    if ($friend = user_external_load($entry['id'])) {
+      $existing = flag_get_user_flags('user', $friend->uid, $account->uid);
+      // Flag does not exist.
+      if (!isset($existing[$settings['name']])) {
+        $flag = flag_get_flag(NULL, $settings['name']);
+        $flag->flag('flag', $friend->uid, $account, $settings['skip_permission_check']);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function rpx_friend_flag_form_rpx_friends_mapping_settings_form_alter(&$form, &$form_state) {
+  $settings = variable_get('rpx_friend_flag', array(
+    'name' => NULL,
+    'skip_permission_check' => TRUE,
+  ));
+
+  $form['rpx_friend_flag'] = array(
+    '#type' => 'fieldset',
+    '#tree' => TRUE,
+    '#title' => t('Flag'),
+    '#group' => 'settings',
+  );
+  
+  $form['rpx_friend_flag']['name'] = array(
+    '#type' => 'select',
+    '#title' => t('Flag'),
+    '#options' => array(),
+    '#empty_option' => t('- Select a flag -'),
+    '#default_value' => $settings['name'],
+    '#description' => t('Choose a user flag.'),
+  );
+  
+  $flags = flag_get_flags();
+
+  foreach ($flags as $flag) {
+    if ($flag->content_type == 'user') {
+      $form['rpx_friend_flag']['name']['#options'][$flag->fid] = $flag->title;
+    }
+  }
+  
+  $form['rpx_friend_flag']['skip_permission_check'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Skip permission check'),
+    '#default_value' => $settings['skip_permission_check'],
+    '#description' => t('Whether the permission to create this flag for the user should be checked or not.'),
+  );
+}
\ No newline at end of file
diff --git rpx_friends/rpx_friend_user_reference/rpx_friend_user_reference.info rpx_friends/rpx_friend_user_reference/rpx_friend_user_reference.info
new file mode 100644
index 0000000..6e35b8c
--- /dev/null
+++ rpx_friends/rpx_friend_user_reference/rpx_friend_user_reference.info
@@ -0,0 +1,9 @@
+; $Id$
+name = Janrain Engage Friend User reference integration
+description = Provides integration of Janrain Engage Friend with User reference module.
+dependencies[] = rpx_friends
+dependencies[] = user_reference
+files[] = rpx_friend_user_reference.module
+configure = admin/config/people/rpx/mapping/friends
+package = Janrain Engage
+core = 7.x
diff --git rpx_friends/rpx_friend_user_reference/rpx_friend_user_reference.module rpx_friends/rpx_friend_user_reference/rpx_friend_user_reference.module
new file mode 100644
index 0000000..6d73c08
--- /dev/null
+++ rpx_friends/rpx_friend_user_reference/rpx_friend_user_reference.module
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * @file
+ * Integrates Janrain Engage Friend with User reference module.
+ */
+
+/**
+ * Implements hook_theme().
+ */
+function rpx_friend_user_reference_theme() {
+  return array(
+    'rpx_friend_user_reference_form' => array(
+      'render element' => 'form',
+    ),
+  );
+}
+
+/**
+ * Implements hook_rpx_friends_retrieve().
+ */
+function rpx_friend_user_reference_rpx_friends_retrieve($account, $rpxid, $entries) {
+  $settings = variable_get('rpx_friend_user_reference', array(
+    'field_set' => NULL,
+    'field_bundle' => NULL,
+    'field' => NULL,
+  ));
+
+  // Profile2.
+  if ($settings['field_set'] == 'profile2' && module_exists('profile2')) {
+    $user_entity = profile2_load_by_user($account->uid, $settings['field_bundle']);
+  }
+  // User.
+  else if ($settings['field_set'] == 'user') {
+    $user_entity = new stdClass();
+    $user_entity->uid = $account->uid;
+  }
+
+  if (!isset($user_entity->{$settings['field']}[LANGUAGE_NONE])) {
+    $user_entity->{$settings['field']}[LANGUAGE_NONE] = array();
+  }
+
+  foreach ($entries as $key => $entry) {
+    if ($friend = user_external_load($entry['id'])) {
+      $exists = FALSE;
+      foreach ($user_entity->{$settings['field']}[LANGUAGE_NONE] as $value) {
+        if ($value['uid'] == $friend->uid) {
+          $exists = TRUE;
+          break;
+        }
+      }
+      // Reference does not exist.
+      if (!$exists) {
+        $user_entity->{$settings['field']}[LANGUAGE_NONE][] = array(
+          'uid' => $friend->uid,
+        );
+        field_attach_update($settings['field']['field_set'], $user_entity);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function rpx_friend_user_reference_form_rpx_friends_mapping_settings_form_alter(&$form, &$form_state) {
+  $settings = variable_get('rpx_friend_user_reference', array(
+    'field_set' => NULL,
+    'field_bundle' => NULL,
+    'field' => NULL,
+  ));
+
+  // 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['rpx_friend_user_reference'] = array(
+    '#type' => 'fieldset',
+    '#tree' => TRUE,
+    '#title' => t('User Reference'),
+    '#weight' => 11,
+    '#group' => 'settings',
+    '#theme' => 'rpx_friend_user_reference_form',
+    '#states' => array(
+      'visible' => array('select[name="rpx_friend_user_reference[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['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['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['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()),
+        ),
+      ),
+    ),
+  );
+}
+
+/**
+ * Theme User reference field picker.
+ *
+ * @ingroup themeable
+ */
+function theme_rpx_friend_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;
+}
diff --git rpx_friends/rpx_friend_user_relationships/rpx_friend_user_relationships.info rpx_friends/rpx_friend_user_relationships/rpx_friend_user_relationships.info
new file mode 100644
index 0000000..73c2265
--- /dev/null
+++ rpx_friends/rpx_friend_user_relationships/rpx_friend_user_relationships.info
@@ -0,0 +1,9 @@
+; $Id$
+name = Janrain Engage Friend User relationships integration
+description = Provides integration of Janrain Engage Friend with User relationships module.
+dependencies[] = rpx_friends
+dependencies[] = user_relationships
+files[] = rpx_friend_user_relationships.module
+configure = admin/config/people/rpx/mapping/friends
+package = Janrain Engage
+core = 7.x
diff --git rpx_friends/rpx_friend_user_relationships/rpx_friend_user_relationships.module rpx_friends/rpx_friend_user_relationships/rpx_friend_user_relationships.module
new file mode 100644
index 0000000..9274733
--- /dev/null
+++ rpx_friends/rpx_friend_user_relationships/rpx_friend_user_relationships.module
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Integrates Janrain Engage Friend with User relationships module.
+ */
+
+/**
+ * Implements hook_rpx_friends_retrieve().
+ */
+function rpx_friend_user_relationships_rpx_friends_retrieve($account, $rpxid, $entries) {
+  $settings = variable_get('rpx_friend_user_relationships', array(
+    'type' => NULL,
+    'approve' => TRUE,
+  ));
+
+  foreach ($entries as $key => $entry) {
+    if ($friend = user_external_load($entry['id'])) {
+      $existing = user_relationships_load(array('requester_id' => $account->uid, 'requestee_id' => $friend->uid, 'rtid' => $settings['type']));
+      $relationship = reset($existing);
+      // No relationship exists.
+      if (empty($existing)) {
+        user_relationships_request_relationship($account, user_load($friend->uid), $settings['type'], $settings['approve']);
+      }
+      // Relationship is not approved.
+      else if ($settings['approve'] && !$relationship->approved) {
+        $relationship->approved = TRUE;
+        user_relationships_save_relationship($relationship);
+      }
+    }
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function rpx_friend_user_relationships_form_rpx_friends_mapping_settings_form_alter(&$form, &$form_state) {
+  $settings = variable_get('rpx_friend_user_relationships', array(
+    'type' => NULL,
+    'approve' => TRUE,
+  ));
+
+  $form['rpx_friend_user_relationships'] = array(
+    '#type' => 'fieldset',
+    '#tree' => TRUE,
+    '#title' => t('User Relationships'),
+    '#group' => 'settings',
+  );
+
+  $form['rpx_friend_user_relationships']['type'] = array(
+    '#type' => 'select',
+    '#title' => t('Relationship type'),
+    '#options' => array(),
+    '#default_value' => $settings['type'],
+    '#empty_option' => t('- Select a type -'),
+    '#description' => t('Choose a relation type.'),
+  );
+
+  foreach (user_relationships_types_load() as $type) {
+    $form['rpx_friend_user_relationships']['type']['#options'][$type->rtid] = $type->name;
+  }
+
+  $form['rpx_friend_user_relationships']['approve'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Automatically approve relationship'),
+    '#default_value' => $settings['approve'],
+    '#description' => t('Whether the relation should be automatically approved or not.'),
+  );
+}
diff --git rpx_friends/rpx_friends.info rpx_friends/rpx_friends.info
new file mode 100644
index 0000000..cf05915
--- /dev/null
+++ rpx_friends/rpx_friends.info
@@ -0,0 +1,9 @@
+; $Id$
+name = Janrain Engage Friends
+description = Janrain Engage Friends support.
+files[] = rpx_friends.module
+files[] = rpx_friends.pages.inc
+dependencies[] = rpx_core
+package = Janrain Engage
+core = 7.x
+configure = admin/config/people/rpx
\ No newline at end of file
diff --git rpx_friends/rpx_friends.js rpx_friends/rpx_friends.js
new file mode 100644
index 0000000..33b646a
--- /dev/null
+++ rpx_friends/rpx_friends.js
@@ -0,0 +1,58 @@
+(function ($) {
+
+/**
+ * Attaches the AJAX behavior for Janrain friends.
+ */
+Drupal.behaviors.RPXFriends = {
+  attach: function (context, settings) {
+    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) {
+        var view = '.view-dom-id-' + settings.view_dom_id;
+        if ($(view).parents().hasClass('rpx-friends-views-processed')) {
+          return; // Means break for $.each().
+        }
+        $(view).parent().addClass('rpx-friends-views-processed');
+        
+        // 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;
+        }
+
+        var element_settings = {
+          url: '/rpx_friends/ajax/ajax',
+          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..498b5da
--- /dev/null
+++ rpx_friends/rpx_friends.module
@@ -0,0 +1,234 @@
+<?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.pages.inc',
+  );
+  $items['rpx_friends/ajax'] = array(
+    'page callback' => 'rpx_friends_ajax_callback',
+    'access arguments' => array('import janrain friends'),
+    'type' => MENU_CALLBACK,
+    'file' => 'rpx_friends.pages.inc',
+  );
+  $items['rpx_friends/ajax/%'] = array(
+    'page callback' => 'rpx_friends_ajax_callback',
+    'page arguments' => array(2),
+    'access arguments' => array('import janrain friends'),
+    'type' => MENU_CALLBACK,
+    'file' => 'rpx_friends.pages.inc',
+  );
+  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;
+}
+
+/**
+ * Implements hook_init().
+ */
+function rpx_friends_init() {
+  if (isset($_SESSION['rpx_friends_ajax'])) {
+    $settings = _rpx_friends_settings();
+    $rpxid = is_bool($_SESSION['rpx_friends_ajax']) ? NULL : $_SESSION['rpx_friends_ajax'];   
+    $options = array();
+    
+    foreach ($settings['ajax_views'] as $ajax_view) {
+      $explode = explode(':', $ajax_view);
+      $options[] = array(
+        'view_name' => $explode[0],
+        'view_display_id' => $explode[1],
+        'rpxid' => $rpxid,
+      );
+    }
+    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');
+    
+    unset($_SESSION['rpx_friends_ajax']);
+  }
+}
+
+/**
+ * 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'] = $info['id'];
+    }
+    else {
+      _rpx_friends_retrieve($info['user'], array($info['id']));
+    }
+  }
+}
+
+/**
+ * Implements hook_user_login().
+ */
+function rpx_friends_user_login(&$edit, $account) {
+  if ($settings['use_ajax']) {
+    $_SESSION['rpx_friends_ajax'] = TRUE;
+  }
+  else {
+    _rpx_friends_retrieve($account);
+  }
+}
+
+/**
+ * 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'],
+    '#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($account, $rpxids = NULL) {
+  $entries = array();
+
+  if (!isset($rpxids)) {
+    $rpxids = db_query('SELECT authname FROM {authmap} WHERE uid = :uid AND module = :module', array('uid' => $account->uid, 'module' => 'rpx_core'))->fetchCol();
+  }
+
+  foreach ($rpxids as $rpxid) {
+    // Retrieve contacts.
+    $results = RPX::get_contacts(variable_get('rpx_apikey', ''), $rpxid);
+
+    if ($results['stat'] != 'ok') {
+      drupal_set_message(t('Impossible to retrieve friends list.'), 'error');
+      watchdog('rpx_friends', 'Failed to retrieve friends list for RPXID %id: get_contacts() returned error: %err', array('%id' => $rpxid, '%err' => $results['err']['msg']), WATCHDOG_ERROR);
+    }
+    else {
+      module_invoke_all('rpx_friends_retrieve', $account, $rpxid, $results['response']['entry']);
+    }
+  }
+}
+
+/**
+ * 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' => FALSE,
+    'ajax_views' => array(),
+  ));
+}
diff --git rpx_friends/rpx_friends.pages.inc rpx_friends/rpx_friends.pages.inc
new file mode 100644
index 0000000..f967516
--- /dev/null
+++ rpx_friends/rpx_friends.pages.inc
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Pages and admin callbacks for the rpx_friends module.
+ */
+
+/**
+ * Menu callback: AJAX callback.
+ */
+function rpx_friends_ajax_callback($type = 'ajax') {
+  global $user;
+  
+  if (isset($_REQUEST['rpxid'])) {
+    $rpxids = array($rpxid);
+  }
+  else {
+    $rpxids = NULL;
+  }
+  
+  if ($type == 'ajax') {
+    _rpx_friends_retrieve($user, $rpxids);
+    module_load_include('inc', 'views', 'includes/ajax');
+    $response = views_ajax();
+    ajax_deliver($response);
+  }
+  else {
+    _rpx_friends_retrieve($user, $rpxids);
+    drupal_goto();
+  }
+}
+
+/**
+ * Menu callback: Generate a form to configure friends fields.
+ *
+ * @ingroup forms
+ * @see rpx_mapping_settings()
+ */
+function rpx_friends_mapping_settings_form($form, &$form_state) {
+  $form['settings'] = array(
+    '#type' => 'vertical_tabs',
+  );
+  
+  return system_settings_form($form);
+}
\ No newline at end of file
diff --git rpx_ui.admin.inc rpx_ui.admin.inc
index 1222a1f..32f687b 100644
--- rpx_ui.admin.inc
+++ rpx_ui.admin.inc
@@ -913,10 +913,10 @@ 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);
+  variable_set('rpx_' . $type . '_fields_map', $map);
   drupal_goto('admin/config/people/rpx/mapping');
 }
 
@@ -973,8 +973,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;
 
@@ -996,6 +996,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',
@@ -1039,8 +1041,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'];
@@ -1050,7 +1053,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.
@@ -1065,7 +1068,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;
 }
 
 /**
@@ -1076,8 +1079,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 165e95a..c2dd4fb 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;
 }
 
