'User Management',
'description' => 'Local account to facebook account mapping',
'page callback' => 'drupal_get_form',
'page arguments' => array('fb_user_admin_settings'),
'access arguments' => array(FB_PERM_ADMINISTER),
'file' => 'fb_user.admin.inc',
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Returns configuration for this module, on a per-app basis.
*/
function _fb_user_get_config($fb_app) {
$fb_app_data = fb_get_app_data($fb_app);
$fb_user_data = isset($fb_app_data['fb_user']) ? $fb_app_data['fb_user'] : array();
// Merge in defaults
$fb_user_data += array(
'create_account' => FB_USER_OPTION_CREATE_NEVER,
'map_account' => array(
FB_USER_OPTION_MAP_ALWAYS => FB_USER_OPTION_MAP_ALWAYS,
FB_USER_OPTION_MAP_EMAIL => FB_USER_OPTION_MAP_EMAIL,
),
'new_user_rid' => NULL,
);
return $fb_user_data;
}
/**
* There are several pages where we don't want to automatically create a new
* account or use an account configured for this app.
*/
function _fb_user_special_page() {
// fb_app/event is called by facebook. Don't create accounts on that page.
return ((arg(0) == 'fb_app' && arg(1) == 'event'));
}
/**
* Helper to ensure local user is logged out.
*/
function _fb_user_logout() {
session_destroy();
$user = drupal_anonymous_user();
}
/**
* Implementation of hook_fb.
*/
function fb_user_fb($op, $data, &$return) {
$fb_app = isset($data['fb_app']) ? $data['fb_app'] : NULL;
$fb = isset($data['fb']) ? $data['fb'] : NULL;
global $user;
if ($fb_app) {
$fb_user_data = _fb_user_get_config($fb_app);
}
if ($op == FB_OP_POST_INIT) {
if (isset($_SESSION['fb_user_fbu']) &&
$_SESSION['fb_user_fbu'] != fb_facebook_user()) {
// User has logged out of facebook, and drupal is only now learning about it.
_fb_user_logout();
drupal_goto($_GET['q']); // @TODO - need request params here?
}
if (_fb_user_special_page() ||
(variable_get('site_offline', FALSE) && !user_access('administer site configuration'))) {
// Prevent some behavior.
fb_controls(FB_USER_CONTROL_NO_HONOR_MAP, TRUE);
fb_controls(FB_USER_CONTROL_NO_CREATE_MAP, TRUE);
fb_controls(FB_USER_CONTROL_NO_CREATE_ACCOUNT, TRUE);
}
}
elseif ($op == FB_OP_GET_FBU) {
// This is a request to learn the user's FB id.
$return = _fb_user_get_fbu($data['uid']);
}
elseif ($op == FB_OP_AJAX_EVENT) {
// fb.js has notified us of an event via AJAX. Not the same as facebook event callback above.
if ($data['event_type'] == 'session_change' && isset($data['event_data']['fbu'])) {
// A user has logged in.
// Don't trust fbu from $data['event_data'], too easy to spoof.
// Don't set fb_user if SESSION[fb_user_fbu], could be an old session not properly cleaned up.
if (($fbu = fb_facebook_user($data['fb'])) &&
$fbu != fb_get_fbu($GLOBALS['user'])) {
// There's no need to honor maps in ajax callback. If honored,
// could lead to new session start which would not actually have any
// effect in ajax callback.
fb_controls(FB_USER_CONTROL_NO_HONOR_MAP, TRUE);
_fb_user_process_authorized_user();
}
}
}
}
function fb_user_footer() {
if (($fbu = fb_facebook_user()) &&
$fbu != fb_get_fbu($GLOBALS['user']) &&
$_GET['q'] !== variable_get('site_403', FALSE) &&
$_GET['q'] !== variable_get('site_404', FALSE)) {
$uid = $GLOBALS['user']->uid; // Remember original uid.
_fb_user_process_authorized_user();
if ($uid != $GLOBALS['user']->uid) {
// during user processing, we started a new session.
drupal_goto($_GET['q']); // @TODO - need request params here?
}
}
}
/**
* Test facebook session by calling into facebook. This is expensive, so
* limit check to once per session. Use session variable to flag that we have
* completed the test.
*/
function _fb_user_check_session($fbu) {
// Make sure facebook session is valid and fb_user table is correct.
// Relatively expensive operations, so we perform them only once per session.
if (!isset($_SESSION['fb_user_fbu']) || $_SESSION['fb_user_fbu'] != $fbu) {
if ($valid_session = fb_api_check_session($GLOBALS['_fb'])) { // Expensive check.
$_SESSION['fb_user_fbu'] = $fbu;
}
else {
unset($_SESSION['fb_user_fbu']);
}
}
return (isset($_SESSION['fb_user_fbu']) && $_SESSION['fb_user_fbu'] == $fbu);
}
/**
* If facebook user has authorized app, and account map exists, login as the local user.
*
* @return - TRUE, if user_external_login succeeds.
*/
function _fb_user_external_login($account = NULL) {
$fbu = fb_facebook_user();
if (!$account) {
$account = fb_user_get_local_user($fbu, $GLOBALS['_fb_app']);
}
if ($account &&
$account->uid == $GLOBALS['user']->uid) {
// Already logged in.
return $account;
}
elseif ($fbu &&
$account &&
$account->uid != $GLOBALS['user']->uid &&
!fb_controls(FB_USER_CONTROL_NO_HONOR_MAP)) {
// Map exists. Log in as local user.
if (fb_verbose() === 'extreme') { // debug
watchdog("fb_user", "fb_user_fb changing user to $account->uid");
$session_id = session_id();
}
// user_external_login() fails if already logged in, so log out first.
if ($GLOBALS['user']->uid) {
_fb_user_logout();
}
$valid_user = user_external_login($account);
if (fb_verbose() === 'extreme') { // debug
watchdog("fb_user", "fb_user_fb changed session from $session_id to " . session_id());
}
if ($valid_user) {
// Session changed after external login. Invoking hook here allows modules to drupal_set_message().
fb_invoke(FB_OP_POST_EXTERNAL_LOGIN, array('account' => $account));
return $account;
}
}
return FALSE;
}
/**
* Create a map linking the facebook account to the currently logged in local user account.
*
* @return - TRUE, if map created.
*/
function _fb_user_create_map() {
if ($GLOBALS['user']->uid) {
$fbu = fb_facebook_user();
$account = fb_user_get_local_user($fbu);
if ($fbu &&
!$account &&
!fb_controls(FB_USER_CONTROL_NO_CREATE_MAP)) {
_fb_user_set_map($GLOBALS['user'], $fbu);
return TRUE;
}
}
return FALSE;
}
function _fb_user_create_map_by_email() {
$fbu = fb_facebook_user();
$account = fb_user_get_local_user($fbu, $GLOBALS['_fb_app']);
if ($fbu &&
!$account &&
($email_account = fb_user_get_local_user_by_email($fbu)) &&
!fb_controls(FB_USER_CONTROL_NO_CREATE_MAP)) {
_fb_user_set_map($email_account, $fbu);
return TRUE;
}
return FALSE;
}
/**
* Helper function to create local account for the currently authorized user.
*/
function _fb_user_create_local_account() {
$fbu = fb_facebook_user();
$account = fb_user_get_local_user($fbu);
if ($fbu &&
!$account &&
!fb_controls(FB_USER_CONTROL_NO_CREATE_ACCOUNT)) {
$config = _fb_user_get_config($GLOBALS['_fb_app']);
// Establish user name.
// Case 1: use name from FB
// Case 2: create a unique user name ourselves
// Which we use is determined by the setting at
// admin/build/fb/fb_user
if (variable_get(FB_USER_VAR_USERNAME_STYLE, FB_USER_OPTION_USERNAME_FBU) == FB_USER_OPTION_USERNAME_FULL) {
try {
// Use fb->api() rather than fb_users_getInfo(). Later fails to learn name on test accounts.
$info = $GLOBALS['_fb']->api($fbu);
$username = $info['name'];
} catch (Exception $e) {
fb_log_exception($e, t('Failed to learn full name of new user'), $GLOBALS['_fb']);
}
}
else {
// Create a name that is likely to be unique.
$username = "$fbu@facebook";
}
if ($config['new_user_rid']) {
$roles = array($config['new_user_rid'] => TRUE);
}
else {
$roles = array();
}
$account = fb_user_create_local_user($GLOBALS['_fb'], $GLOBALS['_fb_app'],
$fbu, array(
'name' => $username,
'roles' => $roles,
));
watchdog('fb_user',
t("Created new user !username for application %app", array(
'!username' => l($account->name, 'user/' . $account->uid),
'%app' => $GLOBALS['_fb_app']->label)));
return $account;
}
return FALSE;
}
/**
* Create local account or account map for a facebook user who has authorized the application.
*/
function _fb_user_process_authorized_user() {
$fbu = fb_facebook_user();
$mapped = FALSE;
if ($fbu && _fb_user_check_session($fbu)) {
// First check if map already exists.
$account = fb_user_get_local_user($fbu, $GLOBALS['_fb_app']);
$config = _fb_user_get_config($GLOBALS['_fb_app']);
if (!$account) {
if ($GLOBALS['user']->uid > 0 &&
$config['map_account'][FB_USER_OPTION_MAP_ALWAYS]) {
// Create map for logged in user.
$mapped = _fb_user_create_map();
}
if (!$mapped &&
$config['map_account'][FB_USER_OPTION_MAP_EMAIL]) {
// Create map if email matches.
$mapped = _fb_user_create_map_by_email();
}
if (!$mapped &&
$config['create_account'] == FB_USER_OPTION_CREATE_LOGIN) {
// Create new local account with map.
$mapped = _fb_user_create_local_account();
}
if ($mapped) {
$account = fb_user_get_local_user($fbu, $GLOBALS['_fb_app']);
}
}
if ($account) {
// Ensure the user has any roles associated with this app.
$rid = $config['new_user_rid'];
if ($account && $rid &&
(!isset($account->roles[$rid]) || !$account->roles[$rid])) {
// there should be an API for this...
db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)',
$account->uid, $rid);
watchdog('fb_user', "Added role %role to existing user !username for application %app", array(
'!username' => theme('username', $account),
'%app' => $fb_app->label,
'%role' => $rid));
}
// Login as facebook user, if not already.
_fb_user_external_login($account);
}
}
}
/**
* Implements hook_form_alter().
*/
function fb_user_form_alter(&$form, &$form_state, $form_id) {
if (isset($form['fb_app_data'])) {
// Add our settings to the fb_app edit form.
module_load_include('inc', 'fb_user', 'fb_user.admin');
fb_user_admin_form_alter($form, $form_state, $form_id);
}
elseif ($form_id == 'user_edit' && ($app = $form['#fb_app'])) {
// Disable buttons on user/edit/app pages, nothing to submit
unset($form['submit']);
unset($form['delete']);
}
elseif ($form_id == 'user_profile_form') {
// On user/edit, hide proxied email
if (isset($form['account']) && isset($form['account']['mail'])) {
$account = $form['_account']['#value'];
if (isset($account->fb_user_proxied_mail) &&
($form['account']['mail']['#default_value'] == $account->fb_user_proxied_mail)) {
unset($form['account']['mail']['#default_value']);
}
}
}
elseif (($form_id == 'user_register' && variable_get(FB_USER_VAR_ALTER_REGISTER, TRUE)) ||
($form_id == 'user_login' && variable_get(FB_USER_VAR_ALTER_LOGIN, TRUE))) {
if ($fb = $GLOBALS['_fb']) {
if ($fbu = fb_facebook_user()) {
// Facebook user has authorized app.
// Show user name and picture.
$form['fb_user'] = array(
'name' => array(
'#value' => '
", '#suffix' => "
\n", ); } elseif (!$fbu) { // they are not connected to facebook; give option to connect here $fb_button = theme('fb_login_button', t('Connect with Facebook'), array('form_id' => $form_id)); $form['fb_user'] = array( '#value' => $fb_button, '#type' => 'markup', '#weight' => -1, '#prefix' => "\n", '#suffix' => "
\n", ); } } else { $form[$_fb_app->label] = array( '#type' => 'markup', '#value' => t('Local account !username is not connected to facebook.com.', array('!username' => theme('username', $account), )), '#prefix' => "\n", '#suffix' => "
\n", ); } } if (isset($form)) { $form['map']['#tree'] = TRUE; } else { // Could add a facebook connect button or canvas page authorization link. $form['description'] = array( '#type' => 'markup', '#value' => t('This account is not associated with a Facebook Application.'), '#prefix' => '', '#suffix' => '
', ); } return $form; } elseif ($op == 'update' && $category == 'fb_user') { if ($edit['map']) { _fb_user_set_map($account, $edit['map']); } else { // Delete account mapping, because administrator has unchecked the connect option. db_query('DELETE FROM {fb_user} WHERE uid=%d', $account->uid); } } elseif ($op == 'delete') { db_query('DELETE FROM {fb_user} WHERE uid=%d', $account->uid); } } /** * Helper function to add or update a row in the fb_user table, which maps local uid to facebook ids. */ function _fb_user_set_map($account, $fbu) { if ($fbu && $account->uid != 0) { $an_fb_user = array( 'uid' => $account->uid, 'fbu' => $fbu, ); // test if record exists // @TODO - should this be DELETE FROM {fb_user} WHERE uid=%d OR fbu=%d? $sql = "SELECT count(*) AS c FROM {fb_user} WHERE uid = %d"; $result = db_query($sql, $account->uid); $rec = db_fetch_object($result); if ($rec->c > 0) { // exists so send along keys for drupal_write_record to update record $result = drupal_write_record('fb_user', $an_fb_user, "uid"); } else { $result = drupal_write_record('fb_user', $an_fb_user); } if (fb_verbose()) { watchdog('fb_user', 'Using fb_user to associate user !user with facebook user id %fbu.', array('!user' => l($account->name, 'user/' . $account->uid), '%fbu' => $fbu, )); } } } /** * Creates a local Drupal account for the specified facebook user id. * * @param fbu * The facebook user id corresponding to this account. * * @param edit * An associative array with user configuration. As would be passed to user_save(). */ function fb_user_create_local_user($fb, $fb_app, $fbu, $edit = array()) { // Ensure $fbu is a real facebook user id. if (!$fbu || !is_numeric($fbu)) { return; } $account = fb_user_get_local_user($fbu); if (!$account) { // Create a new user in our system // Learn some details from facebook. $infos = fb_users_getInfo(array($fbu), $fb); $info = $infos[0]; // All Drupal users get authenticated user role. $edit['roles'][DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; if (isset($edit['name']) && $edit['name']) { $username = $edit['name']; } else { // Fallback, should never be reached. $username = "$fbu@facebook"; $edit['name'] = $username; } $i = 1; while (db_result(db_query("SELECT name FROM {users} WHERE name='%s'", $edit['name']))) { $i++; $edit['name'] = $username . '_' . $i; } // Give modules a way to suppress new account creation. $edit['fb_user_do_create'] = TRUE; // Allow third-party module to adjust any of our data before we create // the user. $edit = fb_invoke(FB_OP_PRE_USER, array('fbu' => $fbu, 'fb' => $GLOBALS['_fb'], 'fb_app' => $fb_app, 'info' => $info), $edit); if ($edit['fb_user_do_create']) { unset($edit['fb_user_do_create']); // Don't confuse user_save. // Fill in any default that are missing. $defaults = array( 'pass' => user_password(), 'init' => db_escape_string($fbu . '@facebook'), // Supposed to be email, but we may not know it. 'status' => 1, ); // Mail available only if user has granted extended permission. if (isset($info['email']) && ($info['email'] != $info['proxied_email'])) { $defaults['mail'] = $info['email']; } // Merge defaults $edit = array_merge($defaults, $edit); // Confirm username is not taken. FB_OP_PRE_USER may have changed it. if ($uid = db_result(db_query("SELECT uid FROM {users} WHERE name='%s'", $edit['name']))) { // The desired name is taken. watchdog('fb_user', 'Failed to create new user %name. That name is already in the users table.', array('%name' => $edit['name']), WATCHDOG_ERROR, l(t('view user'), 'user/' . $uid)); } else { $account = user_save('', $edit); _fb_user_set_map($account, $fbu); watchdog('fb_user', 'New user: %name %email.', array('%name' => $account->name, '%email' => '<'. $account->mail .'>'), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit')); // Allow third-party modules to act after account creation. fb_invoke(FB_OP_POST_USER, array('account' => $account, 'fb_app' => $fb_app, 'fb' => $fb)); } } } return $account; } /** * Given an app and facebook user id, return the corresponding local user. * * @param $fbu * User's id on facebook.com * * @param $fb_app * Historically, this method took the app details into account when mapping user ids. Presently, this parameter is not used. */ function fb_user_get_local_user($fbu, $fb_app = NULL) { $result = db_result(db_query("SELECT uid FROM {fb_user} WHERE fbu=%d", array( $fbu, ))); if ($result) { return user_load($result); } } /** * Try to determine the local user account by the email address. */ function fb_user_get_local_user_by_email($fbu) { global $_fb; if (isset($_fb) && $fbu) { $info = $_fb->api($fbu); if (isset($info['email']) && ($email = $info['email'])) { return user_load(array('mail' => $email)); } } } /** * Returns local uids of friends of a given user. * * Query is relatively efficient for the current user of a canvas page. For * all other users, and non-canvas pages it requires expensive call to * facebook. That said, our local database query may be inefficient for users * with large numbers of friends, so use with caution. * * TODO: should this function cache results? * * Note: the api takes fbu as a parameter, but this usually causes problems * because facebook restricts users to query only about their own friends. * For the time being, expect this function to work only on canvas pages to * find friends of the current user. * * Only works if the "map accounts" feature is enabled. */ function fb_user_get_local_friends($fbu = NULL, $fb_app = NULL) { if (!isset($fbu)) { $fbu = fb_facebook_user(); } $uids = array(); if ($fbus = fb_get_friends($fbu, $fb_app)) { $query = "SELECT uid FROM {fb_user} WHERE fbu IN ('%s')"; $args[] = implode(',', $fbus); $result = db_query($query, $args); while ($data = db_fetch_object($result)) { if ($data->uid) { $uids[] = $data->uid; } } } return $uids; } /** * Given a local user id, find the facebook id. This is for internal use. * Outside modules use fb_get_fbu(). * * Only works if the "map accounts" feature is enabled, or the account was created by this module. */ function _fb_user_get_fbu($uid) { static $cache = array(); // cache to avoid excess queries. if (!isset($cache[$uid])) { // Look up this user in the authmap $result = db_result(db_query("SELECT fbu FROM {fb_user} WHERE uid=%d", array( $uid, ))); if ($result) { $cache[$uid] = $result; } } if (isset($cache[$uid])) { return $cache[$uid]; } } //// token module hooks. function fb_user_token_list($type = 'all') { if ($type == 'all' || $type == 'fb' || $type == 'fb_app') { $tokens['fb_app']['fb-app-user-fbu'] = t('Current user\'s Facebook ID'); $tokens['fb_app']['fb-app-user-name'] = t('Current user\'s name on Facebook (TODO)'); $tokens['fb_app']['fb-app-user-name-fbml'] = t('Current user\'s name for display on Facebook profile and canvas pages.'); $tokens['fb_app']['fb-app-profile-url'] = t('Current user\'s Facebook profile URL'); } return $tokens; } function fb_user_token_values($type = 'all', $object = NULL) { $values = array(); if ($type == 'fb_app' && $object) { $fb_app = $object; global $user; $fbu = _fb_user_get_fbu($user->uid); if ($fbu) { $values['fb-app-user-fbu'] = $fbu; $values['fb-app-user-name'] = 'TODO XXX'; // XXX $values['fb-app-user-name-fbml'] = '