You are here

fb_connect.module in Drupal for Facebook 6.2

Support for Facebook Connect features

Note that Facebook connect will work properly only with themes that are Facebook Connect aware.

File

fb_connect.module
View source
<?php

/**
 * @file
 * Support for Facebook Connect features
 * 
 * Note that Facebook connect will work properly only with themes that are
 * Facebook Connect aware.
 */

// Drupal variables
define('FB_CONNECT_VAR_PRIMARY', 'fb_connect_primary_label');
define('FB_CONNECT_VAR_LOGIN', 'fb_connect_login_label');
define('FB_CONNECT_VAR_THEME_USERNAME_1', 'fb_connect_theme_username_1');
define('FB_CONNECT_VAR_THEME_USERNAME_2', 'fb_connect_theme_username_2');
define('FB_CONNECT_VAR_THEME_USERPIC_1', 'fb_connect_theme_userpic_1');
define('FB_CONNECT_VAR_THEME_USERPIC_2', 'fb_connect_theme_userpic_2');
define('FB_CONNECT_PATH_SESSION_START', 'fb_connect/session_start');
define('FB_CONNECT_PATH_SESSION_END', 'fb_connect/session_end');

/**
 * Implementation of hook_menu().
 */
function fb_connect_menu() {
  $items = array();

  // Cross-domain receiver.
  // XXX going away!  use fb_receiver.html instead.  This is left in to make the upgrade path easier, but it willgo away before 2.0 release.
  $items['fb_connect/receiver'] = array(
    'page callback' => 'fb_connect_receiver',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );

  // Ajax helper
  $items['fb_connect/js'] = array(
    'page callback' => 'fb_connect_js_cb',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );

  // Session and authmap helpers.
  $items[FB_CONNECT_PATH_SESSION_START] = array(
    'page callback' => 'fb_connect_session_start_cb',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );
  $items[FB_CONNECT_PATH_SESSION_END] = array(
    'page callback' => 'fb_connect_session_end_cb',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );

  // Admin pages
  $items[FB_PATH_ADMIN . '/fb_connect'] = array(
    'title' => 'Facebook Connect',
    'description' => 'Configure Facebook Connect',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'fb_connect_admin_settings',
    ),
    'access arguments' => array(
      FB_PERM_ADMINISTER,
    ),
    'file' => 'fb_connect.admin.inc',
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Session start callback.
 *
 * This can be invoked when a user is logging into facebook connect.  If the
 * user was previously logged into Drupal.  An authmap entry will be made
 * linking the two accounts.
 */
function fb_connect_session_start_cb() {
  global $_fb, $_fb_app;
  global $user;
  if (fb_verbose() == 'extreme') {
    watchdog('fb_connect', t("fb_connect_session_start_cb, session_name is " . session_name() . ", session_id is " . session_id()));
  }

  //watchdog('fbc_authmap_cb', dprint_r($_REQUEST, 1)); // debug
  if ($_fb_app->apikey == $_REQUEST['apikey']) {

    // http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
    if ($fbu = $_fb
      ->get_loggedin_user()) {
      if (function_exists('_fb_user_set_authmap')) {
        _fb_user_set_authmap($_fb_app, $fbu, $user);
      }
    }
  }
  session_destroy();

  // Destroy local drupal session, not session controlled by facebook cookies.
  $user = drupal_anonymous_user();
  drupal_json(TRUE);
  exit;
}
function fb_connect_session_end_cb() {
  global $user;
  if (fb_verbose() == 'extreme') {
    watchdog('fb_connect', t("fb_connect_session_end_cb, session_name is " . session_name() . ", session_id is " . session_id()));
  }
  session_destroy();

  // When user logs out of connect, give them a new session.
  $user = drupal_anonymous_user();
  drupal_json(TRUE);
  exit;
}

/**
 * Ajax javascript callback.
 *
 * For sites which use ajax, various events may create javascript which is
 * normally embedded in a page.  For example, posting to a user's wall.  When
 * ajax is used instead of a page reload, this callback will provide any
 * javascript which should be run.
 */
function fb_connect_js_cb() {
  $extra = fb_invoke(FB_OP_CONNECT_JS_INIT, NULL, array());
  $extra_js = implode("\n", $extra);
  print $extra_js;
  exit;
}

/**
 * Without a receiver file, cross-domain javascript will not work.
 *
 * In their infinite wisdom, facebook has decreed that the URL for
 * this static page be in the same place as the app's callback URL.
 * So we have to make a Drupal callback for what would otherwise be a
 * simple file.
 *
 * Supports SSL, http://wiki.developers.facebook.com/index.php/Facebook_Connect_Via_SSL.
 */
function fb_connect_receiver() {

  // DEPRECATED and will go away!
  // facebook_config global comes from facebook's API.
  $src_suffix = $GLOBALS['facebook_config']['debug'] ? '.debug' : '';
  $src = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && !variable_get('fb_force_no_ssl', FALSE) ? "https://ssl.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver{$src_suffix}.js" : "http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver{$src_suffix}.js";
  $output = <<<HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<body>
  <!-- Drupal for Facebook cross-domain receiver. -->
  <!-- http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel -->
  <script src="{<span class="php-variable">$src</span>}" type="text/javascript"></script>
</body>
</html>
HTML;
  print $output;
  die;

  // prevent Drupal from writing anything else.
}

/**
 * Prepare for fbConnect use.  Because a single Drupal might support
 * multiple apps, we don't know in advance which is the fbConnect app.
 * Theoretically, it might be possible to simultaneously use multiple
 * apps and fbConnect, but my suspicion is facebook would throw a
 * total hissy-fit if you tried.
 */
function fb_connect_app_init($fb_app) {
  if (isset($GLOBALS['_fb_app']) && fb_is_iframe_canvas() && $GLOBALS['_fb_app']->apikey != $fb_app->apikey) {

    // If we're in an iframe, only support connect for the iframe app.
    return;
  }
  if ($fb = fb_api_init($fb_app, FB_FBU_CURRENT)) {
    $fbu = $fb
      ->get_loggedin_user();

    // TODO: sometimes this method indicates we are logged in when we really are not.  Find a way to verify.
    if ($fbu && (!isset($GLOBALS['_fb_app']) || $GLOBALS['_fb_app']->apikey != $fb_app->apikey)) {

      // The user has authorized the app and we now know something about them.  Use a hook to trigger the actions of other modules.
      fb_invoke(FB_OP_APP_IS_AUTHORIZED, array(
        'fbu' => $fbu,
        'fb_app' => $fb_app,
        'fb' => $fb,
      ));
    }

    // Remember which app we've initialized.
    _fb_connect_set_app($fb_app);
  }
  return $fb;
}

/**
 * Helper function for other modules to know page is connected.
 * 
 * Note that this may return data on connect pages and in iframe apps
 * (depending on how iframe is configured).
 */
function fb_connect_get_app() {
  return _fb_connect_set_app();
}
function _fb_connect_set_app($fb_app = NULL) {
  static $cache;
  if (isset($fb_app)) {
    $cache = $fb_app;
  }
  return $cache;
}

/**
 * Which apps are fbConnect enabled?
 */
function fb_connect_enabled_apps() {

  // We do a bit of work for each enabled app, so really we want to restrict this list to only apps which have been "turned on".
  // But for now we're lazy and just get the list of all apps.
  $apps = fb_get_all_apps();
  return $apps;
}

/**
 * Implementation of hook_fb().
 */
function fb_connect_fb($op, $data, &$return) {

  //dpm(func_get_args(), "fb_connect_fb($op)");
  if ($op == FB_OP_CURRENT_APP && !$return) {

    // This will cause fb.module to set the global $_fb when user is logged in via fbConnect.
    if ($label = variable_get(FB_CONNECT_VAR_PRIMARY, NULL)) {
      $return = fb_get_app(array(
        'label' => $label,
      ));
    }
  }
  elseif ($op == FB_OP_POST_INIT) {
    if ($label = variable_get(FB_CONNECT_VAR_PRIMARY, NULL)) {
      if ($data['fb_app']->label == $label && !fb_is_fbml_canvas()) {
        $fb_app = $data['fb_app'];

        // Init Facebook javascript for primary app
        fb_connect_require_feature('XFBML', $fb_app);

        // fb_connect_init_option('reloadIfSessionStateChanged', TRUE, $fb_app);
        fb_connect_init_option('ifUserConnected', "{FB_Connect.on_connected}", $fb_app);
        fb_connect_init_option('ifUserNotConnected', "{FB_Connect.on_not_connected}", $fb_app);
      }
    }

    // Include our admin hooks.
    if (fb_is_fb_admin_page()) {
      module_load_include('inc', 'fb_connect', 'fb_connect.admin');
    }
  }
  elseif ($op == FB_OP_CONNECT_JS_INIT) {
    foreach (fb_connect_init_js() as $js) {
      $return[] = $js;
    }
  }
}

/**
 * This wrapper function around drupal_add_js() ensures that our
 * settings are added once and only once when needed.
 */
function _fb_connect_add_js() {
  static $just_once;
  if (!isset($just_once)) {
    if ($uid = $GLOBALS['user']->uid && module_exists('fb_user')) {
      $session_start_url = url(FB_CONNECT_PATH_SESSION_START, array(
        'absolute' => TRUE,
      ));
    }
    drupal_add_js(array(
      'fb_connect' => array(
        'front_url' => url('<front>'),
        'fbu' => fb_facebook_user(),
        'uid' => $uid,
        'in_iframe' => fb_is_iframe_canvas(),
        'session_start_url' => $session_start_url,
        'session_end_url' => url(FB_CONNECT_PATH_SESSION_END, array(
          'absolute' => TRUE,
        )),
      ),
    ), 'setting');
    drupal_add_js(drupal_get_path('module', 'fb_connect') . '/fb_connect.js');
    $just_once = TRUE;
  }
}

/**
 * Implementation of hook_user
 *
 * On logout, redirect the user so facebook can expire their session.
 * Should be a facebook API to do this, but there's none I know of.
 */
function fb_connect_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'logout') {

    // Affect connect pages, but not canvas pages.
    if (fb_is_fbml_canvas() || fb_is_iframe_canvas()) {
      return;
    }
    $apps = fb_connect_enabled_apps();
    foreach ($apps as $fb_app) {
      try {
        $fb = fb_api_init($fb_app, FB_FBU_CURRENT);
        if ($fb && $fb->api_client->session_key) {

          // Log out of facebook
          $session_key = $fb->api_client->session_key;

          // Figure out where to send the user.
          if ($_REQUEST['destination']) {
            $next = url($_REQUEST['destination'], array(
              'absolute' => TRUE,
            ));

            // It pains me to muck with $_REQUEST['destination'], but facebook's weak-ass API leaves us little choice.
            unset($_REQUEST['destination']);
          }
          else {
            $next = url('<front>', array(
              'absolute' => TRUE,
            ));
          }

          // http://forum.developers.facebook.com/viewtopic.php?id=21879
          // Use next parameter to expire session.
          drupal_goto("http://www.facebook.com/logout.php?app_key={$fb_app->apikey}&session_key={$session_key}&next=" . $next);
        }
      } catch (Exception $e) {
        fb_log_exception($e, t('Failed to log out of fbConnect session'));
      }
    }
  }
}

/**
 * Here we attempt to log the user out of facebook connect, not just Drupal.
 * If we fail, the user will be reconnected right away, because
 * connect cookies are still in place.
 */
function fb_connect_exit($url = NULL) {
  if (isset($GLOBALS['fb_connect_logging_out'])) {
    global $_fb;
    session_write_close();

    // drupal_goto calls this, so why not us?
    if (!isset($url)) {
      $url = url('<front>', array(
        'absolute' => TRUE,
      ));
    }
    $_fb
      ->logout($url);
  }
}

/**
 * Allows other modules to specify which Facebook Connect features are
 * required.  This will affect how the FB_RequireFeatures javascript method is
 * called.
 */
function fb_connect_require_feature($feature = NULL, $fb_app = NULL) {
  if ($feature && !isset($fb_app) && isset($GLOBALS['_fb_app'])) {
    $fb_app = $GLOBALS['_fb_app'];
  }

  // some features may apply without an app, but for now let's enforce that an app is required.
  if ($feature && !isset($fb_app)) {
    return;
  }
  static $features;
  if (!$features) {
    _fb_connect_add_js();

    // include our javascript.
    $features = array();
  }
  if ($fb_app && !isset($features[$fb_app->apikey])) {
    $features[$fb_app->apikey] = array(
      'fb_app' => $fb_app,
      'features' => array(),
    );
  }
  if ($feature) {
    $features[$fb_app->apikey]['features'][$feature] = $feature;
  }
  return $features;
}

/**
 * Add an option when initializing facebook's javascript api.
 */
function fb_connect_init_option($option = NULL, $value = NULL, $fb_app = NULL) {
  if ($option && !isset($fb_app)) {
    $fb_app = $GLOBALS['_fb_app'];
  }
  if ($option && !$fb_app) {
    return;
  }
  static $options;
  if (!$options) {
    $options = array();
  }
  if ($fb_app && !isset($options[$fb_app->apikey])) {
    $options[$fb_app->apikey] = array();
  }
  if ($option) {
    $options[$fb_app->apikey][$option] = $value;
  }
  return $options;
}

/**
 * Add javascript to a facebook connect page.
 *
 * Use this to add calls to facebook JS,
 * http://wiki.developers.facebook.com/index.php/JS_API_Index.
 * 
 * We use Drupal's cache to store the javascript until it is rendered
 * to a page.  This approach is analogous to drupal_set_message
 * storing data temporarily in the session.  We use cache instead of
 * session, because the session is not shared between Facebook's event
 * callbacks and regular page requests.
 *
 */
function fb_connect_init_js($js = NULL) {
  $fbu = fb_facebook_user();
  $fb_app = $GLOBALS['_fb_app'];
  $cid = 'fb_connect_init_js_' . $fb_app->apikey . '_' . $fbu;
  $cache = cache_get($cid, 'cache');
  if (!isset($cache->data)) {
    $cache = new stdClass();
    $cache->data = array();
  }
  if ($js) {
    $cache->data[] = $js;
    cache_set($cid, $cache->data, 'cache', time() + 60000);

    // Update cache
  }
  elseif ($js === NULL) {
    cache_clear_all($cid, 'cache');
  }
  return $cache->data;
}

/**
 * Include facebook javascript in the footer of the current page.
 */
function fb_connect_footer($is_front) {

  // Do nothing on FBML pages
  if (fb_is_fbml_canvas()) {
    return;
  }
  global $base_path;
  $fb_feature_src = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' && !variable_get('fb_force_no_ssl', FALSE) ? "https://ssl.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" : "http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php";
  $feature_data = fb_connect_require_feature();
  $option_data = fb_connect_init_option();
  if (count($feature_data)) {
    foreach ($feature_data as $data) {

      // Give other modules a chance to add javascript which executes after init.
      $extra = fb_invoke(FB_OP_CONNECT_JS_INIT, $data, array());
      $extra_js = implode("\n", $extra);
      $fb_app = $data['fb_app'];
      $features = $data['features'];
      $options = json_encode($option_data[$fb_app->apikey]);

      // Hack!  What's the way to json_encode a function name?
      $options = str_replace('"{', '', $options);
      $options = str_replace('}"', '', $options);

      // drupal_add_js cannot add external javascript, so we use hook_footer instead.
      $output = '<script src="' . $fb_feature_src . '" type="text/javascript"></script>';
      $output .= "\n";
      $feature_list = '["' . implode('","', $features) . '"]';

      // Put together the URL for the receiver.  The prefix must be identical to the apps connect URL.

      //$receiver = fb_connect_get_connect_url($fb_app) . "fb_connect/receiver"; // OLD WAY. XXX
      $receiver = variable_get('fb_xd_receiver', url(drupal_get_path('module', 'fb') . '/fb_receiver.html', array(
        'absolute' => TRUE,
      )));
      $output .= "\n<script type=\"text/javascript\">\n  \$(document).ready(function() {\n    FB_RequireFeatures({$feature_list}, function () {\n\n      //FB.FBDebug.logLevel = 4;\n      //FB.FBDebug.isEnabled = true;\n\n      FB.XFBML.Host.autoParseDomTree = false;\n\n      FB.init(\"{$fb_app->apikey}\", \"{$receiver}\", {$options});\n    });\n  });\n";

      // Extra JS after successful fbConnect init.
      $output .= "\n  FB.ensureInit(function()\n    {\n      FB_Connect.init();\n      {$extra_js}\n    });\n";
      $output .= "\n</script>\n";
    }
  }
  return $output;
}

/**
 * Convenience method to get an apps connect URL.
 *
 */
function fb_connect_get_connect_url($fb_app) {

  // absolute URL with no rewriting applied
  global $base_url;
  return $base_url . '/';
}
function _fb_connect_block_login_defaults() {
  return array(
    'anon_not_connected' => array(
      'title' => t('Facebook Connect'),
      'body' => t('<fb:login-button onclick="FB_Connect.login_onclick()" v="2"><fb:intl>Connect with Facebook</fb:intl></fb:login-button>'),
    ),
    'user_not_connected' => array(
      'title' => t('Facebook Connect'),
      'body' => t('<fb:login-button onclick="FB_Connect.login_onclick()" v="2"><fb:intl>Connect with Facebook</fb:intl></fb:login-button>'),
    ),
    'connected' => array(
      'title' => t('Facebook Connect'),
      'body' => "<fb:profile-pic uid=!fbu></fb:profile-pic><!--<fb:login-button onclick='FB_Connect.logout_onclick();' autologoutlink=true></fb:login-button>-->",
    ),
  );
}

/**
 * Implementation of hook_block.
 */
function fb_connect_block($op = 'list', $delta = 0, $edit = array()) {
  if ($op == 'list') {
    $items = array();
    foreach (fb_connect_enabled_apps() as $fb_app) {
      $d = 'login_' . $fb_app->label;
      $items[$d] = array(
        'info' => t('Facebook Connect Login to !app', array(
          '!app' => $fb_app->title,
        )),
      );
    }
    return $items;
  }
  elseif ($op == 'configure') {
    $orig_defaults = _fb_connect_block_login_defaults();
    $defaults = variable_get('fb_connect_block_' . $delta, $orig_defaults);
    $form['config'] = array(
      '#tree' => TRUE,
    );

    // Settings for each user status that we can detect.
    foreach (array(
      'anon_not_connected',
      'user_not_connected',
      'connected',
    ) as $key) {
      $form['config'][$key] = array(
        '#type' => 'fieldset',
        // title and description below
        '#collapsible' => TRUE,
        '#collapsed' => FALSE,
      );
      $form['config'][$key]['title'] = array(
        '#type' => 'textfield',
        '#title' => t('Default title'),
        //'#description' => t('Default title.'),
        '#default_value' => $defaults[$key]['title'],
      );
      $form['config'][$key]['body'] = array(
        '#type' => 'textarea',
        '#title' => t('Body'),
        //'#description' => t('Block body'),
        '#default_value' => $defaults[$key]['body'],
      );
    }
    $form['config']['anon_not_connected']['#title'] = t('Anonymous user, not connected');
    $form['config']['anon_not_connected']['#description'] = t('Settings when local user is Anonymous, and not connected to Facebook.  Typically a new account will be created when the user clicks the connect button.');
    $form['config']['anon_not_connected']['body']['#description'] = t('Suggestion: %default .', array(
      '%default' => $orig_defaults['anon_not_connected']['body'],
    ));
    $form['config']['user_not_connected']['#title'] = t('Registered user, not connected');
    $form['config']['user_not_connected']['#description'] = t('Settings when local user is registered, and not connected to Facebook.  Typically the facebook id will be linked to the local id after the user clicks the connect button.');
    $form['config']['user_not_connected']['body']['#description'] = t('Suggestion: %default .', array(
      '%default' => $orig_defaults['user_not_connected']['body'],
    ));
    $form['config']['connected']['#title'] = t('Connected user');
    $form['config']['connected']['#description'] = t('Settings when local user is connected to Facebook.  You may render facebook\'s logout button, and/or information about the user.  Consider using <a target="_blank" href="!xfbml_url">XFBML</a> such as &lt;fb:name uid=!fbu&gt;&lt;/fb:name&gt; or &lt;fb:profile-pic uid=!fbu&gt;&lt;/fb:profile-pic&gt;', array(
      'xfbml_url' => 'http://wiki.developers.facebook.com/index.php/XFBML',
    ));
    $form['config']['connected']['body']['#description'] .= t('Note that <strong>!fbu</strong> will be replaced with the user\'s facebook id.<br/>Suggestion: %default .', array(
      '%default' => $orig_defaults['connected']['body'],
    ));
    $form['config']['format'] = filter_form($defaults['format']);
    $form['config']['format']['#description'] .= t('<p><strong>Be sure to select a format which allows XFBML tags!</strong> (That is, use <em>Full HTML</em> or <em>PHP code</em>, rather than  <em>Filtered HTML</em>.)</p><p>Format selected will apply to all body fields above.</p>');
    $form['config']['format']['#collapsed'] = FALSE;
    return $form;
  }
  elseif ($op == 'save') {
    $edit['config']['format'] = $edit['format'];
    variable_set('fb_connect_block_' . $delta, $edit['config']);
  }
  elseif ($op == 'view') {
    if (strpos($delta, 'login_') === 0) {

      // Login block
      $label = substr($delta, 6);

      // length of 'login_'
      $fb_app = fb_get_app(array(
        'label' => $label,
      ));
      if ($fb = fb_connect_app_init($fb_app)) {
        $fbu = $fb
          ->get_loggedin_user();
        fb_connect_require_feature('XFBML', $fb_app);
        fb_connect_init_option('ifUserConnected', "{FB_Connect.on_connected}", $fb_app);
        fb_connect_init_option('ifUserNotConnected', "{FB_Connect.on_not_connected}", $fb_app);
        $base = drupal_get_path('module', 'fb_connect');
        $defaults = variable_get('fb_connect_block_' . $delta, _fb_connect_block_login_defaults());
        if ($fbu) {
          $subject = $defaults['connected']['title'];
          $content = $defaults['connected']['body'];

          // substitute %fbu
          $content = str_replace('!fbu', $fbu, $content);
        }
        elseif ($GLOBALS['user']->uid > 1) {
          $subject = $defaults['user_not_connected']['title'];
          $content = $defaults['user_not_connected']['body'];
        }
        elseif ($GLOBALS['user']->uid == 1) {
          $subject = $defaults['user_not_connected']['title'];
          $content = '<em>' . t('Facebook Connect login disabled for user #1.') . '</em>';
        }
        else {
          $subject = $defaults['anon_not_connected']['title'];
          $content = $defaults['anon_not_connected']['body'];
        }

        // If user has changed defaults, run filter
        if (isset($defaults['format'])) {
          $subject = check_plain($subject);
          $content = check_markup($content, $defaults['format'], FALSE);
        }
        $block = array(
          'subject' => $subject,
          'content' => $content,
        );
        return $block;
      }
    }
  }
}
function fb_connect_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_connect_data = $fb_app_data['fb_connect'];
    $form['fb_app_data']['fb_connect'] = array(
      '#type' => 'fieldset',
      '#title' => 'Facebook Connect',
      '#tree' => TRUE,
      '#collapsible' => TRUE,
      '#collapsed' => $node->label ? TRUE : FALSE,
    );

    // "Primary" will be initialized on every non-canvas page.
    $primary_label = variable_get(FB_CONNECT_VAR_PRIMARY, NULL);
    $form['fb_app_data']['fb_connect']['primary'] = array(
      '#type' => 'checkbox',
      '#title' => t('Primary'),
      '#description' => t('Initialize fbConnect javascript on all (non-canvas) pages.  If this site supports multiple Facebook Apps, this may be checked for at most one.'),
      '#default_value' => isset($fb_app->label) && $primary_label == $fb_app->label,
    );
    if ($primary_label && $primary_label != $fb_app->label) {
      $form['fb_app_data']['fb_connect']['primary']['#description'] .= '<br/>' . t('Note that checking this will replace %app as the primary Facebook Connect app.', array(
        '%app' => $primary_label,
      ));
    }
    $form['#submit'][] = 'fb_connect_app_submit';
  }

  // Add button to login form.
  if (($form_id == 'user_login_block' || $form_id == 'user_login') && !fb_is_fbml_canvas()) {
    if ($app_label = variable_get(FB_CONNECT_VAR_LOGIN, NULL)) {
      $fb_app = fb_get_app(array(
        'label' => $app_label,
      ));

      // Render connect link if not connected already.
      if ($fb = fb_connect_app_init($fb_app) && !fb_facebook_user($fb)) {

        // Ensure that facebook javascript is in the page closure.
        fb_connect_require_feature('XFBML', $fb_app);
        fb_connect_init_option('ifUserConnected', "{FB_Connect.on_connected}", $fb_app);
        fb_connect_init_option('ifUserNotConnected', "{FB_Connect.on_not_connected}", $fb_app);

        // Add button to the form.
        // @TODO - make markup customizable.
        $form['fb_connect_button'] = array(
          '#type' => 'markup',
          '#value' => '<fb:login-button v="2"><fb:intl>Connect with Facebook</fb:intl></fb:login-button>',
          '#prefix' => '<div class="form-item" id="fb-connect-button">',
          '#suffix' => '</div>',
          '#weight' => 4,
        );
      }
    }
    if ($form_id == 'user_login') {

      // On login, we must leave the user/login page or else get access denied.
      drupal_add_js(array(
        'fb_connect' => array(
          'destination' => url('user'),
        ),
      ), 'setting');
    }
  }
}
function fb_connect_app_submit($form, &$form_state) {
  $values = $form_state['values'];
  $label = $values['label'];
  $data = $values['fb_app_data']['fb_connect'];
  if ($data['primary']) {
    variable_set(FB_CONNECT_VAR_PRIMARY, $label);
    drupal_set_message(t('%label is the primary Facebook Connect application.', array(
      '%label' => $label,
    )));
  }
  elseif ($label == variable_get(FB_CONNECT_VAR_PRIMARY, NULL)) {

    // This app was the primary one, but the user has unchecked it.
    variable_set(FB_CONNECT_VAR_PRIMARY, NULL);
  }
}

/**
 * Implementation of hook_theme_registry_alter().
 *
 * Override theme functions for things that can be displayed using
 * XFBML.  Currently overriding username and user_picture.  We rename
 * the original entries, as we will use them for users without
 * javascript enabled.
 *
 * This hook is not well documented.  Who knows what its supposed to
 * return?  No doubt this will need updating with each new version of
 * Drupal.
 */
function fb_connect_theme_registry_alter(&$theme_registry) {

  // Ideally, we'd do this only on themes which will certainly be used for facebook connect pages.
  if (variable_get(FB_CONNECT_VAR_THEME_USERNAME_2, TRUE) || variable_get(FB_CONNECT_VAR_THEME_USERNAME_1, TRUE) && $theme_registry['username']['type'] == 'module') {

    // Re-register the original theme function under a new name.
    $theme_registry['fb_connect_username_orig'] = $theme_registry['username'];

    // Override theme username
    $theme_registry['username'] = array(
      'arguments' => array(
        'object' => NULL,
      ),
      'function' => 'fb_connect_theme_username_override',
      'type' => 'module',
    );
  }
  if (variable_get(FB_CONNECT_VAR_THEME_USERPIC_2, TRUE) || variable_get(FB_CONNECT_VAR_THEME_USERPIC_1, TRUE) && $theme_registry['user_picture']['type'] == 'module') {

    // Re-register the original theme function under a new name.
    $theme_registry['fb_connect_user_picture_orig'] = $theme_registry['user_picture'];

    // Override theme username
    $theme_registry['user_picture'] = array(
      'arguments' => array(
        'account' => NULL,
      ),
      'function' => 'fb_connect_theme_user_picture_override',
      'type' => 'module',
    );
  }
}

/**
 * Our replacement for theme('user_picture', ...)
 */
function fb_connect_theme_user_picture_override($account) {

  // Markup without fb_connect.
  $orig = theme('fb_connect_user_picture_orig', $account);

  // Make no changes to FBML pages.
  if (fb_is_fbml_canvas()) {
    return $orig;
  }

  // Respect Drupal's profile pic, if uploaded.
  if (isset($account->picture) && $account->picture) {
    return $orig;
  }
  if ($fbu = fb_get_object_fbu($account)) {
    $output = theme('fb_connect_fbml_user_picture', $fbu, $account, $orig);
  }
  else {
    $output = $orig;
  }
  return $output;
}

/**
 * Our replacement for theme('username', ...)
 */
function fb_connect_theme_username_override($object) {
  $orig = theme('fb_connect_username_orig', $object);

  // Make no changes to FBML pages.
  if (fb_is_fbml_canvas()) {
    return $orig;
  }
  elseif ($fbu = fb_get_object_fbu($object)) {

    // Theme the username with XFBML, using original username as backup.
    return theme('fb_connect_fbml_username', $fbu, $object, $orig);
  }
  else {
    return $orig;
  }
}

/**
 * Implements hook_theme().
 *
 * We use theme function for XFBML username and picture so that the
 * markup can be relatively easily customized.
 */
function fb_connect_theme() {
  return array(
    'fb_connect_fbml_username' => array(
      'arguments' => array(
        'fbu' => NULL,
        'object' => NULL,
        'orig' => NULL,
      ),
    ),
    'fb_connect_fbml_user_picture' => array(
      'arguments' => array(
        'fbu' => NULL,
        'account' => NULL,
        'orig' => NULL,
      ),
    ),
    'fb_connect_fbml_popup' => array(
      'arguments' => array(
        'elements' => NULL,
      ),
    ),
  );
}
function theme_fb_connect_fbml_username($fbu, $object, $orig) {
  if (!$fbu) {
    return $orig;
  }
  $wrap_pre = "<span class=fb_connect_hide>{$orig}</span><span class=fb_connect_show style='display:none'>";
  $wrap_post = "</span>\n";
  if ($object->uid && user_access('access user profiles')) {

    // Provide link if local account.
    $wrap_pre = $wrap_pre . '<a href="' . url('user/' . $object->uid) . '">';
    $wrap_post = '</a>' . $wrap_post;
    $ifcantsee = 'ifcantsee="' . addslashes(check_plain($object->name)) . '"';
  }
  $fbml = "<fb:name linked=false useyou=false uid=\"{$fbu}\" {$ifcantsee}></fb:name>";
  $output = $wrap_pre . $fbml . $wrap_post;
  return $output;
}
function theme_fb_connect_fbml_user_picture($fbu, $object, $orig) {
  if (!$fbu) {
    return $orig;
  }
  $fbml = "<fb:profile-pic linked=false uid=\"{$fbu}\"></fb:profile-pic>";
  $wrap_pre = '<span class=fb_connect_hide>' . $orig . '</span><span class="fb_connect_show" style="display:none;"><div class="picture">';
  $wrap_post = '</div></span>';
  if ($object->uid && user_access('access user profiles')) {

    // Provide link to local account.
    $wrap_pre = $wrap_pre . '<a href="' . url('user/' . $object->uid) . '">';
    $wrap_post = '</a>' . $wrap_post;
  }
  return $wrap_pre . $fbml . $wrap_post;
}
function theme_fb_connect_fbml_popup($elem) {

  // Hide this markup until javascript shows it.
  $t = '<div class="fb_connect_fbml_popup_wrap" style="display:none;" ' . ">\n";
  $t .= '<a href="#" title="' . check_plain($elem['#title']) . '" ' . (isset($elem['#link_attributes']) ? drupal_attributes($elem['#link_attributes']) : '') . '>' . check_plain($elem['#link_text']) . '</a>';
  $t .= '<div class="fb_connect_fbml_popup" ' . drupal_attributes($elem['#attributes']) . '>';
  $t .= $elem['#children'];
  $t .= "</div></div>\n";
  return $t;
}

Functions

Namesort descending Description
fb_connect_app_init Prepare for fbConnect use. Because a single Drupal might support multiple apps, we don't know in advance which is the fbConnect app. Theoretically, it might be possible to simultaneously use multiple apps and fbConnect, but my suspicion is…
fb_connect_app_submit
fb_connect_block Implementation of hook_block.
fb_connect_enabled_apps Which apps are fbConnect enabled?
fb_connect_exit Here we attempt to log the user out of facebook connect, not just Drupal. If we fail, the user will be reconnected right away, because connect cookies are still in place.
fb_connect_fb Implementation of hook_fb().
fb_connect_footer Include facebook javascript in the footer of the current page.
fb_connect_form_alter
fb_connect_get_app Helper function for other modules to know page is connected.
fb_connect_get_connect_url Convenience method to get an apps connect URL.
fb_connect_init_js Add javascript to a facebook connect page.
fb_connect_init_option Add an option when initializing facebook's javascript api.
fb_connect_js_cb Ajax javascript callback.
fb_connect_menu Implementation of hook_menu().
fb_connect_receiver Without a receiver file, cross-domain javascript will not work.
fb_connect_require_feature Allows other modules to specify which Facebook Connect features are required. This will affect how the FB_RequireFeatures javascript method is called.
fb_connect_session_end_cb
fb_connect_session_start_cb Session start callback.
fb_connect_theme Implements hook_theme().
fb_connect_theme_registry_alter Implementation of hook_theme_registry_alter().
fb_connect_theme_username_override Our replacement for theme('username', ...)
fb_connect_theme_user_picture_override Our replacement for theme('user_picture', ...)
fb_connect_user Implementation of hook_user
theme_fb_connect_fbml_popup
theme_fb_connect_fbml_username
theme_fb_connect_fbml_user_picture
_fb_connect_add_js This wrapper function around drupal_add_js() ensures that our settings are added once and only once when needed.
_fb_connect_block_login_defaults
_fb_connect_set_app

Constants