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..4a9f4aa --- /dev/null +++ rpx_friends/rpx_friend/rpx_friend.info.inc @@ -0,0 +1,89 @@ +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['provider'] = array( + 'label' => t('Provider ID'), + 'type' => 'text', // Should be token, but let's keep compatible with current rpx module. + 'description' => t('The machine-readable name of the provider.'), + 'getter callback' => 'entity_property_verbatim_get', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer janrain friends', + 'required' => TRUE, + ) + $properties['provider']; +*/ + $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']; +/* + $properties['profile_url'] = array( + 'label' => t('Friend profile URL'), + 'type' => 'uri', + 'description' => t('The contact\'s profile URL.'), + 'getter callback' => 'entity_property_verbatim_get', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer janrain friends', + 'required' => TRUE, + ) + $properties['profile_url']; +*/ + 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..dc8aea4 --- /dev/null +++ rpx_friends/rpx_friend/rpx_friend.install @@ -0,0 +1,85 @@ + 'A Janrain Engage friend.', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier.', + ), + /* + 'provider' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The machine-readable name of the provider.', + ), + */ + '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.', + ), + /* + 'profile_url' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The friend\'s profile URL.', + ), + */ + ), + 'primary key' => array('id'), + // @todo: unique keys + 'indexes' => array( + 'uid' => array('uid'), + 'friend_uid' => array('friend_uid'), + //'profile_url' => array('profile_url'), + ), + 'foreign keys' => array( + 'uid' => array('users' => 'uid'), + 'friend_uid' => array('users' => 'uid'), + 'rpxid' => array('authmap' => 'authname'), + 'friend_rpxid' => array('authmap' => 'authname'), + //'profile_url' => 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..9642ca5 --- /dev/null +++ rpx_friends/rpx_friend/rpx_friend.module @@ -0,0 +1,333 @@ + '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_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 @@ + 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 @@ + 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..c949103 --- /dev/null +++ rpx_friends/rpx_friends.admin.inc @@ -0,0 +1,339 @@ +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, User Reference, User Relationships and Flag 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 Friend 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 Janrain Engage Friend entity module for non-existing users or User Reference, User Relationships or Flag 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.module rpx_friends/rpx_friends.module new file mode 100644 index 0000000..1116014 --- /dev/null +++ rpx_friends/rpx_friends.module @@ -0,0 +1,396 @@ +' . t('Configure how friends should be created and configure mapping of friends fields to the 3rd party friends data returned by Engage.') . '

'. t('To create friends for non-existing users, you need to enable Janrain Engage Friend entity module.') . '
'. t('To relate existing users as friends, you need at least one of the following modules: User Reference, User Relationships or Flag.') . '

'; + return $help; + } +} + +/** + * 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', + ); + 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; +} + +/** + * 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['cron']) { + $queue = DrupalQueue::get('rpx_friends_queue_retrieve'); + $queue->createItem(array( + 'rpxid' => $info['id'], + 'uid' => $info['user']->uid, + )); + } + 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. get_contacts() 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']['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 strtotime() 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, + )); + } + if ($settings['delete']) { + + } + } + 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, + )); +} + +/** + * 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 @@ +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; }