Skip to content
fb.module 25.1 KiB
Newer Older
// $Id$
// hook_fb
define('FB_HOOK', 'fb');

// Ops for hook_fb.
define('FB_OP_CANVAS_GET_APP', 'get_app'); // Query which app a FB callback addresses
define('FB_OP_CANVAS_INITIALIZE', 'init'); // Start a FB callback
define('FB_OP_CANVAS_POST_INIT', 'post init'); // Another chance to start FB callback
define('FB_OP_CANVAS_EXIT', 'exit'); // End an FB callback
define('FB_OP_GET_FBU', 'get_fbu'); // Query the local user's FB account
define('FB_OP_GET_INFINITE_SESSION', 'get_inf_sess'); // Query infinite session data XXX deprecated
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_APP_IS_AUTHORIZED', 'app_authorized');  // Invoked if user has authorized an app.  Triggers creation of user accounts or authmap entries
// node_access realms (belongs here?)
define('FB_GRANT_REALM_FRIEND', 'fb_friend');
define('FB_GRANT_REALM_GROUP', 'fb_group');

// When initializing Facebook API, which user to log in as:
define('FB_FBU_INFINITE_SESSION', 'fbu_infinite');
define('FB_FBU_CURRENT', 'fbu_current'); // Canvas pages only
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

/**
 * Implementation of hook_init
 * 
 * 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.)  Then, we
 * notify interested modules in various events.
 * 
 */
function fb_init() {
  global $fb, $fb_app, $fb_app_node; // Set by this function.
  if (arg(0) != 'admin')
    // Session sanity check.  This is relevant on iframe pages when user once
    // had the app added, but has since removed it.  This is a bit of a hack
    // because we rely on the fb_... params passed by facebook.
    if ($_SESSION['fb_frame_params'] &&
	$_REQUEST['fb_sig']) {
      if ($_REQUEST['fb_sig_session_key'] != $_SESSION['fb_frame_params']['session_key']) {
	// The session key from facebook has changed.  Treat this as a new session.
	$req = array();
	foreach ($_REQUEST as $key => $val) {
	  if (strpos($key, 'fb_') === 0)
	    $req[] = $key . '=' . $val;
	}
	$url = url($_REQUEST['q'], implode('&', $req), NULL, TRUE);
	if (fb_verbose())
	  watchdog('fb_debug', t('Facebook session key was %old, now %new.  Destroying session and sending user to %url.',
				 array('%old' => $_SESSION['fb_frame_params']['session_key'],
				       '%new' => $_REQUEST['fb_sig_session_key'],
				       '%url' => $url)));
	
	session_destroy();
	drupal_goto($url);
  // Perform sanity check, help users who skip the README.
  if (!variable_get('fb_settings_check', FALSE) && user_access('access administration pages')) {
    drupal_set_message(t('!drupal_for_facebook has been enabled, but not properly installed.  Please read the !readme.',
                         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>')), 'error');
  }
  // Ask other modules for app detail
  $fb_app = fb_invoke(FB_OP_CANVAS_GET_APP);
  if ($fb_app) {
    // we are in a callback
    // For canvas pages, use current user, never infinite session.
    $fb = fb_api_init($fb_app, FB_FBU_CURRENT);
    if ($fb) {
      // Give other modules a chance to initialize, require login, etc...
      //fb_invoke($fb_app, FB_OP_CANVAS_INITIALIZE);
      fb_invoke(FB_OP_CANVAS_INITIALIZE, array('fb_app' => $fb_app,
					       'fb' => $fb));

      // See if the facebook user id is known
      if ($fbu = $fb->get_loggedin_user()) {
	fb_invoke(FB_OP_APP_IS_AUTHORIZED, array('fb_app' => $fb_app,
						 'fb' => $fb,
						 'fbu' => $fbu));
      }
      watchdog('fb', "URL indicates a facebook app, but could not initialize Facebook", WATCHDOG_ERROR);
  //fb_invoke($fb_app, FB_OP_CANVAS_POST_INIT);
  fb_invoke(FB_OP_CANVAS_POST_INIT, array('fb_app' => $fb_app,
					  'fb' => $fb));
 * Include the necessary facebook-platform code and initialize the API object.
 * 
 * @param fbu To log into facebook as a particular user, pass the facebook id.
 * This is useful during cron jobs, for example, but rarely if ever needed on
 * a canvas page.  If no valid session key is known for the user, this call
 * will still return a facebook reference.
 * 
 * If FB_FBU_INFINITE_SESSION is passed, we attempt to log into an infinite
 * session we've configured.
 * 
 * If FB_FBU_ANY is passed in, we will log in as the canvas page user if
 * already logged in.  Otherwise we try the infinite session, if configured.
 * 
 * Future calls into the the facebook api could fail for various reasons.  For
 * one, we may fail to log in as the specified user.  This call does not
 * actually contact facebook to test the connection, it just sets things up so
 * that when the connection is needed, it might work.  Even if the connection
 * is established, the user may not have sufficient permission to whatever you
 * are asking facebook to do.
 * 
  //dpm(func_get_args(), "fb_api_init");
  //$trace = debug_backtrace();
  //dpm($trace, "fb_api_init call stack");

  // This helps with uncaught exceptions.  However, it should be configurable
  // or at least not overwrite previously declared handler.
  set_exception_handler('fb_handle_exception');
    $filename = variable_get('fb_api_file', 'facebook-platform/client/facebook.php');

    if (!file_exists($filename)) {
      // Print an error directly to the canvas page.
      //print("Failed to open Facebook API file $filename.  Either fix this problem or disable Facebook modules.");
      return NULL;
  $facebook_config['debug'] = variable_get('fb_debug', FALSE); // set TRUE for debug output from FB API
  // Determine the actual facebook user id to use.
  if ($GLOBALS['fb'] && $GLOBALS['fb']->validate_fb_params() &&
      ($fbu == FB_FBU_CURRENT || $fbu == FB_FBU_ANY ||
       $fbu == $GLOBALS['fb']->get_loggedin_user())) {
    return $GLOBALS['fb'];
  }
  else if (($fbu == FB_FBU_CURRENT || $fbu == FB_FBU_ANY) 
           && isset($_SESSION['fb_frame_params']) && !$_REQUEST['fb_sig']) {
    $params = $_SESSION['fb_frame_params'];
    $fbu = $params['user'];
    $session = $params['session_key'];
  }
  else if ($fbu == FB_FBU_CURRENT && $GLOBALS['fb']) {
    // No current user to use, probably anonymous canvas page.
    return $GLOBALS['fb'];
  }
  else if ($fbu == FB_FBU_INFINITE_SESSION || $fbu == FB_FBU_ANY) {
    // Learn the infinite session id and key
    // Infinite session is deprecated!!! XXX
    $data = fb_invoke(FB_OP_GET_INFINITE_SESSION, array('fb_app' => $fb_app,
							'fb' => $GLOBALS['fb']),
		      array());
    $fbu = $data[0];
    $session = $data[1];
  }
  else {
    // FB user id passed in.
  }
    // We don't have a cached resource for this app/user combo, so we're going to create one.
    $fb = new Facebook($fb_app->apikey, $fb_app->secret);
    // Iframes may link or redirect within the iframe, in which case facebook
    // will not provide all its normal variables.  To prepare for such a case,
    // we cache the facebook parameters in the session.
    // We update the cache whenever possible, to keep it current.
    if ($fb->in_frame() && $fb->validate_fb_params() && $fb->fb_params) {
      $_SESSION['fb_frame_params'] = $fb->fb_params; // hacky to use fb's internal data structure
    
    // If we learned the session, above, use it
    if ($fbu && $session) {
      $fb->set_user($fbu, $session);
    else if ($fbu && $fbu == $fb->get_loggedin_user() && $fb->validate_fb_params()) {
      // Canvas page, current user is logged in already.
      // Nothing to do here.
      // FB user id passed in.  If we happen to have valid session info for
      // them, we can log in as them.
      $data = fb_invoke(FB_OP_GET_USER_SESSION,
			array('fb_app' => $fb_app,
			      'fb' => $fb,
			      'fbu' => $fbu),
			array());
      if (count($data) && $data[0] && $data[1]) {
        $fb->set_user($data[0], $data[1]);
    // Cache the result, in case we're called again.
    // Note that facebook api has not actually logged into facebook yet.
    // We won't really know if our session is valid until later.
    // get_loggedin_user does not really test it.
    if ($fbu_orig != FB_FBU_CURRENT && !$fb->get_loggedin_user()) {
      // An FBU other than CURRENT was specified, but we failed to log in.
      watchdog('fb', t('Failed to log into facebook app %app as user %user',
                       array('%app' => $fb_app->title,
                             '%user' => $fbu_orig)), WATCHDOG_ERROR);
  
  $fb = $cache[$fb_app->apikey][$fbu];
  return $fb;
/**
 * 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', t('%function unable to initialize Facebook API.',
                     array('%function' => '_fb_api_init()')), WATCHDOG_ERROR);
    return;
  }
  else
    return $fb;
}


 * 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 pages or when infinite session has been initialized.
function fb_facebook_user($fb = NULL) {
  if (!isset($fb))
    $fb = $GLOBALS['fb'];
  $fbu = $fb->get_loggedin_user();
  if ($fb->api_client->error_code) {
    watchdog('fb', 'Failed to get Facebook user id.', 'error');
    if (function_exists('dprint_r'))
      watchdog('fb', 'Failed to get Facebook user id.  detail:' . dprint_r($_REQUEST, 1), 'error');
  }
  return $fbu;
}

/**
 * Given a local user id, find the facebook id.
 */
function fb_get_fbu($uid, $fb_app = NULL) {
  // default to current app (only set if we're in a FB callback)
  if (!$fb_app)
  // Accept either a user object or uid passed in.
  if (is_object($uid) && $uid->fbu)
    return $uid->fbu;
  else if (is_object($uid))
    $uid = $uid->uid;

  // User management is handled by another module.  Use our hook to ask for mapping.
  $fbu = fb_invoke(FB_OP_GET_FBU, array('fb_app' => $fb_app,
					'uid' => $uid,
					'fb' => $GLOBALS['fb']));
/**
 * Convenience method to get an apps callback URL.
 *
 * TODO: This code will probably not handle every case and may need some work.
 */
function fb_get_callback_url($fb_app) {
  $url = url('', NULL, NULL, TRUE) . FB_SETTINGS_APP_NID . '/'. $fb_app->nid . '/';
  return $url;
}

 * 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_CANVAS_GET_APP, $search_data);
  return $fb_app;
}

/**
 * Convenience method to return a list of all known apps, suitable for form
 * elements.
 */
function fb_get_app_options($include_current) {
  $options = array();
  if ($include_current)
    $options[FB_APP_CURRENT] = t('<current>');
  foreach ($apps as $app) {
    $options[$app->nid] = $app->title;
/**
 * 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.
 * In other cases it tries to work, but will be an expensive operation and
 * only succeed when the user has created an infinite session.
 * 
 * @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;

  if (!isset($cache[$fbu])) {
Dave Cohen's avatar
Dave Cohen committed
    if ($fb === $GLOBALS['fb'] && 
        $fbu == fb_facebook_user($fb))
      $items = $fb->api_client->friends_get();
    // friends_get does not work in cron call, so we double check.
    if (!$items || !count($items)) {
      $logged_in = fb_facebook_user($fb);
      $query = "SELECT uid2 FROM friend WHERE uid1=$fbu";
      $result = $fb->api_client->fql_query($query);
      fb_report_errors($fb);
      
      $items = array();
      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])) {
    $cache[$fbu] = $fb->api_client->groups_get($fbu, NULL);    
  }
  
  return $cache[$fbu];
}


// deprecated since creation of fb_user module, but cron hook still uses this.
function fb_user_load($fbu = NULL) {
  global $user;
  if (!$fbu)
    // default to current logged in user
    $fbu = fb_facebook_user();
  if ($fbu && $user->fbu == $fbu) {
    return $user;
  }
  if ($fbu) {
    $account = user_external_load("$fbu-$fb_app->apikey@facebook.com");
    if (!$account)
      $account = user_external_load("$fbu@facebook.com");
    if (!$account)
      $account = user_load(array('uid' => variable_get('fb_facebook_user', 2)));
    if (!$account)
      watchdog('fb', t('Failed to load user from facebook fbu=%fbu',
                       array('%fbu' => $fbu)), 'error');
    $account->fbu = $fbu;
    return $account;
  }
}


function fb_form_alter($form_id, &$form) {
  // Because facebook users don't have email, it can't be required on user form
  if ($form_id == 'user_register') {
    if (user_access('administer users')) {
      $form['mail']['#required'] = FALSE;
    }
  }
  if ($form_id == 'user_edit') {
    if (user_access('administer users')) {
      $form['account']['mail']['#required'] = FALSE;
    }
  }
}


function fb_menu($may_cache) {
  $items = array();
  
  if (!$may_cache) {
    // Initialization moved to fb_init(), nothing to do here.
  }
  else {
    // When forms are submitted directly to us, we cache the results,
    // and show them later via this callback
    $items[] = array('path' => 'fb/form_cache',
                     'callback' => '_fb_form_cache_cb',
                     'type' => MENU_CALLBACK,
                     'access' => TRUE);

    // A page to help determine infinite session keys
    $items[] = array('path' => 'fb/session',
                     'callback' => '_fb_session_cb',
                     'type' => MENU_CALLBACK,
                     'access' => TRUE); // TODO: restrict access?
  }
  return $items;
}

/**
 * When exiting we need to do some special stuff for forms
 */
function fb_exit($destination = NULL) {
  global $fb_app, $fb;
  //fb_invoke($fb_app, FB_OP_CANVAS_EXIT, $destination);
  fb_invoke(FB_OP_CANVAS_EXIT, array('fb_app' => $fb_app,
				     'fb' => $GLOBALS['fb']),
	    $destination);
}

function _fb_form_cache_cb($cid) {
  // Facebook started appending a '?', we need to get rid of it.
  if ($pos = strpos($cid, '?'))
    $cid = substr($cid, 0, $pos);

  watchdog('fb', "Returning cached form page $cid");
  $cache = cache_get($cid, 'cache_page');
  // Don't clear, as user may refresh browser.  Cache will expire eventually.
  // cache_clear_all($cid, 'cache_page');
  print $cache->data;
  exit();
}

function _fb_session_cb() {
  global $fb, $fb_app;
  global $user;
  drupal_set_message("_fb_session_cb" . dpr($_REQUEST, 1));
  $output = '<p>'.t('You are logged in as local user !username, with Facebook user id %fbu.',
                    array('!username' => theme('username', $user),
                          '%fbu' => fb_get_fbu($user))).'</p>';
  $output .= '<p>'.l(t('Request an infinite session key.'),
                       'http://www.facebook.com/code_gen.php?v=1.0&api_key='.$fb_app->apikey).'</p>';
  
  $output .= drupal_get_form('fb_session_key_form');

  $output .= '<p>'.t('Current session key: %key',
                     array('%key' => htmlentities($fb->api_client->session_key)))."</p>\n";
  return $output;
}

function fb_session_key_form() {
  global $fb_app;
  $form = array('auth_token' => array('#type' => 'textfield',
                                      '#title' => t('One-time code'),
                                      '#description' => t('If you do not have a one-time code, you can get one !here.',
                                                          array('!here' => l(t('here'), 'http://www.facebook.com/code_gen.php?v=1.0&api_key='.$fb_app->apikey))),
                ),
                'submit' => array('#type' => 'submit',
                                  '#value' => t('Submit')),
                
                '#redirect' => FALSE, /* necessary when submitting via facebook */
//function fb_invoke($fb_app, $op, $return = NULL, $data = NULL) { old sig XXX
function fb_invoke($op, $data = NULL, $return = NULL) {
  foreach (module_implements(FB_HOOK) as $name) {
    $function = $name . '_' . FB_HOOK;
    try {
      $function($op, $data, $return);
    }
    catch (Exception $e) {
      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 (see fb_settings.inc).  This will remove
 * the extra information.
 */
function fb_scrub_urls($content) {
  foreach (array(FB_SETTINGS_APP_NID, FB_SETTINGS_PAGE_TYPE) as $key) {
    $patterns[] = "|$key/[^/]*/|";
    $replacements[] = "";
  }
  $content = preg_replace($patterns, $replacements, $content);
  return $content;
}

/**
 * Implementation of hook_node_grants.
 * 
 * We put this here so all facebook modules have a standard way to implement
 * hook_node_access_records.  They use that hook to insert records into the
 * node access table.  We use this hook to allow access when the grants are
 * there.
 * 
 * DEPRECATED.  Not sure where, if anywhere, this belongs.
 function fb_node_grantsXXX($account, $op) {
  $grants = array();
  $fbu = fb_get_fbu($account);
  if ($fbu) { // If not anonymous (facebook user not logged in to this app)
    $friends = fb_get_friends($fbu);
    // For access purposes, consider a users to be a friend of themself
    $friends[] = $fbu;
    if (count($friends))
      $grants[FB_GRANT_REALM_FRIEND] = $friends;
    
    $groups = fb_get_groups($fbu);
    if (count($groups))
      $grants[FB_GRANT_REALM_GROUP] = $groups;
  }

  return $grants;
}
/**
 * Convenience method for displaying facebook api errors.
 */
function fb_report_errors($fb = FB_APP_CURRENT, $message = NULL) {
  if ($fb == FB_APP_CURRENT) {
  }
  if ($fb) {
    if ($fb->api_client->error_code) {
      $message = t('!message Facebook API error %code (see !link).',
                   array('%code' => $fb->api_client->error_code,
                         '!link' => l(t('error codes'), "http://wiki.developers.facebook.com/index.php/Error_codes"),
                         '!message' => $message,
      watchdog('fb', $message, WATCHDOG_ERROR);

function fb_log_exception($e, $text = '') {
  if ($text)
    $message = $text .': '. $e->getMessage();
  else
    $message = $e->getMessage();
  $message .= ' ' . $e->getCode();
  
  watchdog('fb', $message, WATCHDOG_ERROR);
  if (user_access('administer fb apps')) {
    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, WATCHDOG_ERROR);
  //drupal_set_message($message, 'error');

  print "<pre>\$_REQUEST:\n";
  print_r($_REQUEST);
  print "\n\nREQUEST_URI:\n" . $_SERVER['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.
 *
 * Particularly useful when displaying user info on iframe canvas pages, where
 * <fb:user> tags are not available.
 *
 * @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) {
    $fb = $GLOBALS['fb'];
  }
  $infos = array();
  if (!is_array($oids))
    $oids = array();

  if ($fb) {
    // First try cache
    if (!$refresh_cache)
      foreach ($oids as $oid) {
        if ($info = $_SESSION['fb'][$fb->api_key]['userinfo'][$oid])
          $infos[] = $info;
      }
    if (count($infos) != count($oids)) {
      // Session cache did not include all users, update the cache.
      $infos = $fb->api_client->users_getInfo($oids,
                                              array('about_me',
                                                    'affiliations',
                                                    'name',
                                                    'is_app_user',
                                                    'pic_big',
                                                    'pic_square',
                                                    'profile_update_time',
                                                    'status',
                                                    ));
      // Update cache with recent results.
      foreach($infos as $info) {
        $_SESSION['fb'][$fb->api_key]['userinfo'][$info['uid']] = $info;
      }
    }

    return $infos;
  }
}

/**
 * Helper function for FBJS files.
 * 
 * Useful for adding Facebook Javascript, which will be incorporated into
 * canvas pages or profile boxes.  When included this way, javascript must be
 * embedded inline, rather than refer to an external URL.  So this function
 * will actually read a local file and include the contents inline.
 */
function fb_add_js($filename, $type) {
  static $cache;
  if (!$cache) {
    $cache = array();

    // Add the most basic file we need.
    $base_file = drupal_get_path('module', 'fb') . '/fb_fbml.js';
    $base_file .= "?v=".filemtime($base_file);
    drupal_add_js($base_file, 'module', 'fbml');
    
    // Add some settings that FBJS code will often need.
    $baseUrl = url('', NULL, NULL, TRUE);
    drupal_add_js(array('fbjs' => array('baseUrlFb' => $baseUrl,
                                        'baseUrl' => fb_scrub_urls($baseUrl),
                        ),
                  ),
  if (!$cache[$filename]) {
    if (file_exists($filename)) {
      // Refresh facebook's cache when file changes
      $filename .= "?v=".filemtime($filename);
    }
    // 'post_settings' is a hack to make our code come after settings. This is
    // ugly, but we're doing it because there is no "onready" in FBJS.
    drupal_add_js($filename, 'post_settings', 'fbml');
    $cache[$filename] = TRUE;
  }
}

/**
 * For debugging, add $conf['fb_verbose'] = TRUE; to settings.php.
 */
function fb_verbose() {
  return variable_get('fb_verbose', NULL);
}