Skip to content
fb.module 33.1 KiB
Newer Older
// $Id$
/**
 * @file
 * This is the core required module of Drupal for Facebook.
 *
 * @see http://drupal.org/project/fb
 * 
 */

// hook_fb
// Paths.
define('FB_PATH_ADMIN', 'admin/build/fb');
define('FB_PATH_ADMIN_ARGS', 3); // how many args in path.
define('FB_PATH_ADMIN_APPS', 'admin/build/fb/app');
define('FB_PATH_ADMIN_APPS_ARGS', 4);
define('FB_PATH_AJAX_EVENT', 'fb/ajax');
define('FB_PATH_AJAX_EVENT_ARGS', 2);
// permissions
define('FB_PERM_ADMINISTER', 'administer fb apps');

define('FB_OP_GET_APP', 'get_app'); // Load data from a known app
define('FB_OP_GET_ALL_APPS', 'get_all_apps'); // Load data about all apps

define('FB_OP_CURRENT_APP', 'current_app'); // determine active app in canvas page or facebook connect
define('FB_OP_INITIALIZE', 'init'); // 
define('FB_OP_POST_INIT', 'post init'); // 

define('FB_OP_EXIT', 'exit'); // End an FB callback
define('FB_OP_GET_FBU', 'get_fbu'); // Query the local user's FB account
define('FB_OP_GET_UID', 'get_uid'); // Query the facebook user's local account
define('FB_OP_GET_USER_SESSION', 'get_user_sess');
define('FB_OP_PRE_USER', 'pre_user'); // Before account creation, fb_user.module
define('FB_OP_POST_USER', 'post_user'); // After account creation, fb_user.module
define('FB_OP_POST_EXTERNAL_LOGIN', 'post_external_login'); // user map has changed global user.
define('FB_OP_APP_IS_AUTHORIZED', 'app_authorized');  // Invoked if user has authorized an app.  Triggers creation of user accounts or fb_user entries
define('FB_OP_JS', 'fb_op_js');  // A chance to inject javascript onto the page.
define('FB_OP_AJAX_EVENT', 'fb_op_ajax'); // Notification of an event via ajax.
// node_access realms (belongs here?)
define('FB_GRANT_REALM_FRIEND', 'fb_friend');
define('FB_GRANT_REALM_GROUP', 'fb_group');

// NOTE: on Connect Pages, using anything other than FB_FBU_CURRENT will cause cookies to be set which cause problems on subsequent pages.  So only use something other than FB_FBU_CURRENT if you absolutely must!

// @TODO - new libs, are these FBU values still needed???
define('FB_FBU_CURRENT', 'fbu_current'); // Canvas pages and Connect pages
define('FB_FBU_ANY', 'fbu_any'); // Use current user on canvas page, fall back to infinite session otherwise.
//// Constants for internal use
define('FB_APP_CURRENT', '000_app_current'); // Canvas pages only.  000 makes it appear first in options list

 * Controls are one way to customize the behavior of Drupal for Facebook modules.
 *
 * Controls are stored as an array of flags.  Each flag overrides a
 * configurable or built-in behavior.  Third-party modules can use this to
 * provide exceptions to otherwise useful behavior.  For example see
 * fb_user.module, where this is used to suppress some behavior in off-line
 * mode.
 *
 * Controls take effect not just for the current page request, but also for
 * ajax callbacks generated by the subsequent page.
 *
 * Because ajax controls could be spoofed by a malicious client, flags should
 * not enable any "risky" features.  For example, fb_user.module provides a
 * control to suppress the creation of account, but not a control to enable
 * new accounts, as that would be a security risk.
 *
 */
function fb_controls($control = NULL, $value = NULL) {
  static $controls;
  if (!isset($controls)) {
    // Initialize.
    if (isset($_REQUEST['fb_controls'])) {
      // Comma separated list passed to ajax calls.
      foreach(explode(',', $_REQUEST['fb_controls']) as $key) {
        $controls[$key] = TRUE;
      }
    }
    else {
      $controls = array();
    }
    // @TODO - would a drupal_alter() be useful here?
  }
  if (isset($control)) {
    if ($value === FALSE) {
      unset($controls[$control]);
      return;
    }
    elseif ($value === TRUE)
      $controls[$control] = TRUE;
    
    return isset($controls[$control]) ? $controls[$control] : FALSE; // Return requested control.
  }
  return array_keys($controls); // Return all controls.
}

/**
 * Implements hook_init().
 *
 * Initializes facebook's javascript.
 * Determines whether we are servicing a Facebook App request.
 * 
 * We invoke our hook, first to determine which application is being invoked
 * (because we support more than one in the same Drupal instance).  We invoke
 * the hook again to let interested modules know the sdk is initialized.
  // Globals provided for internal use and convenience to third-parties.
  global $_fb;
  global $_fb_app;
  // fb_settings.inc may have been included in settings.php.  If not, included it now.
  if (!function_exists('fb_settings')) {
    module_load_include('inc', 'fb', 'fb_settings');
    // trigger test in fb_devel.module
    $GLOBALS['fb_init_no_settings'] = TRUE;
  // Values needed by fb.js.
  $js_settings = array(
    'ajax_event_url' => url(FB_PATH_AJAX_EVENT, array('absolute' => TRUE)),
    // @TODO - replace en_US with dynamically determined locale.
    'js_sdk_url' => variable_get('fb_js_sdk', 'http://connect.facebook.net/en_US/all.js'),
  );
Dave Cohen's avatar
Dave Cohen committed
  // Ask other modules for app details.
    // An App is configured.
    // Initialize javascript.
    $js_settings['apikey'] = $_fb_app->apikey;
    $js_settings['label'] = $_fb_app->label;
    $js_settings['page_type'] = fb_settings(FB_SETTINGS_TYPE); // canvas or connect.
    
    // Initialize the PHP API.
    $_fb = fb_api_init($_fb_app);
      // Give other modules a chance to initialize.
Dave Cohen's avatar
Dave Cohen committed
      fb_invoke(FB_OP_INITIALIZE, array(
      // See if the facebook user id is known
      if ($fbs = $_fb->getSession()) {
Dave Cohen's avatar
Dave Cohen committed
        fb_invoke(FB_OP_APP_IS_AUTHORIZED, array(
                    'fb_app' => $_fb_app,
                    'fb' => $_fb,
                    'fbu' => $_fb->getUser(),
                  ));
        $js_settings['fbu'] = $_fb->getUser();        
Dave Cohen's avatar
Dave Cohen committed
      else {
        // Add perms to settings, for calling FB.login().
        $perms = array();
        drupal_alter('fb_required_perms', $perms);
        $js_settings['perms'] = implode(',', $perms);
      }
      watchdog('fb', "URL indicates a facebook app, but could not initialize Facebook", array(), WATCHDOG_ERROR);
    if (fb_is_canvas()) {
      $js_settings['reload_url'] = 'http://apps.facebook.com/' . $_fb_app->canvas . '/' . fb_scrub_urls($_REQUEST['q']);
    }
    else {
      $js_settings['reload_url'] = url(isset($_GET['q']) ? $_GET['q'] : '<front>',
                                       array('absolute' => TRUE));
    }
  
  // Allow third-parties to act, even if we did not initialize $_fb.
  fb_invoke(FB_OP_POST_INIT, array('fb_app' => $_fb_app,
                                   'fb' => $_fb));
  
  $js_settings['controls'] = implode(',',fb_controls());
  // Add javascript to all pages.
  drupal_add_js(drupal_get_path('module', 'fb') . '/fb.js');
Dave Cohen's avatar
Dave Cohen committed
  // @TODO - move this to fb_preprocess_page().  See og_preprocess_page for example.
  drupal_add_js(array('fb' => $js_settings), 'setting');
 * Include and initialize Facebook's PHP SDK.
function fb_api_init($fb_app) {
  // This helps with uncaught exceptions.  However, it should be configurable
  // or at least not overwrite previously declared handler.
  set_exception_handler('fb_handle_exception');
  if (isset($cache[$fb_app->apikey])) {
    return $cache[$fb_app->apikey];
  }
  
  $filename = variable_get('fb_api_file', 'sites/all/libraries/facebook-php-sdk/src/facebook.php');
  if (!class_exists('Facebook') && !include($filename)) {
    $message = t('Failed to find the Facebook client libraries at %filename.  Read the !readme and follow the instructions carefully.', array(
                   '!drupal_for_facebook' => l(t('Drupal for Facebook'), 'http://drupal.org/project/fb'),
                   // This link should work with clean URLs disabled.
                   '!readme' => '<a href='. base_path() . '/' . drupal_get_path('module', 'fb') . '/README.txt>README.txt</a>',
                   '%filename' => $filename,
                 ));
    drupal_set_message($message, 'error');
    watchdog('fb', $message);
    return NULL;
  try {
    // We don't have a cached resource for this app, so we're going to create one.
    $fb = new Facebook(array(
                         'appId' => $fb_app->apikey,
                         'secret' => $fb_app->secret,
                         'cookie' => TRUE,
                       ));
    // Facebook certs are not valid.
    Facebook::$CURL_OPTS[CURLOPT_SSL_VERIFYPEER] = FALSE;
    Facebook::$CURL_OPTS[CURLOPT_SSL_VERIFYHOST] = FALSE;
    // Cache the result, in case we're called again.
    $cache[$fb_app->apikey] = $fb;
    
    return $fb;
  catch (Exception $e) {
    fb_log_exception($e, t('Failed to construct Facebook client API.'));
/**
 * Wrapper function for fb_api_init.  This helps for functions that should
 * work whether or not we are on a canvas page.  For canvas pages, the active
 * fb object is used.  For non-canvas pages, it will initialize the API using
 * an infinite session, if configured.
 * 
 * @param $fb_app Note this is ignored on canvas pages.
 * 
 * This is for internal use.  Third party modules use fb_api_init().
 */
function _fb_api_init($fb_app = NULL) {
  $fb = $GLOBALS['_fb']; // Default to active app on canvas pages
  if (!$fb && $fb_app)
    // Otherwise, log into facebook api.
    $fb = fb_api_init($fb_app, FB_FBU_ANY);
  
  if (!$fb) {
    watchdog('fb', '%function unable to initialize Facebook API.',
             array('%function' => '_fb_api_init()'), WATCHDOG_ERROR);
/**
 * Since facebook's php sdk is a joke, we have to implement the most basic
 * crap, like this.
 * 
 * @TODO - is this still needed? It's hard to know when their bugs are fixed.
 */
function fb_access_token($fb = NULL) {
  static $cache;
  if (!isset($fb))
    $fb = $GLOBALS['_fb'];

  $apikey = $fb->getAppId();

  if (!isset($cache))
    $cache = array();

  if (!isset($cache[$apikey])) {
    $path = "https://graph.facebook.com/oauth/access_token?client_id=" . $fb->getAppId() . "&client_secret=" . $fb->getApiSecret() . "&type=client_cred";
    $http = drupal_http_request($path);
    $data = explode('=', $http->data);
    $token = $data[1];
    if ($token)
      $cache[$apikey] = $token;
  }
  return $cache[$apikey];
}
/**
 * Facebook's own php-sdk is so friggin' buggy.  If you try $_fb->api(...) and
 * get invalid parameters exceptions, this may work instead.
 */
function fb_call_method($fb, $method, $params = array()) {
  $params['access_token'] = fb_access_token($fb);
  $params['api_key'] = $fb->getAppId();
  $params['format'] = 'json-strings';
Dave Cohen's avatar
Dave Cohen committed

  // Here's how to create a url that conforms to standards:
  $url = url("https://api.facebook.com/method/{$method}", array(
               'query' => $params,
             ));
Dave Cohen's avatar
Dave Cohen committed
  // If facebook gives errors like "Invalid OAuth 2.0 Access Token 190/Unable to get application prop" it might be necessary to uncomment the urldecode below.
  // http://forum.developers.facebook.net/viewtopic.php?id=76228
  // $url = rawurldecode($url);
  
  $http = drupal_http_request($url);

  if ($http->data) {
    $data = json_decode($http->data, TRUE);
    // Yes, it's double encoded.  At least sometimes.
    if (is_string($data)) {
      $data = json_decode($data, TRUE);
    }
    if (is_array($data)) {
      if (isset($data['error_code'])) {
        throw new FacebookApiException($data);      
      }
    }
    else {
      // Never reach this???
    }
    return $data;
/**
 * Helper function for fql queries.
 *
 * Use $params to pass a session_key, when needed.
 */
function fb_fql_query($fb, $query, $params = array()) {
  $params['query'] = $query;
  //$result = fb_call_method($fb, 'fql.query', $params);
  $params['method'] = 'fql.query';
  $result = $fb->api($params);
                       
/**
 * Implements hook_footer().
 */
function fb_footer($is_front) {
  global $_fb, $_fb_app;
  // Necessary?  All facebook examples seem to have it.
  $output = "<div id=\"fb-root\"></div>\n";

  $js_array = fb_invoke(FB_OP_JS, array('fb' => $GLOBALS['_fb'], 'fb_app' => $GLOBALS['_fb_app']), array());
  if (count($js_array)) {
    $output .= "<script type=\"text/javascript\">\n";
    // The function we define in the footer will be called after FB is initialized.
    $output .= "FB_JS.initHandler = function() {\n";
    $output .= implode("\n", $js_array);
    $output .= "};\n";
    $output .= "jQuery(document).bind('fb_init', FB_JS.initHandler);\n";
    $output .= "\n</script>\n";
  }
/**
 * Is the current request a canvas page?
 */
  if (fb_is_tab()) {
    return FALSE;
  }
  elseif (fb_settings(FB_SETTINGS_CB)) {
Dave Cohen's avatar
Dave Cohen committed
    // Using fb_url_rewrite.
    return TRUE;
  }
  elseif (fb_settings(FB_SETTINGS_TYPE) == FB_SETTINGS_TYPE_CANVAS) {
    // No rewrite, but fb_settings.inc has detected type.
    return TRUE;
  }
  return FALSE;
/**
 * Is the current page a profile tab.
 *
 * Only works when "Canvas Session Parameter" is disabled.
 */
function fb_is_tab() {
  global $_fb;
  // Old way, no migrations enabled.
  if (isset($_REQUEST['fb_sig_in_profile_tab']) &&
      $_REQUEST['fb_sig_in_profile_tab']) {
    return TRUE;
  }
  // signed request migration enabled.
  elseif ($_fb && ($sr = $_fb->getSignedRequest()) &&
          isset($sr['profile_id'])) {
    return TRUE;
  }
  return FALSE;
}


/**
 * Sometimes calls to fb_api_init succeed, but calls to the client api
 * will fail because cookies are obsolete or what have you.  This
 * function makes a call to facebook to test the session.  Expensive,
 * so use only when necessary.
 */
function fb_api_check_session($fb) {
  $success = FALSE;
  try {
    $session = $fb->getSession();
Dave Cohen's avatar
Dave Cohen committed
    // Newer API uses access_token
    if (isset($session['access_token'])) {
      $is_user = $fb->api(array(
                            'method' => 'users.isAppUser',
                          ));      
      // Does not matter what is returned, as long as exception is not thrown.
      $success = TRUE;
    }
    // Older API used session_key.  Still needed?
    elseif (isset($session['session_key'])) {
      $is_user = $fb->api(array(
                            'method' => 'users.isAppUser',
                          ));
      $success = TRUE;
  }
  catch (Exception $e) {
    if (fb_verbose()) {
      watchdog('fb', 'fb_api_check_session failed.  Possible attempt to spoof a facebook session!');
    }
Dave Cohen's avatar
Dave Cohen committed
    if (fb_verbose()) {
      fb_log_exception($e, t("fb_api_check_session failed."));
    }
 * Returns the facebook user id currently visiting a canvas page, or if
 * set_user has been called.  Unlike fb_get_fbu(), works only on canvas and
 * connect pages, or when infinite session has been initialized.
function fb_facebook_user($fb = NULL) {
  if (!isset($fb))
  try {
    $fbu = $fb->getUser();
    return $fbu;
  catch (FacebookApiException $e) {
    fb_log_exception($e,
                     t('Failed to get Facebook user id.  detail: !detail',
                       array('!detail' => print_r($e, 1))));

/**
 * Helper tells other modules when to load admin hooks.
 */
function fb_is_fb_admin_page() {
Dave Cohen's avatar
Dave Cohen committed
  if (arg(0) == 'admin' && (arg(1) == 'fb' || arg(2) == 'fb')) {
    // Keep consistant titles across tabs served by multiple modules.
    if ($label = arg(FB_PATH_ADMIN_APPS_ARGS))
      drupal_set_title($label);
    else
      drupal_set_title(t('Drupal for Facebook'));

    return TRUE;
  }
/**
 * Given a facebook user id, learn the local uid, if any.
 *
 */ 
function fb_get_uid($fbu, $fb_app = NULL) {
  $uid = NULL;
  if ($fbu) {
    $uid = fb_invoke(FB_OP_GET_UID, array('fbu' => $fbu, 'fb_app' => $fb_app));
  }
  return $uid;
}


/**
 * Given a local user id, find the facebook id.
 *
 * Invokes hook_fb(FB_OP_GET_FBU) in order to ask other modules what the fbu
 * is.  Typically, fb_user.module will answer the question.
function fb_get_fbu($uid, $fb_app = NULL) {
  // Accept either a user object or uid passed in.
  if (is_object($uid) && ($uid->uid) && 
      isset($uid->fbu) && $uid->fbu)
    return $uid->fbu;
    $uid = $uid->uid;
  if ($uid) {
    // User management is handled by another module.  Use our hook to ask for mapping.
    $fbu = fb_invoke(FB_OP_GET_FBU, array('uid' => $uid,
/**
 * Convenience function to learn the fbu associated with a user, node or comment.
 * Used in theming (X)FBML tags.
 */
function fb_get_object_fbu($object) {
  static $cache;
  if (!isset($cache))
    $cache = array();

  if (isset($object->uid) && isset($cache[$object->uid])) {
    $fbu = $cache[$object->uid];
    return $fbu;
  }
    // Explicitly set.
    $fbu = $object->fbu;
  }
  elseif (isset($object->init) &&
          ($pos = strpos($object->init, '@facebook'))) {
    // Naming convention used by fb_user when creating accounts.
    // $object->init may be present when object is a user.
    $fbu = substr($object->init, 0, $pos);
  }
  elseif ($pos = strpos($object->name, '@facebook')) {
    $fbu = substr($object->name, 0, $pos);
  }
    // This can be expensive on pages with many comments or nodes!
    $fbu = fb_get_fbu($object->uid);
  }
  
  if (isset($fbu) && is_numeric($fbu)) {
    if (isset($object->uid) && ($object->uid > 0)) {
      $cache[$object->uid] = $fbu;
    }
    return $fbu;
  }
}


 * Convenience method to get app info based on apikey or nid.
function fb_get_app($search_data) {
  // $search_data can be an apikey, or an array of other search params.
  if (!is_array($search_data))
    $search_data = array('apikey' => $search_data);
  
  $fb_app = fb_invoke(FB_OP_GET_APP, $search_data);
 * Convenience method for other modules to attach data to the fb_app
 * object.  
 * 
 * It is assumed the fb_app implementation will fill in the data
 * field.  We really should clean up the separation between modules,
 * or merge fb_app.module into this one.
function fb_get_app_data(&$fb_app) {
  if (!isset($fb_app->fb_app_data)) {
    $fb_app->fb_app_data = isset($fb_app->data) ? unserialize($fb_app->data) : array();
/**
 * Will return a human-readable name if the fb_app module supports it, or
 * fb_admin_get_app_properties($fb_app) has been called.  However we don't
 * take the relatively expensive step of calling that ourselves.
 */
function fb_get_app_title($fb_app) {
  if (isset($fb_app->title))
    return $fb_app->title;
  elseif (isset($fb_app->application_name)) {
    return $fb_app->application_name;
  }
  else {
    return $fb_app->label;
  }
}
/**
 * Convenience method to return array of all know fb_apps.
 */
function fb_get_all_apps() {
  $apps = fb_invoke(FB_OP_GET_ALL_APPS, NULL, array());
 * A convenience method for returning a list of facebook friends.  
 *
 * This should work efficiently in canvas pages for finding friends of
 * the current user.
 *
 * @TODO - also support users who have permitted offline access. 
 * 
 * @return: an array of facebook ids
 */
function fb_get_friends($fbu, $fb_app = NULL) {
  static $cache = array();
  // Facebook only allows us to query the current user's friends, so let's try
  // to log in as that user.  It will only actually work if they are the
  // current user of a canvas page, or they've signed up for an infinite
  // session.
  $fb = fb_api_init($fb_app, $fbu);
  if (!$fb || !$fbu)
    return;

  $items = array();
  if (!isset($cache[$fbu])) {
        $fbu == fb_facebook_user($fb)) {
      try {
        $items = fb_call_method($fb, 'friends.get', array(
                                  'uid' => $fbu,
                                ));
      }
      catch (Exception $e) {
        fb_log_exception($e, t('Failed call to friends.get'), $fb);
      }

Dave Cohen's avatar
Dave Cohen committed
    // friends_get does not work in cron call, so we double check. @TODO - still needed?
    if (!$items || !count($items)) {
      $logged_in = fb_facebook_user($fb);
      $query = "SELECT uid2 FROM friend WHERE uid1=$fbu"; // FQL, no {curly_brackets}!
      try {
        $result = fb_call_method($fb, 'fql.query', array(
                                   'query' => $query,
                                 ));
        //dpm($result, "FQL " . $query); // debug
      }
      catch (Exception $e) {
        fb_log_exception($e, t('Failed call to fql.query: !query', array('!query' => $query)), $fb);
      }
      if (is_array($result)) 
        foreach ($result as $data) {
          $items[] = $data['uid2'];
        }
    }
    // Facebook's API has the annoying habit of returning an item even if user
    // has no friends.  We need to clean that up.
    if (!$items[0])
      unset($items[0]);
    $cache[$fbu] = $items;
  }
  
  return $cache[$fbu];
}

// Return array of facebook gids
function fb_get_groups($fbu, $fb_app = NULL) {
  $items = array();
  $groups = fb_get_groups_data($fbu);

  if ($groups && count($groups))
    foreach ($groups as $data) {
      $items[] = $data['gid'];
    }
  return $items;
}

function fb_get_groups_data($fbu, $fb_app = NULL) {
  static $cache = array();
  if (!$fb || !$fbu)
    return;
  
  if (!isset($cache[$fbu])) {
Dave Cohen's avatar
Dave Cohen committed
    $cache[$fbu] = fb_call_method($fb, 'groups.get', array(
function fb_menu() {
  $items[FB_PATH_ADMIN] = array(
    'title' => 'Facebook Applications',
    'description' => 'Facebook Applications',
    'page callback' => 'fb_admin_page',
    'access arguments' => array(FB_PERM_ADMINISTER),
    'file' => 'fb.admin.inc',
    'type' => MENU_NORMAL_ITEM,
  $items[FB_PATH_ADMIN . '/list'] = array(
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
Dave Cohen's avatar
Dave Cohen committed
  // Admin pages for each app.
  $items[FB_PATH_ADMIN_APPS . '/%fb'] = array(
    'title' => 'Application Detail',
    'description' => 'Facebook Applications',
    'page callback' => 'fb_admin_app_page',
    'page arguments' => array(FB_PATH_ADMIN_APPS_ARGS),
    'access arguments' => array(FB_PERM_ADMINISTER),
    'file' => 'fb.admin.inc',
    'type' => MENU_CALLBACK,
  );

  $items[FB_PATH_ADMIN_APPS .'/%fb/fb'] = array(
    'title' => 'View',
    'weight' => -2,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
Dave Cohen's avatar
Dave Cohen committed
  $items[FB_PATH_ADMIN_APPS . '/%fb/fb/set_props'] = array(
    'title' => 'Set Properties',
    'description' => 'Set Facebook Application Properties',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('fb_admin_set_properties_form', FB_PATH_ADMIN_APPS_ARGS),
    'access arguments' => array(FB_PERM_ADMINISTER),
    'type' => MENU_CALLBACK,
  );

  // Javascript helper
  $items['fb/js'] = array(
    'page callback' => 'fb_js_cb',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );

  // Ajax event handler.
  $items[FB_PATH_AJAX_EVENT . '/%'] = array(
    'page callback' => 'fb_ajax_event',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
    'page arguments' => array(FB_PATH_AJAX_EVENT_ARGS),
  );
/**
 * Implementation of a %wildcard_load(). http://drupal.org/node/224170
Dave Cohen's avatar
Dave Cohen committed
 *
 * Seems to get called a lot(!) so we cache.
Dave Cohen's avatar
Dave Cohen committed
  static $cache;
  if (!isset($cache))
    $cache = array();
  if (!isset($cache[$id])) {
    $query = array('label' => $id);
    if (fb_is_fb_admin_page()) {
      // Show disabled apps to admins.
      $query['status'] = 0; // status >= 0
    }
    $cache[$id] = fb_get_app($query);
Dave Cohen's avatar
Dave Cohen committed
  return $cache[$id];
/**
 * Implementation of hook_perm().
 */
function fb_perm() {
Dave Cohen's avatar
Dave Cohen committed
  return array(FB_PERM_ADMINISTER);
 * Implements hook_exit().
 *
 * When completing a canvas page we need special processing for the session.  See fb_session.inc.
 *
 * Also invoke hook_fb(FB_OP_EXIT), so that other modules can handle special
 * cases (in particular form support in b_canvas.module.
 */
function fb_exit($destination = NULL) {
    fb_invoke(FB_OP_EXIT, array('fb_app' => $_fb_app,
                                'fb' => $GLOBALS['_fb']),
function fb_invoke($op, $data = NULL, $return = NULL, $hook = FB_HOOK) {
  foreach (module_implements($hook) as $name) {
    $function = $name . '_' . $hook;
    try {
      $function($op, $data, $return);
    }
    catch (Exception $e) {
Dave Cohen's avatar
Dave Cohen committed
      if (isset($data['fb_app'])) {
        fb_log_exception($e, t('Exception calling %function(%op) (!app)', array(
                                 '%function' => $function,
                                 '%op' => $op,
                                 '%label' => $data['fb_app']->label,
                                 '%apikey' => $data['fb_app']->apikey,
                                 '!app' => l($data['fb_app']->label, FB_PATH_ADMIN_APPS . '/' . $data['fb_app']->label),
                               )));
      }
      else {
        fb_log_exception($e, t('Exception calling %function(%op)', array(
                                 '%function' => $function,
                                 '%op' => $op)));
      }
/**
 * This method will clean up URLs.  When serving canvas pages, extra
 * information is included in URLs. This will remove the extra
 * information. Useful when linking back to the website from a canvas page or
 * wall post.
 *
 * For example in the following code, $url2 will link out to the server's domain:
 *
 * $url = url('node/42', array('absolute' => TRUE)); // i.e. http://apps.facebook.com/example/node/42
 * $url2 = fb_scrub_urls($url); // i.e. http://example.com/node/42
 *
 * 
 * @see fb_url_rewrite.inc
 */
function fb_scrub_urls($content) {
  if (function_exists('_fb_settings_url_rewrite_prefixes')) {
    foreach (_fb_settings_url_rewrite_prefixes() as $key) {
      $patterns[] = "|$key/[^/]*/|";
      $replacements[] = "";
    }
    $content = preg_replace($patterns, $replacements, $content);
  }
  return $content;
}

/**
 * Convenience function to log and report exceptions.
 */
function fb_log_exception($e, $text = '', $fb = NULL) {
  if ($text)
    $message = $text .': '. $e->getMessage();
  else
    $message = $e->getMessage();
  $message .= ' ' . $e->getCode();
  
    $message .= '. (' . t('logged into facebook as %fbu', array('%fbu' => $fb->getUser())) . ')';
  if (fb_verbose()) {
    $message .= '<pre>' . $e . '</pre>';
  }
  watchdog('fb', $message, array(), WATCHDOG_ERROR);
  if (user_access(FB_PERM_ADMINISTER)) {
    drupal_set_message($message, 'error');    
  }
}

/**
 * Exception handler for PHP5 exceptions.
 */
 function fb_handle_exception($exception) {
   $message = t('Facebook API exception %message.  !trace', array(
                '%message' => $exception->getMessage(),
                '!trace' => '<pre>'. $exception->getTraceAsString() .'</pre>',
              ));
   watchdog('fb', $message, array(), WATCHDOG_ERROR);
  //drupal_set_message($message, 'error');

  print "<pre>\$_REQUEST:\n";
  print_r($_REQUEST);
  print "\n\nREQUEST_URI:\n" . request_uri();
  print "</pre>";

/**
 * Helper function for facebook's users_getInfo API.
 *
 * This function makes calls to users_getInfo more efficient, by caching
 * results in the session, so calls do not always require hitting Facebook's
 * servers.
 *
 * Update, users.getInfo appears to be growing less reliable.  For example,
 * does not return names of test accounts.  @TODO - migrate away from this and
 * towards use of fb->api().
 * 
 * @param $oids
 * Array of facebook object IDs.  In this case they should each be a user id.
 */
function fb_users_getInfo($oids, $fb = NULL, $refresh_cache = FALSE) {
  if (!$fb) {
  }
  $infos = array();
  if (!is_array($oids))
    $oids = array();
  if ($fb) {
    $apikey = $fb->getAppId();
    // First try cache
    if (!$refresh_cache && isset($_SESSION['fb'])) {
      foreach ($oids as $oid) {
        if (isset($_SESSION['fb'][$apikey]['userinfo'][$oid])) {
          $info = $_SESSION['fb'][$apikey]['userinfo'][$oid];
          $infos[] = $info;
    if (count($infos) != count($oids)) {
      // Session cache did not include all users, update the cache.
        // For historical reasons, use users.getInfo.  New code should migrate to graph api.
        $infos = fb_call_method($fb, 'users.getInfo', array(
                                  'uids' => $oids,
                                  'fields' => array(
                                    'about_me',
                                    'affiliations',
                                    'name',
                                    'is_app_user',
                                    'pic',
                                    'pic_big',
                                    'pic_square',
                                    'profile_update_time',
                                    'proxied_email',
                                    'status',
                                    'email_hashes',
                                    'email',
                                  )));
        // Update cache with recent results.
        if (is_array($infos)) {
          foreach ($infos as $info) {
            $_SESSION['fb'][$apikey]['userinfo'][$info['uid']] = $info;
      } catch(FacebookApiException $e) {
        fb_log_exception($e, t('Failed call to users.getInfo'), $fb);
  }
}

/**
 * For debugging, add $conf['fb_verbose'] = TRUE; to settings.php.
 */
function fb_verbose() {
  return variable_get('fb_verbose', NULL);
}
/**
 * This function will be replaced, hopefully, by format_username in D7.