You are here

fb_connect.module in Drupal for Facebook 5.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.
 */
function fb_connect_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'fb_connect/receiver',
      'callback' => 'fb_connect_receiver',
      'type' => MENU_CALLBACK,
      'access' => TRUE,
    );
  }
  return $items;
}

/**
 * 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.
 */
function fb_connect_receiver() {
  $output = '
<!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" xmlns:fb="http://www.facebook.com/2008/fbml">
<body>
  <!-- http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel -->
  <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js" type="text/javascript"></script>
</body>
</html>
';
  print $output;
  exit;
  die;
}

/**
 * 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 ($fb = fb_api_init($fb_app, FB_FBU_CURRENT)) {
    return $fb;
  }
}

/**
 * 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 ($apikey = variable_get('fb_connect_primary_apikey', NULL)) {
      $return = fb_get_app(array(
        'apikey' => $apikey,
      ));
    }
  }
  else {
    if ($op == FB_OP_POST_INIT) {
      if ($apikey = variable_get('fb_connect_primary_apikey', NULL)) {
        if ($data['fb_app']->apikey == $apikey) {

          // Init Facebook javascript whenever logged into fbConnect
          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);
        }
      }
    }
  }
}

/**
 * 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') {
    if (isset($GLOBALS['fb_connect_apikey'])) {
      global $fb, $fb_app;
      try {
        if ($fb && $fb->api_client->session_key) {

          // We will log out of facebook in hook_exit.
          $GLOBALS['fb_connect_logging_out'] = TRUE;
        }
      } catch (Exception $e) {
        fb_log_exception($e, t('Failed to log out of fbConnect session'));
      }
    }
  }
}
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>', NULL, NULL, 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)) {
    $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 && !$fb_app) {
    return;
  }
  static $features;
  if (!$features) {
    $features = array();
  }
  if ($fb_app && !$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 && !$options[$fb_app->apikey]) {
    $options[$fb_app->apikey] = array();
  }
  if ($option) {
    $options[$fb_app->apikey][$option] = $value;
  }
  return $options;
}

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

  // Do nothing on FBML pages
  if (function_exists('fb_canvas_is_fbml') && fb_canvas_is_fbml()) {
    return;
  }
  global $base_path;
  $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="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" 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 callback URL.
      $receiver = fb_get_callback_url($fb_app) . "fb_connect/receiver";
      $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.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;
}
function _fb_connect_block_login_defaults() {
  return array(
    'anon_not_connected' => array(
      'title' => t('Facebook Connect'),
      'body' => t('Facebook users login here. !button', array(
        '!button' => "<fb:login-button onclick='fb_connect_login_onclick();'></fb:login-button>",
      )),
    ),
    'user_not_connected' => array(
      'title' => t('Facebook Connect'),
      'body' => t('Link your account with Facebook. !button', array(
        '!button' => "<fb:login-button onclick='fb_connect_login_onclick();'></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;
  }
  else {
    if ($op == 'configure') {
      $defaults = variable_get('fb_connect_block_' . $delta, _fb_connect_block_login_defaults());
      $form['config'] = array(
        '#tree' => TRUE,
      );
      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']['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']['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.');
      $form['config']['connected']['body']['#description'] .= t('Note that <em>!fbu</em> will be replaced with the user\'s facebook id.');
      $form['config']['filter'] = filter_form($defaults['filter']);
      $form['config']['filter']['#description'] .= t('Format selected will apply to all body fields above.  Be sure to select a format which allows FBML tags!');
      $form['config']['filter']['#collapsed'] = FALSE;
      return $form;
    }
    else {
      if ($op == 'save') {
        variable_set('fb_connect_block_' . $delta, $edit['config']);
      }
      else {
        if ($op == 'view') {
          if (strpos($delta, 'login_') === 0) {

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

            // length of 'login_'
            $fb_app = fb_get_app(array(
              'label' => $label,
            ));
            $fb = fb_connect_app_init($fb_app);
            $fbu = $fb
              ->get_loggedin_user();
            fb_connect_require_feature('XFBML', $fb_app);

            //fb_connect_init_option('reloadIfSessionStateChanged', TRUE, $fb_app);

            //fb_connect_init_option('doNotUseCachedConnectState', TRUE, $fb_app);
            $base = drupal_get_path('module', 'fb_connect');
            drupal_add_js(array(
              'fb_connect' => array(
                'fbu' => $fbu ? $fbu : 0,
                // XXX
                'logout_url' => url('logout'),
                // XXX
                'front_url' => url('<front>'),
                'enable_login' => TRUE,
              ),
            ), 'setting');
            drupal_add_js($base . '/fb_connect.js');
            $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);
            }
            else {
              if ($GLOBALS['user']->uid) {
                $subject = $defaults['user_not_connected']['title'];
                $content = $defaults['user_not_connected']['body'];
              }
              else {
                $subject = $defaults['anon_not_connected']['title'];
                $content = $defaults['anon_not_connected']['body'];
              }
            }

            // If user has changed defaults, run filter
            if ($defaults['filter']) {
              $subject = check_plain($subject);
              $content = check_markup($content, $default['filter'], FALSE);
            }
            $block = array(
              'subject' => $subject,
              'content' => $content,
            );
            return $block;
          }
        }
      }
    }
  }
}
function fb_connect_form_alter($form_id, &$form) {

  // Add our settings to the fb_app edit form.
  if (is_array($form['fb_app_data'])) {
    $node = $form['#node'];
    $fb_app_data = fb_app_get_data($node->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->nid ? TRUE : FALSE,
    );
    $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' => $fb_connect_data['primary'],
    );
    if ($primary_apikey = variable_get('fb_connect_primary_apikey', NULL)) {
      if ($primary_apikey != $node->fb_app->apikey) {
        $primary = fb_get_app(array(
          'apikey' => $primary_apikey,
        ));
        $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 ? $primary->title : $primary_apikey,
        ));
      }
    }
  }
}
function fb_connect_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  if (($op == 'insert' || $op == 'update') && $node->type == 'fb_app') {

    //dpm(func_get_args(), "fb_connect_nodeapi($op)"); // debug
    if ($node->fb_app_data['fb_connect']['primary']) {
      variable_set('fb_connect_primary_apikey', $node->fb_app['apikey']);
      drupal_set_message(t('!node is now the primary Facebook Connect application.', array(
        '!node' => l($node->title, 'node/' . $node->nid),
      )));
    }
  }
}

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_block Implementation of hook_block.
fb_connect_enabled_apps Which apps are fbConnect enabled?
fb_connect_exit
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_init_option Add an option when initializing facebook's javascript api.
fb_connect_menu @file Support for Facebook Connect features
fb_connect_nodeapi
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_user Implementation of hook_user
_fb_connect_block_login_defaults