');
  foreach ($apps as $app) {
    if ($key == 'apikey') {
      $options[$app->apikey] = $app->title;
    }
    else {
      $options[$app->nid] = $app->title;
    }
  }
  return $options;
}
/**
 * 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());
  return $apps;
}
/**
 * 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 is logged in via
 * Connect, or has created an infinite session.
 * 
 * @return: an array of facebook ids
 */
function fb_get_friends($fbu, $fb_app = NULL) {
  static $cache = array();
  if (!$fb_app)
    $fb_app = $GLOBALS['fb_app'];
  // 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])) {
    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();
  $fb = _fb_api_init($fb_app);
  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', 'Failed to load user from facebook fbu=%fbu',
               array('%fbu' => $fbu), WATCHDOG_ERROR);
    $account->fbu = $fbu;
    return $account;
  }
}
function fb_form_alter(&$form, &$form_state, $form_id) {
  // 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() {
  $items = array();
  
    // When forms are submitted directly to us, we cache the results,
    // and show them later via this callback
    $items['fb/form_cache'] = array(
                     'page callback' => '_fb_form_cache_cb',
                     'type' => MENU_CALLBACK,
                     'access callback' => TRUE);
  return $items;
}
/**
 * When exiting we need to do some special stuff for forms
 */
function fb_exit($destination = NULL) {
  global $fb_app, $fb;
  if ($fb_app && $fb) {
    fb_invoke(FB_OP_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_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 */
  );
  return $form;
}
/**
 * Invoke hook_fb.
 */
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)));
    }
  }
  return $return;
}
/**
 * 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) {
    $fb = $GLOBALS['fb'];
  }
  if ($fb) {
    if (isset($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, array(), WATCHDOG_ERROR);
      if (user_access('administer fb apps')) {
        drupal_set_message($message, 'error');
      }
    }
  }
}
function fb_log_exception($e, $text = '', $fb = NULL) {
  if ($text)
    $message = $text .': '. $e->getMessage();
  else
    $message = $e->getMessage();
  $message .= ' ' . $e->getCode();
  
  if ($fb) {
    $message .= '. (' . t('logged into facebook as %fbu', array('%fbu' => $fb->get_loggedin_user())) . ')';
  }
  if (fb_verbose()) {
    $message .= '' . $e . '
';
  }
  watchdog('fb', $message, array(), 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' => ''.$exception->getTraceAsString().'
',
               ));
   watchdog('fb', $message, array(), WATCHDOG_ERROR);
  //drupal_set_message($message, 'error');
  print $message;
  print "\$_REQUEST:\n";
  print_r($_REQUEST);
  print "\n\nREQUEST_URI:\n" . $_SERVER['REQUEST_URI'];
  print "
";
}
/**
 * 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.
 *
 * @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',
                                                    'pic_big',
                                                    'pic_square',
                                                    'profile_update_time',
                                                    'proxied_email',
                                                    'status',
                                                    'email_hashes',
                                              ));
      // Update cache with recent results.
      if (is_array($infos)) {
        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('', array('absolute' => TRUE));
    drupal_add_js(array('fbjs' => array('baseUrlFb' => $baseUrl,
                                        'baseUrl' => fb_scrub_urls($baseUrl),
                        ),
                  ),
                  'setting', 'fbml');
    
  }
  
  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);
}
 
/**
 * Define custom_url_rewrite_inbound() if not defined already.
 */
if (!function_exists('custom_url_rewrite_inbound')) {
  function custom_url_rewrite_inbound(&$result, $path, $path_language) {
    fb_url_inbound_alter($result, $path, $path_language);
  }
}
/**
 * Define custom_url_rewrite_outbound() if the url_alter.module is not enabled.
 */
if (!function_exists('custom_url_rewrite_outbound')) {
  function custom_url_rewrite_outbound(&$path, &$options, $original_path) {
    fb_url_outbound_alter($path, $options, $original_path);
  }
}
/**
 * Implementation of hook_url_outbound_alter().
 */
function fb_url_outbound_alter(&$path, &$options, $original_path) {
  //dpm(func_get_args(), 'fb_settings_url_rewrite_outbound');
  $pre = '';
  
  // Prefix each known value to the URL
  foreach (_fb_settings_url_rewrite_prefixes() as $prefix) {
    if ($value = fb_settings($prefix))
      $pre .= $prefix . '/'. $value . '/';
  }
  $path  = $pre . $path;
  
  return $path;
}
/**
 * Implementation of hook_url_inbound_alter().
 *
 * Rewrite URLs for facebook canvas pages, and connect callbacks.
 * 
 */
function fb_url_inbound_alter(&$result, $path, $path_language){
  //$origpath = $path;
  //watchdog('fb_settings', "fb_settings_url_rewrite_inbound($result, $path, $path_language)", array(), WATCHDOG_DEBUG);
  
  // See if this is a request for us.
  if (strpos($path, FB_SETTINGS_APP_NID . '/') === 0) {
    // Too soon for arg() function.
    $args = explode('/', $path);
    while (count($args) && in_array($args[0], _fb_settings_url_rewrite_prefixes())) {
      $key = array_shift($args);
      $value = array_shift($args);
      $app_nid = fb_settings($key, $value); // Store for use later.
    }
    if ($app_nid = fb_settings(FB_SETTINGS_APP_NID)) {
      if (count($args)) {
        $path = implode('/', $args); // remaining args
        $alias = drupal_lookup_path('source', $path, $path_language); //can't use drupal_get_normal_path, it calls custom_url_rewrite_inbound
        if ($alias) 
          $path = $alias;
      }
      else {
        // frontpage
        $path = variable_get('site_frontpage', 'node');
        $alias = drupal_lookup_path('source', $path, $path_language);
        if ($alias) 
          $path = $alias;
        $_REQUEST['destination'] = $path; //required workaround for compatibility with Global Redirect module, best practice?
      }
    }
  }
  else { //resolve aliases for non-fb-callbacks
    $alias = drupal_lookup_path('source', $path, $path_language);
    if ($alias) 
      $path = $alias;
  }
  
  $result = $path;
}
/**
 * This function will be replaced, hopefully, by format_username in D7.
 *
 * See http://drupal.org/node/192056
 */
function fb_format_username($account) {
  $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous'));
  drupal_alter('username', $name, $account);
  return $name;
}
/**
 * hook_username_alter().
 *
 * Return a user's facebook name, instead of local username.
 */
function fb_username_alter(&$name, $account) {
  //dpm(func_get_args(), "fb_username_alter($name)");
  if (isset($account->fbu) && ($name == $account->fbu . '@facebook')) {
    $info = fb_users_getInfo(array($account->fbu));
    if ($info[0]['name']) {
      $name = $info[0]['name'];
    }
  }
}
?>