You are here

fb_register.module in Drupal for Facebook 6.2

This code aims to prevent duplicate accounts.

If a user has an account both locally and on facebook, when they first authorize an app, this code attemps to link the two accounts rather than create a new local account for the facebook user.

http://wiki.developers.facebook.com/index.php/Linking_Accounts_and_Findi...

File

contrib/fb_register.module
View source
<?php

/**
 * @file
 * This code aims to prevent duplicate accounts. 
 * 
 * If a user has an account both locally and on facebook, when they
 * first authorize an app, this code attemps to link the two accounts
 * rather than create a new local account for the facebook user.
 *
 * http://wiki.developers.facebook.com/index.php/Linking_Accounts_and_Finding_Friends
 */

/**
 * Implementation of hook_menu().
 */
function fb_register_menu() {

  // Register Users config page.
  $items[FB_PATH_ADMIN_APPS . '/%fb/fb_register'] = array(
    'title' => 'Register Users',
    'page callback' => 'fb_register_detail_page',
    'page arguments' => array(
      FB_PATH_ADMIN_APPS_ARGS,
    ),
    'access arguments' => array(
      FB_PERM_ADMINISTER,
    ),
    'type' => MENU_LOCAL_TASK,
  );

  // Callback to register users, right now instead of waiting for cron.
  $items[FB_PATH_ADMIN_APPS . '/%fb/fb_register/doit/%'] = array(
    'title' => 'Register Users',
    'page callback' => 'fb_register_all_users',
    'page arguments' => array(
      FB_PATH_ADMIN_APPS_ARGS,
      FB_PATH_ADMIN_APPS_ARGS + 3,
    ),
    'access arguments' => array(
      FB_PERM_ADMINISTER,
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
function fb_register_detail_page($fb_app) {
  $output = '';
  if (isset($_REQUEST['how_many'])) {
    $how_many = $_REQUEST['how_many'];
  }
  else {
    $how_many = variable_get('fb_register_limit', 100);
  }
  $output .= '<p>' . t('Use this form below to test user registration.  Press the Register button to emulate one cron iteration.') . '</p>';
  $output .= drupal_get_form('fb_register_now_form', $fb_app, $how_many);
  $output .= '<p>' . t('The registration module is enabled for this application.  Users will be registered during cron jobs and when they change email addresses.  <br/><a href="!url">Click here to register ALL users now</a>.', array(
    '!url' => url('fb_register/' . $fb_app->label . '/' . $how_many),
  )) . "</p>\n";
  $output .= '<pre>' . print_r($fb_app, 1) . "</pre>\n";

  // debug
  return $output;
}

/**
 * This callback will register a chunk of users, then redirect to
 * itself to register another chunk.  And so on, to quickly register
 * all users of a site, rather than waiting for cron jobs to take care
 * of it all.
 */
function fb_register_all_users($fb_app, $how_many) {
  $success_count = _fb_register_register_users($fb_app, $how_many);
  drupal_set_message(t("%count users successfully registered.", array(
    '%count' => $success_count,
  )));
  $output = '<p>' . _fb_register_summary($fb_app) . "</p>\n";
  if ($success_count > 0) {
    $output .= '<p>' . t('This page will refresh, to register another batch of users...') . '</p>';
    drupal_set_html_head('<meta http-equiv="refresh" content="3" />');
  }
  elseif ($success_count < 0) {
    $output .= '<p>' . t('Encountered an error.  Not refreshing page.') . "</p>\n";
  }
  return $output;
}

/**
 * TODO: This should probably be a theme function.
 */
function _fb_register_summary($fb_app) {

  // Display a summary.
  $cache = cache_get('fb_register_cache');
  if (!$cache) {
    $cache = new stdClass();
    $cache->data = array(
      $fb_app->apikey => 0,
    );
  }
  $last_uid_registered = $cache->data[$fb_app->apikey];
  $registered = db_result(db_query("SELECT count(uid) as count FROM {fb_register} WHERE uid <= %d", $last_uid_registered));
  $local = db_result(db_query("SELECT count(u.uid) FROM {users} u WHERE uid > 0"));
  $registerable = db_result(db_query("SELECT count(u.uid) FROm {users} u WHERE u.uid > 0 ANd u.mail IS NOT NULL AND u.mail <> ''"));
  $summary = t("%registered of %registerable accounts have been registered for !app. <br/>(Of %local total users, %registerable have email addresses.)", array(
    '%registered' => $registered,
    '%registerable' => $registerable,
    '%local' => $local,
    '!app' => $fb_app->label,
  ));
  return $summary;
}
function fb_register_now_form($form_state, $fb_app, $how_many) {

  //dpm(func_get_args(), "fb_register_now_form");

  // Display a summary.
  $form['summary'] = array(
    '#type' => 'markup',
    '#value' => _fb_register_summary($fb_app),
  );
  $form['how_many'] = array(
    '#type' => 'textfield',
    '#title' => 'Register (up to) how many local users?',
    '#default_value' => $how_many,
  );
  $form['fb_app_label'] = array(
    '#type' => 'value',
    '#value' => $fb_app->label,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Register Now'),
  );
  return $form;
}
function fb_register_now_form_submit($form, &$form_state) {
  $values = $form_state['values'];

  //dpm($values, "fb_register_now_form_submit");
  $fb_app = fb_get_app(array(
    'label' => $values['fb_app_label'],
  ));
  $success_count = _fb_register_register_users($fb_app, $values['how_many']);
  drupal_set_message(t('%count email addresses registered.', array(
    '%count' => $success_count,
  )));
}

/**
 * Register a chunk of users.
 */
function _fb_register_register_users($fb_app, $how_many) {

  // Use cache to keep track of how many users we've already registered.
  // Joining {users} and {fb_register} tables does not give us this
  // information, because we must register users for each application
  // hosted on this Drupal.
  $cache = cache_get('fb_register_cache');
  if (!$cache) {
    $cache = new stdClass();
    $cache->data = array(
      $fb_app->apikey => 0,
    );
  }
  $last_uid_registered = $cache->data[$fb_app->apikey];

  // Initialize facebook api for this app.
  $fb = fb_api_init($fb_app);
  $result = db_query("SELECT u.uid, u.name, u.mail, fbr.email_hash FROM {users} u LEFT JOIN {fb_register} fbr ON fbr.uid=u.uid WHERE (fbr.uid IS NULL OR u.uid > %d) AND u.mail IS NOT NULL AND u.mail <> '' ORDER BY u.uid LIMIT %d, %d", $last_uid_registered, 0, $how_many);
  while ($account = db_fetch_object($result)) {
    $register_data[] = array(
      'email_hash' => fb_register_email_hash($account->mail),
      'account_url' => url('user/' . $account->uid, array(
        'absolute' => TRUE,
      )),
    );
    $user_data[] = $account;
  }
  $success_count = 0;
  $error_count = 0;
  if (isset($register_data) && count($register_data)) {
    try {
      $success_data = $fb->api_client
        ->connect_registerUsers(json_encode($register_data));

      // Check results
      foreach ($register_data as $i => $data) {
        $account = $user_data[$i];
        if (isset($success_data[$i]) && $success_data[$i] == $data['email_hash']) {

          // Success
          db_query("DELETE FROM {fb_register} WHERE uid=%d", $account->uid);
          db_query("INSERT INTO {fb_register} (uid, email_hash) VALUES (%d, '%s')", $account->uid, $data['email_hash']);
          $success_count++;
          $last_uid_registered = max($last_uid_registered, $account->uid);
        }
        else {

          // Failure
          if (fb_verbose()) {
            watchdog('fb_register', '%application failed to register email hash %hash for user %uid (%name)', array(
              '%application' => $fb_app->title,
              '%hash' => $data['email_hash'],
              '%uid' => $account->uid,
              '%name' => $account->name,
            ), WATCHDOG_ERROR);
          }
          $error_count++;

          // Make certain we try this user again next time.
          db_query("DELETE FROM {fb_register} WHERE uid=%d", $account->uid);
        }
      }

      // debugging

      //dpm($register_data, "register data");

      //dpm($success_data, "success data");
    } catch (Exception $e) {
      fb_log_exception($e, t('Failed to register users via connect_registerUsers'));
      $error_count++;
    }
    fb_report_errors($fb, t('Failed to register email hash'));
  }
  else {
    drupal_set_message(t('Found no users to register.'));
  }

  // Remember any progress we've made.
  $cache->data[$fb_app->apikey] = $last_uid_registered;
  cache_set('fb_register_cache', $cache->data, 'cache', CACHE_PERMANENT);
  if ($error_count > 0) {
    return -1 * $error_count;
  }
  else {
    return $success_count;
  }
}

/**
 * Implementation of hook_form_alter().
 * 
 * Add our settings to each Facebook Application form.
 */
function fb_register_form_alter(&$form, &$form_state, $form_id) {

  // Add our settings to the fb_app edit form.
  if (isset($form['fb_app_data'])) {
    $fb_app = $form['#fb_app'];
    $fb_app_data = fb_get_app_data($fb_app);
    $fb_register_data = isset($fb_app_data['fb_register']) ? $fb_app_data['fb_register'] : array(
      'register_users' => FALSE,
    );
    $form['fb_app_data']['fb_register'] = array(
      '#type' => 'fieldset',
      '#title' => t('Register Users'),
      '#tree' => TRUE,
      '#collapsible' => TRUE,
      '#collapsed' => TRUE,
    );
    $form['fb_app_data']['fb_register']['register_users'] = array(
      '#type' => 'checkbox',
      '#title' => t('Register Local Users on Facebook'),
      '#description' => t('Use <a target=_blank href=http://wiki.developers.facebook.com/index.php/Connect.registerUsers>Connect.registerUsers</a> to help map local accounts to Facebook accounts.  This will share information about your users with Facebook.'),
      '#default_value' => $fb_register_data['register_users'],
    );
  }
}

/**
 * Implementation of hook_cron().
 *
 * Register users with Facebook during cron jobs.
 */
function fb_register_cron() {
  $all_apps = fb_get_all_apps();

  // All applications
  foreach ($all_apps as $fb_app) {
    $fb_app_data = fb_get_app_data($fb_app);
    $fb_register_data = isset($fb_app_data['fb_register']) ? $fb_app_data['fb_register'] : array(
      'register_users' => FALSE,
    );
    if (is_array($fb_register_data) && $fb_register_data['register_users']) {

      // This app has registration enabled.
      $count = _fb_register_register_users($fb_app, variable_get('fb_register_limit', 100));
      if ($count < 0) {

        // errors
        watchdog('fb_register', "Errors incountered while registering users for Facebook App %app", array(
          '%app' => $fb_app->label,
        ), WATCHDOG_ERROR);
      }
      elseif ($count > 0) {
        watchdog('fb_register', "Registered %count users for Facebook App !app", array(
          '%count' => $count,
          '%app' => $fb_app->label,
        ));
      }
    }
  }
}

/**
 * Implementation of hook_user().
 * 
 * Register users whenever an email address may have changed.
 */
function fb_register_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'delete') {
    db_query("DELETE FROM {fb_register} WHERE uid = %d", $account->uid);
  }
  elseif ($op == 'insert' || $op == 'update') {

    // Re-register on update, as mail address may have changed.
    // Register on insert is optional.
    if ($edit['mail']) {
      $hash = fb_register_email_hash($edit['mail']);
      $register_data = array(
        array(
          'email_hash' => $hash,
          'account_url' => url('user/' . $account->uid, array(
            'absolute' => TRUE,
          )),
        ),
      );
      db_query("DELETE FROM {fb_register} WHERE uid = %d", $account->uid);
      db_query("INSERT INTO {fb_register} (uid, email_hash) VALUES (%d, '%s')", $account->uid, $hash);
      $apps = fb_get_all_apps();
      foreach ($apps as $app) {
        $fb_app_data = fb_get_app_data($app);
        $fb_register_data = isset($fb_app_data['fb_register']) ? $fb_app_data['fb_register'] : array();
        if (is_array($fb_register_data) && isset($fb_register_data['register_users']) && $fb_register_data['register_users']) {
          $fb = fb_api_init($app);
          $success_data = $fb->api_client
            ->connect_registerUsers(json_encode($register_data));
          if (fb_verbose()) {
            watchdog('fb_register', '%application sent email hash for !user.  Facebook returned %num_returned successfully registered.', array(
              '%application' => $app->title,
              '!user' => l($edit['name'], 'user/' . $account->uid),
              '%num_returned' => count($success_data),
            ));
          }
        }
      }
    }
  }
}

/**
 * Implementation of hook_fb().  Here we customize the behavior of
 * Drupal for Facebook.
 *
 * Here we detect whether the user has previously been registered.  If
 * so, we map the facebook account to our local account.
 */
function fb_register_fb($op, $data, &$return) {
  if ($op == FB_APP_OP_EVENT) {
    if ($data['event_type'] == FB_APP_EVENT_POST_AUTHORIZE) {

      // User has authorized the application.
      $fbu = fb_facebook_user();
      $info = fb_users_getInfo(array(
        $fbu,
      ));
      if (is_array($info[0]['email_hashes'])) {
        $result = db_query("SELECT * FROM {fb_register} WHERE email_hash IN (" . db_placeholders($info[0]['email_hashes'], 'varchar') . ")", $info[0]['email_hashes']);
        if ($d = db_fetch_object($result)) {

          // We found a mapping to a local user.
          $account = user_load(array(
            'uid' => $d->uid,
          ));
          list($module, $authname) = _fb_user_get_authmap($data['fb_app'], $fbu);
          user_set_authmaps($account, array(
            $module => $authname,
          ));
          if (fb_verbose()) {
            watchdog('fb_register', 'Mapping facebook %fbu to local account !user', array(
              '%fbu' => $fbu,
              '!user' => l($account->name, 'user/' . $account->uid),
            ));
          }
        }
      }
    }
  }
}

/**
 * Compute email hash as specified in http://wiki.developers.facebook.com/index.php/Connect.registerUsers
 */
function fb_register_email_hash($mail) {
  $mail = strtolower(trim($mail));
  $crc32 = sprintf('%u', crc32($mail));
  $md5 = md5($mail);
  $hash = $crc32 . '_' . $md5;
  return $hash;
}

Functions

Namesort descending Description
fb_register_all_users This callback will register a chunk of users, then redirect to itself to register another chunk. And so on, to quickly register all users of a site, rather than waiting for cron jobs to take care of it all.
fb_register_cron Implementation of hook_cron().
fb_register_detail_page
fb_register_email_hash Compute email hash as specified in http://wiki.developers.facebook.com/index.php/Connect.registerUsers
fb_register_fb Implementation of hook_fb(). Here we customize the behavior of Drupal for Facebook.
fb_register_form_alter Implementation of hook_form_alter().
fb_register_menu Implementation of hook_menu().
fb_register_now_form
fb_register_now_form_submit
fb_register_user Implementation of hook_user().
_fb_register_register_users Register a chunk of users.
_fb_register_summary TODO: This should probably be a theme function.