]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Gather all the Facebook stuff together in one place
authorZach Copley <zach@status.net>
Wed, 21 Oct 2009 01:07:03 +0000 (01:07 +0000)
committerZach Copley <zach@status.net>
Wed, 21 Oct 2009 01:07:03 +0000 (01:07 +0000)
34 files changed:
extlib/facebook/facebook.php [deleted file]
extlib/facebook/facebook_desktop.php [deleted file]
extlib/facebook/facebookapi_php5_restlib.php [deleted file]
extlib/facebook/jsonwrapper/JSON/JSON.php [deleted file]
extlib/facebook/jsonwrapper/JSON/LICENSE [deleted file]
extlib/facebook/jsonwrapper/jsonwrapper.php [deleted file]
extlib/facebook/jsonwrapper/jsonwrapper_inner.php [deleted file]
plugins/FBConnect/FBCLoginGroupNav.php [deleted file]
plugins/FBConnect/FBCSettingsNav.php [deleted file]
plugins/FBConnect/FBC_XDReceiver.php [deleted file]
plugins/FBConnect/FBConnectAuth.php [deleted file]
plugins/FBConnect/FBConnectLogin.php [deleted file]
plugins/FBConnect/FBConnectPlugin.css [deleted file]
plugins/FBConnect/FBConnectPlugin.php [deleted file]
plugins/FBConnect/FBConnectSettings.php [deleted file]
plugins/FBConnect/README [deleted file]
plugins/FBConnect/fbfavicon.ico [deleted file]
plugins/Facebook/FBCLoginGroupNav.php [new file with mode: 0644]
plugins/Facebook/FBCSettingsNav.php [new file with mode: 0644]
plugins/Facebook/FBC_XDReceiver.php [new file with mode: 0644]
plugins/Facebook/FBConnectAuth.php [new file with mode: 0644]
plugins/Facebook/FBConnectLogin.php [new file with mode: 0644]
plugins/Facebook/FBConnectPlugin.css [new file with mode: 0644]
plugins/Facebook/FBConnectPlugin.php [new file with mode: 0644]
plugins/Facebook/FBConnectSettings.php [new file with mode: 0644]
plugins/Facebook/README
plugins/Facebook/facebook/facebook.php [new file with mode: 0644]
plugins/Facebook/facebook/facebook_desktop.php [new file with mode: 0644]
plugins/Facebook/facebook/facebookapi_php5_restlib.php [new file with mode: 0755]
plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php [new file with mode: 0644]
plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE [new file with mode: 0644]
plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php [new file with mode: 0644]
plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php [new file with mode: 0644]
plugins/Facebook/fbfavicon.ico [new file with mode: 0644]

diff --git a/extlib/facebook/facebook.php b/extlib/facebook/facebook.php
deleted file mode 100644 (file)
index 016e8e8..0000000
+++ /dev/null
@@ -1,598 +0,0 @@
-<?php
-// Copyright 2004-2009 Facebook. All Rights Reserved.
-//
-// +---------------------------------------------------------------------------+
-// | Facebook Platform PHP5 client                                             |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007 Facebook, Inc.                                         |
-// | All rights reserved.                                                      |
-// |                                                                           |
-// | Redistribution and use in source and binary forms, with or without        |
-// | modification, are permitted provided that the following conditions        |
-// | are met:                                                                  |
-// |                                                                           |
-// | 1. Redistributions of source code must retain the above copyright         |
-// |    notice, this list of conditions and the following disclaimer.          |
-// | 2. Redistributions in binary form must reproduce the above copyright      |
-// |    notice, this list of conditions and the following disclaimer in the    |
-// |    documentation and/or other materials provided with the distribution.   |
-// |                                                                           |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
-// +---------------------------------------------------------------------------+
-// | For help with this library, contact developers-help@facebook.com          |
-// +---------------------------------------------------------------------------+
-
-include_once 'facebookapi_php5_restlib.php';
-
-define('FACEBOOK_API_VALIDATION_ERROR', 1);
-class Facebook {
-  public $api_client;
-  public $api_key;
-  public $secret;
-  public $generate_session_secret;
-  public $session_expires;
-
-  public $fb_params;
-  public $user;
-  public $profile_user;
-  public $canvas_user;
-  protected $base_domain;
-  /*
-   * Create a Facebook client like this:
-   *
-   * $fb = new Facebook(API_KEY, SECRET);
-   *
-   * This will automatically pull in any parameters, validate them against the
-   * session signature, and chuck them in the public $fb_params member variable.
-   *
-   * @param api_key                  your Developer API key
-   * @param secret                   your Developer API secret
-   * @param generate_session_secret  whether to automatically generate a session
-   *                                 if the user doesn't have one, but
-   *                                 there is an auth token present in the url,
-   */
-  public function __construct($api_key, $secret, $generate_session_secret=false) {
-    $this->api_key                 = $api_key;
-    $this->secret                  = $secret;
-    $this->generate_session_secret = $generate_session_secret;
-    $this->api_client = new FacebookRestClient($api_key, $secret, null);
-    $this->validate_fb_params();
-
-    // Set the default user id for methods that allow the caller to
-    // pass an explicit uid instead of using a session key.
-    $defaultUser = null;
-    if ($this->user) {
-      $defaultUser = $this->user;
-    } else if ($this->profile_user) {
-      $defaultUser = $this->profile_user;
-    } else if ($this->canvas_user) {
-      $defaultUser = $this->canvas_user;
-    }
-
-    $this->api_client->set_user($defaultUser);
-
-
-    if (isset($this->fb_params['friends'])) {
-      $this->api_client->friends_list = explode(',', $this->fb_params['friends']);
-    }
-    if (isset($this->fb_params['added'])) {
-      $this->api_client->added = $this->fb_params['added'];
-    }
-    if (isset($this->fb_params['canvas_user'])) {
-      $this->api_client->canvas_user = $this->fb_params['canvas_user'];
-    }
-  }
-
-  /*
-   * Validates that the parameters passed in were sent from Facebook. It does so
-   * by validating that the signature matches one that could only be generated
-   * by using your application's secret key.
-   *
-   * Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE,
-   * in that order. $_POST and $_GET are always more up-to-date than cookies,
-   * so we prefer those if they are available.
-   *
-   * For nitty-gritty details of when each of these is used, check out
-   * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
-   *
-   * @param bool  resolve_auth_token  convert an auth token into a session
-   */
-  public function validate_fb_params($resolve_auth_token=true) {
-    $this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
-
-    // note that with preload FQL, it's possible to receive POST params in
-    // addition to GET, so use a different prefix to differentiate them
-    if (!$this->fb_params) {
-      $fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
-      $fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
-      $this->fb_params = array_merge($fb_params, $fb_post_params);
-    }
-
-    // Okay, something came in via POST or GET
-    if ($this->fb_params) {
-      $user               = isset($this->fb_params['user']) ?
-                            $this->fb_params['user'] : null;
-      $this->profile_user = isset($this->fb_params['profile_user']) ?
-                            $this->fb_params['profile_user'] : null;
-      $this->canvas_user  = isset($this->fb_params['canvas_user']) ?
-                            $this->fb_params['canvas_user'] : null;
-      $this->base_domain  = isset($this->fb_params['base_domain']) ?
-                            $this->fb_params['base_domain'] : null;
-
-      if (isset($this->fb_params['session_key'])) {
-        $session_key =  $this->fb_params['session_key'];
-      } else if (isset($this->fb_params['profile_session_key'])) {
-        $session_key =  $this->fb_params['profile_session_key'];
-      } else {
-        $session_key = null;
-      }
-      $expires     = isset($this->fb_params['expires']) ?
-                     $this->fb_params['expires'] : null;
-      $this->set_user($user,
-                      $session_key,
-                      $expires);
-    }
-    // if no Facebook parameters were found in the GET or POST variables,
-    // then fall back to cookies, which may have cached user information
-    // Cookies are also used to receive session data via the Javascript API
-    else if ($cookies =
-             $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
-
-      $base_domain_cookie = 'base_domain_' . $this->api_key;
-      if (isset($_COOKIE[$base_domain_cookie])) {
-        $this->base_domain = $_COOKIE[$base_domain_cookie];
-      }
-
-      // use $api_key . '_' as a prefix for the cookies in case there are
-      // multiple facebook clients on the same domain.
-      $expires = isset($cookies['expires']) ? $cookies['expires'] : null;
-      $this->set_user($cookies['user'],
-                      $cookies['session_key'],
-                      $expires);
-    }
-    // finally, if we received no parameters, but the 'auth_token' GET var
-    // is present, then we are in the middle of auth handshake,
-    // so go ahead and create the session
-    else if ($resolve_auth_token && isset($_GET['auth_token']) &&
-             $session = $this->do_get_session($_GET['auth_token'])) {
-      if ($this->generate_session_secret &&
-          !empty($session['secret'])) {
-        $session_secret = $session['secret'];
-      }
-
-      if (isset($session['base_domain'])) {
-        $this->base_domain = $session['base_domain'];
-      }
-
-      $this->set_user($session['uid'],
-                      $session['session_key'],
-                      $session['expires'],
-                      isset($session_secret) ? $session_secret : null);
-    }
-
-    return !empty($this->fb_params);
-  }
-
-  // Store a temporary session secret for the current session
-  // for use with the JS client library
-  public function promote_session() {
-    try {
-      $session_secret = $this->api_client->auth_promoteSession();
-      if (!$this->in_fb_canvas()) {
-        $this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret);
-      }
-      return $session_secret;
-    } catch (FacebookRestClientException $e) {
-      // API_EC_PARAM means we don't have a logged in user, otherwise who
-      // knows what it means, so just throw it.
-      if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
-        throw $e;
-      }
-    }
-  }
-
-  public function do_get_session($auth_token) {
-    try {
-      return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret);
-    } catch (FacebookRestClientException $e) {
-      // API_EC_PARAM means we don't have a logged in user, otherwise who
-      // knows what it means, so just throw it.
-      if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
-        throw $e;
-      }
-    }
-  }
-
-  // Invalidate the session currently being used, and clear any state associated
-  // with it. Note that the user will still remain logged into Facebook.
-  public function expire_session() {
-    if ($this->api_client->auth_expireSession()) {
-      $this->clear_cookie_state();
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  /** Logs the user out of all temporary application sessions as well as their
-   * Facebook session.  Note this will only work if the user has a valid current
-   * session with the application.
-   *
-   * @param string  $next  URL to redirect to upon logging out
-   *
-   */
-   public function logout($next) {
-    $logout_url = $this->get_logout_url($next);
-
-    // Clear any stored state
-    $this->clear_cookie_state();
-
-    $this->redirect($logout_url);
-  }
-
-  /**
-   *  Clears any persistent state stored about the user, including
-   *  cookies and information related to the current session in the
-   *  client.
-   *
-   */
-  public function clear_cookie_state() {
-    if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
-       $cookies = array('user', 'session_key', 'expires', 'ss');
-       foreach ($cookies as $name) {
-         setcookie($this->api_key . '_' . $name, false, time() - 3600);
-         unset($_COOKIE[$this->api_key . '_' . $name]);
-       }
-       setcookie($this->api_key, false, time() - 3600);
-       unset($_COOKIE[$this->api_key]);
-     }
-
-     // now, clear the rest of the stored state
-     $this->user = 0;
-     $this->api_client->session_key = 0;
-  }
-
-  public function redirect($url) {
-    if ($this->in_fb_canvas()) {
-      echo '<fb:redirect url="' . $url . '"/>';
-    } else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) {
-      // make sure facebook.com url's load in the full frame so that we don't
-      // get a frame within a frame.
-      echo "<script type=\"text/javascript\">\ntop.location.href = \"$url\";\n</script>";
-    } else {
-      header('Location: ' . $url);
-    }
-    exit;
-  }
-
-  public function in_frame() {
-    return isset($this->fb_params['in_canvas'])
-        || isset($this->fb_params['in_iframe']);
-  }
-  public function in_fb_canvas() {
-    return isset($this->fb_params['in_canvas']);
-  }
-
-  public function get_loggedin_user() {
-    return $this->user;
-  }
-
-  public function get_canvas_user() {
-    return $this->canvas_user;
-  }
-
-  public function get_profile_user() {
-    return $this->profile_user;
-  }
-
-  public static function current_url() {
-    return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
-  }
-
-  // require_add and require_install have been removed.
-  // see http://developer.facebook.com/news.php?blog=1&story=116 for more details
-  public function require_login() {
-    if ($user = $this->get_loggedin_user()) {
-      return $user;
-    }
-    $this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
-  }
-
-  public function require_frame() {
-    if (!$this->in_frame()) {
-      $this->redirect($this->get_login_url(self::current_url(), true));
-    }
-  }
-
-  public static function get_facebook_url($subdomain='www') {
-    return 'http://' . $subdomain . '.facebook.com';
-  }
-
-  public function get_install_url($next=null) {
-    // this was renamed, keeping for compatibility's sake
-    return $this->get_add_url($next);
-  }
-
-  public function get_add_url($next=null) {
-    $page = self::get_facebook_url().'/add.php';
-    $params = array('api_key' => $this->api_key);
-
-    if ($next) {
-      $params['next'] = $next;
-    }
-
-    return $page . '?' . http_build_query($params);
-  }
-
-  public function get_login_url($next, $canvas) {
-    $page = self::get_facebook_url().'/login.php';
-    $params = array('api_key' => $this->api_key,
-                    'v'       => '1.0');
-
-    if ($next) {
-      $params['next'] = $next;
-    }
-    if ($canvas) {
-      $params['canvas'] = '1';
-    }
-
-    return $page . '?' . http_build_query($params);
-  }
-
-  public function get_logout_url($next) {
-    $page = self::get_facebook_url().'/logout.php';
-    $params = array('app_key'     => $this->api_key,
-                    'session_key' => $this->api_client->session_key);
-
-    if ($next) {
-      $params['connect_next'] = 1;
-      $params['next'] = $next;
-    }
-
-    return $page . '?' . http_build_query($params);
-  }
-
-  public function set_user($user, $session_key, $expires=null, $session_secret=null) {
-    if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user'])
-                                   || $_COOKIE[$this->api_key . '_user'] != $user)) {
-      $this->set_cookies($user, $session_key, $expires, $session_secret);
-    }
-    $this->user = $user;
-    $this->api_client->session_key = $session_key;
-    $this->session_expires = $expires;
-  }
-
-  public function set_cookies($user, $session_key, $expires=null, $session_secret=null) {
-    $cookies = array();
-    $cookies['user'] = $user;
-    $cookies['session_key'] = $session_key;
-    if ($expires != null) {
-      $cookies['expires'] = $expires;
-    }
-    if ($session_secret != null) {
-      $cookies['ss'] = $session_secret;
-    }
-
-    foreach ($cookies as $name => $val) {
-      setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain);
-      $_COOKIE[$this->api_key . '_' . $name] = $val;
-    }
-    $sig = self::generate_sig($cookies, $this->secret);
-    setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain);
-    $_COOKIE[$this->api_key] = $sig;
-
-    if ($this->base_domain != null) {
-      $base_domain_cookie = 'base_domain_' . $this->api_key;
-      setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain);
-      $_COOKIE[$base_domain_cookie] = $this->base_domain;
-    }
-  }
-
-  /**
-   * Tries to undo the badness of magic quotes as best we can
-   * @param     string   $val   Should come directly from $_GET, $_POST, etc.
-   * @return    string   val without added slashes
-   */
-  public static function no_magic_quotes($val) {
-    if (get_magic_quotes_gpc()) {
-      return stripslashes($val);
-    } else {
-      return $val;
-    }
-  }
-
-  /*
-   * Get the signed parameters that were sent from Facebook. Validates the set
-   * of parameters against the included signature.
-   *
-   * Since Facebook sends data to your callback URL via unsecured means, the
-   * signature is the only way to make sure that the data actually came from
-   * Facebook. So if an app receives a request at the callback URL, it should
-   * always verify the signature that comes with against your own secret key.
-   * Otherwise, it's possible for someone to spoof a request by
-   * pretending to be someone else, i.e.:
-   *      www.your-callback-url.com/?fb_user=10101
-   *
-   * This is done automatically by verify_fb_params.
-   *
-   * @param  assoc  $params     a full array of external parameters.
-   *                            presumed $_GET, $_POST, or $_COOKIE
-   * @param  int    $timeout    number of seconds that the args are good for.
-   *                            Specifically good for forcing cookies to expire.
-   * @param  string $namespace  prefix string for the set of parameters we want
-   *                            to verify. i.e., fb_sig or fb_post_sig
-   *
-   * @return  assoc the subset of parameters containing the given prefix,
-   *                and also matching the signature associated with them.
-   *          OR    an empty array if the params do not validate
-   */
-  public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') {
-    $prefix = $namespace . '_';
-    $prefix_len = strlen($prefix);
-    $fb_params = array();
-    if (empty($params)) {
-      return array();
-    }
-
-    foreach ($params as $name => $val) {
-      // pull out only those parameters that match the prefix
-      // note that the signature itself ($params[$namespace]) is not in the list
-      if (strpos($name, $prefix) === 0) {
-        $fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val);
-      }
-    }
-
-    // validate that the request hasn't expired. this is most likely
-    // for params that come from $_COOKIE
-    if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) {
-      return array();
-    }
-
-    // validate that the params match the signature
-    $signature = isset($params[$namespace]) ? $params[$namespace] : null;
-    if (!$signature || (!$this->verify_signature($fb_params, $signature))) {
-      return array();
-    }
-    return $fb_params;
-  }
-
-  /**
-   *  Validates the account that a user was trying to set up an
-   *  independent account through Facebook Connect.
-   *
-   *  @param  user The user attempting to set up an independent account.
-   *  @param  hash The hash passed to the reclamation URL used.
-   *  @return bool True if the user is the one that selected the
-   *               reclamation link.
-   */
-  public function verify_account_reclamation($user, $hash) {
-    return $hash == md5($user . $this->secret);
-  }
-
-  /**
-   * Validates that a given set of parameters match their signature.
-   * Parameters all match a given input prefix, such as "fb_sig".
-   *
-   * @param $fb_params     an array of all Facebook-sent parameters,
-   *                       not including the signature itself
-   * @param $expected_sig  the expected result to check against
-   */
-  public function verify_signature($fb_params, $expected_sig) {
-    return self::generate_sig($fb_params, $this->secret) == $expected_sig;
-  }
-
-  /**
-   * Validate the given signed public session data structure with
-   * public key of the app that
-   * the session proof belongs to.
-   *
-   * @param $signed_data the session info that is passed by another app
-   * @param string $public_key Optional public key of the app. If this
-   *               is not passed, function will make an API call to get it.
-   * return true if the session proof passed verification.
-   */
-  public function verify_signed_public_session_data($signed_data,
-                                                    $public_key = null) {
-
-    // If public key is not already provided, we need to get it through API
-    if (!$public_key) {
-      $public_key = $this->api_client->auth_getAppPublicKey(
-        $signed_data['api_key']);
-    }
-
-    // Create data to verify
-    $data_to_serialize = $signed_data;
-    unset($data_to_serialize['sig']);
-    $serialized_data = implode('_', $data_to_serialize);
-
-    // Decode signature
-    $signature = base64_decode($signed_data['sig']);
-    $result = openssl_verify($serialized_data, $signature, $public_key,
-                             OPENSSL_ALGO_SHA1);
-    return $result == 1;
-  }
-
-  /*
-   * Generate a signature using the application secret key.
-   *
-   * The only two entities that know your secret key are you and Facebook,
-   * according to the Terms of Service. Since nobody else can generate
-   * the signature, you can rely on it to verify that the information
-   * came from Facebook.
-   *
-   * @param $params_array   an array of all Facebook-sent parameters,
-   *                        NOT INCLUDING the signature itself
-   * @param $secret         your app's secret key
-   *
-   * @return a hash to be checked against the signature provided by Facebook
-   */
-  public static function generate_sig($params_array, $secret) {
-    $str = '';
-
-    ksort($params_array);
-    // Note: make sure that the signature parameter is not already included in
-    //       $params_array.
-    foreach ($params_array as $k=>$v) {
-      $str .= "$k=$v";
-    }
-    $str .= $secret;
-
-    return md5($str);
-  }
-
-  public function encode_validationError($summary, $message) {
-    return json_encode(
-               array('errorCode'    => FACEBOOK_API_VALIDATION_ERROR,
-                     'errorTitle'   => $summary,
-                     'errorMessage' => $message));
-  }
-
-  public function encode_multiFeedStory($feed, $next) {
-    return json_encode(
-               array('method'   => 'multiFeedStory',
-                     'content'  =>
-                     array('next' => $next,
-                           'feed' => $feed)));
-  }
-
-  public function encode_feedStory($feed, $next) {
-    return json_encode(
-               array('method'   => 'feedStory',
-                     'content'  =>
-                     array('next' => $next,
-                           'feed' => $feed)));
-  }
-
-  public function create_templatizedFeedStory($title_template, $title_data=array(),
-                                    $body_template='', $body_data = array(), $body_general=null,
-                                    $image_1=null, $image_1_link=null,
-                                    $image_2=null, $image_2_link=null,
-                                    $image_3=null, $image_3_link=null,
-                                    $image_4=null, $image_4_link=null) {
-    return array('title_template'=> $title_template,
-                 'title_data'   => $title_data,
-                 'body_template'=> $body_template,
-                 'body_data'    => $body_data,
-                 'body_general' => $body_general,
-                 'image_1'      => $image_1,
-                 'image_1_link' => $image_1_link,
-                 'image_2'      => $image_2,
-                 'image_2_link' => $image_2_link,
-                 'image_3'      => $image_3,
-                 'image_3_link' => $image_3_link,
-                 'image_4'      => $image_4,
-                 'image_4_link' => $image_4_link);
-  }
-
-
-}
-
diff --git a/extlib/facebook/facebook_desktop.php b/extlib/facebook/facebook_desktop.php
deleted file mode 100644 (file)
index e79a2ca..0000000
+++ /dev/null
@@ -1,104 +0,0 @@
-<?php
-// Copyright 2004-2009 Facebook. All Rights Reserved.
-//
-// +---------------------------------------------------------------------------+
-// | Facebook Platform PHP5 client                                             |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007 Facebook, Inc.                                         |
-// | All rights reserved.                                                      |
-// |                                                                           |
-// | Redistribution and use in source and binary forms, with or without        |
-// | modification, are permitted provided that the following conditions        |
-// | are met:                                                                  |
-// |                                                                           |
-// | 1. Redistributions of source code must retain the above copyright         |
-// |    notice, this list of conditions and the following disclaimer.          |
-// | 2. Redistributions in binary form must reproduce the above copyright      |
-// |    notice, this list of conditions and the following disclaimer in the    |
-// |    documentation and/or other materials provided with the distribution.   |
-// |                                                                           |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
-// +---------------------------------------------------------------------------+
-// | For help with this library, contact developers-help@facebook.com          |
-// +---------------------------------------------------------------------------+
-//
-
-/**
- *  This class extends and modifies the "Facebook" class to better
- *  suit desktop apps.
- */
-class FacebookDesktop extends Facebook {
-  // the application secret, which differs from the session secret
-  public $app_secret;
-  public $verify_sig;
-
-  public function __construct($api_key, $secret) {
-    $this->app_secret = $secret;
-    $this->verify_sig = false;
-    parent::__construct($api_key, $secret);
-  }
-
-  public function do_get_session($auth_token) {
-    $this->api_client->secret = $this->app_secret;
-    $this->api_client->session_key = null;
-    $session_info = parent::do_get_session($auth_token);
-    if (!empty($session_info['secret'])) {
-      // store the session secret
-      $this->set_session_secret($session_info['secret']);
-    }
-    return $session_info;
-  }
-
-  public function set_session_secret($session_secret) {
-    $this->secret = $session_secret;
-    $this->api_client->secret = $session_secret;
-  }
-
-  public function require_login() {
-    if ($this->get_loggedin_user()) {
-      try {
-        // try a session-based API call to ensure that we have the correct
-        // session secret
-        $user = $this->api_client->users_getLoggedInUser();
-
-        // now that we have a valid session secret, verify the signature
-        $this->verify_sig = true;
-        if ($this->validate_fb_params(false)) {
-          return $user;
-        } else {
-          // validation failed
-          return null;
-        }
-      } catch (FacebookRestClientException $ex) {
-        if (isset($_GET['auth_token'])) {
-          // if we have an auth_token, use it to establish a session
-          $session_info = $this->do_get_session($_GET['auth_token']);
-          if ($session_info) {
-            return $session_info['uid'];
-          }
-        }
-      }
-    }
-    // if we get here, we need to redirect the user to log in
-    $this->redirect($this->get_login_url(self::current_url(), $this->in_fb_canvas()));
-  }
-
-  public function verify_signature($fb_params, $expected_sig) {
-    // we don't want to verify the signature until we have a valid
-    // session secret
-    if ($this->verify_sig) {
-      return parent::verify_signature($fb_params, $expected_sig);
-    } else {
-      return true;
-    }
-  }
-}
diff --git a/extlib/facebook/facebookapi_php5_restlib.php b/extlib/facebook/facebookapi_php5_restlib.php
deleted file mode 100755 (executable)
index 55cb7fb..0000000
+++ /dev/null
@@ -1,3618 +0,0 @@
-<?php
-// Copyright 2004-2009 Facebook. All Rights Reserved.
-//
-// +---------------------------------------------------------------------------+
-// | Facebook Platform PHP5 client                                             |
-// +---------------------------------------------------------------------------+
-// | Copyright (c) 2007-2009 Facebook, Inc.                                    |
-// | All rights reserved.                                                      |
-// |                                                                           |
-// | Redistribution and use in source and binary forms, with or without        |
-// | modification, are permitted provided that the following conditions        |
-// | are met:                                                                  |
-// |                                                                           |
-// | 1. Redistributions of source code must retain the above copyright         |
-// |    notice, this list of conditions and the following disclaimer.          |
-// | 2. Redistributions in binary form must reproduce the above copyright      |
-// |    notice, this list of conditions and the following disclaimer in the    |
-// |    documentation and/or other materials provided with the distribution.   |
-// |                                                                           |
-// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
-// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
-// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
-// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
-// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
-// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
-// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
-// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
-// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
-// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
-// +---------------------------------------------------------------------------+
-// | For help with this library, contact developers-help@facebook.com          |
-// +---------------------------------------------------------------------------+
-//
-
-include_once 'jsonwrapper/jsonwrapper.php';
-
-class FacebookRestClient {
-  public $secret;
-  public $session_key;
-  public $api_key;
-  // to save making the friends.get api call, this will get prepopulated on
-  // canvas pages
-  public $friends_list;
-  public $user;
-  // to save making the pages.isAppAdded api call, this will get prepopulated
-  // on canvas pages
-  public $added;
-  public $is_user;
-  // we don't pass friends list to iframes, but we want to make
-  // friends_get really simple in the canvas_user (non-logged in) case.
-  // So we use the canvas_user as default arg to friends_get
-  public $canvas_user;
-  public $batch_mode;
-  private $batch_queue;
-  private $pending_batch;
-  private $call_as_apikey;
-  private $use_curl_if_available;
-  private $format = null;
-
-  const BATCH_MODE_DEFAULT = 0;
-  const BATCH_MODE_SERVER_PARALLEL = 0;
-  const BATCH_MODE_SERIAL_ONLY = 2;
-
-  /**
-   * Create the client.
-   * @param string $session_key if you haven't gotten a session key yet, leave
-   *                            this as null and then set it later by just
-   *                            directly accessing the $session_key member
-   *                            variable.
-   */
-  public function __construct($api_key, $secret, $session_key=null) {
-    $this->secret       = $secret;
-    $this->session_key  = $session_key;
-    $this->api_key      = $api_key;
-    $this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT;
-    $this->last_call_id = 0;
-    $this->call_as_apikey = '';
-    $this->use_curl_if_available = true;
-    $this->server_addr  = Facebook::get_facebook_url('api') . '/restserver.php';
-
-    if (!empty($GLOBALS['facebook_config']['debug'])) {
-      $this->cur_id = 0;
-      ?>
-<script type="text/javascript">
-var types = ['params', 'xml', 'php', 'sxml'];
-function getStyle(elem, style) {
-  if (elem.getStyle) {
-    return elem.getStyle(style);
-  } else {
-    return elem.style[style];
-  }
-}
-function setStyle(elem, style, value) {
-  if (elem.setStyle) {
-    elem.setStyle(style, value);
-  } else {
-    elem.style[style] = value;
-  }
-}
-function toggleDisplay(id, type) {
-  for (var i = 0; i < types.length; i++) {
-    var t = types[i];
-    var pre = document.getElementById(t + id);
-    if (pre) {
-      if (t != type || getStyle(pre, 'display') == 'block') {
-        setStyle(pre, 'display', 'none');
-      } else {
-        setStyle(pre, 'display', 'block');
-      }
-    }
-  }
-  return false;
-}
-</script>
-<?php
-    }
-  }
-
-  /**
-   * Set the default user id for methods that allow the caller
-   * to pass an uid parameter to identify the target user
-   * instead of a session key. This currently applies to
-   * the user preferences methods.
-   *
-   * @param $uid int the user id
-   */
-  public function set_user($uid) {
-    $this->user = $uid;
-  }
-
-  /**
-   * Normally, if the cURL library/PHP extension is available, it is used for
-   * HTTP transactions.  This allows that behavior to be overridden, falling
-   * back to a vanilla-PHP implementation even if cURL is installed.
-   *
-   * @param $use_curl_if_available bool whether or not to use cURL if available
-   */
-  public function set_use_curl_if_available($use_curl_if_available) {
-    $this->use_curl_if_available = $use_curl_if_available;
-  }
-
-  /**
-   * Start a batch operation.
-   */
-  public function begin_batch() {
-    if ($this->pending_batch()) {
-      $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
-      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
-      throw new FacebookRestClientException($description, $code);
-    }
-
-    $this->batch_queue = array();
-    $this->pending_batch = true;
-  }
-
-  /*
-   * End current batch operation
-   */
-  public function end_batch() {
-    if (!$this->pending_batch()) {
-      $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
-      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
-      throw new FacebookRestClientException($description, $code);
-    }
-
-    $this->pending_batch = false;
-
-    $this->execute_server_side_batch();
-    $this->batch_queue = null;
-  }
-
-  /**
-   * are we currently queueing up calls for a batch?
-   */
-  public function pending_batch() {
-    return $this->pending_batch;
-  }
-
-  private function execute_server_side_batch() {
-    $item_count = count($this->batch_queue);
-    $method_feed = array();
-    foreach ($this->batch_queue as $batch_item) {
-      $method = $batch_item['m'];
-      $params = $batch_item['p'];
-      list($get, $post) = $this->finalize_params($method, $params);
-      $method_feed[] = $this->create_url_string(array_merge($post, $get));
-    }
-
-    $serial_only =
-      ($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
-
-    $params = array('method_feed' => json_encode($method_feed),
-                    'serial_only' => $serial_only,
-                    'format' => $this->format);
-    $result = $this->call_method('facebook.batch.run', $params);
-
-    if (is_array($result) && isset($result['error_code'])) {
-      throw new FacebookRestClientException($result['error_msg'],
-                                            $result['error_code']);
-    }
-
-    for ($i = 0; $i < $item_count; $i++) {
-      $batch_item = $this->batch_queue[$i];
-      $batch_item['p']['format'] = $this->format;
-      $batch_item_result = $this->convert_result($result[$i],
-                                                 $batch_item['m'],
-                                                 $batch_item['p']);
-
-      if (is_array($batch_item_result) &&
-          isset($batch_item_result['error_code'])) {
-        throw new FacebookRestClientException($batch_item_result['error_msg'],
-                                              $batch_item_result['error_code']);
-      }
-      $batch_item['r'] = $batch_item_result;
-    }
-  }
-
-  public function begin_permissions_mode($permissions_apikey) {
-    $this->call_as_apikey = $permissions_apikey;
-  }
-
-  public function end_permissions_mode() {
-    $this->call_as_apikey = '';
-  }
-
-
-  /*
-   * If a page is loaded via HTTPS, then all images and static
-   * resources need to be printed with HTTPS urls to avoid
-   * mixed content warnings. If your page loads with an HTTPS
-   * url, then call set_use_ssl_resources to retrieve the correct
-   * urls.
-   */
-  public function set_use_ssl_resources($is_ssl = true) {
-    $this->use_ssl_resources = $is_ssl;
-  }
-
-  /**
-   * Returns public information for an application (as shown in the application
-   * directory) by either application ID, API key, or canvas page name.
-   *
-   * @param int $application_id              (Optional) app id
-   * @param string $application_api_key      (Optional) api key
-   * @param string $application_canvas_name  (Optional) canvas name
-   *
-   * Exactly one argument must be specified, otherwise it is an error.
-   *
-   * @return array  An array of public information about the application.
-   */
-  public function application_getPublicInfo($application_id=null,
-                                            $application_api_key=null,
-                                            $application_canvas_name=null) {
-    return $this->call_method('facebook.application.getPublicInfo',
-        array('application_id' => $application_id,
-              'application_api_key' => $application_api_key,
-              'application_canvas_name' => $application_canvas_name));
-  }
-
-  /**
-   * Creates an authentication token to be used as part of the desktop login
-   * flow.  For more information, please see
-   * http://wiki.developers.facebook.com/index.php/Auth.createToken.
-   *
-   * @return string  An authentication token.
-   */
-  public function auth_createToken() {
-    return $this->call_method('facebook.auth.createToken');
-  }
-
-  /**
-   * Returns the session information available after current user logs in.
-   *
-   * @param string $auth_token             the token returned by
-   *                                       auth_createToken or passed back to
-   *                                       your callback_url.
-   * @param bool $generate_session_secret  whether the session returned should
-   *                                       include a session secret
-   *
-   * @return array  An assoc array containing session_key, uid
-   */
-  public function auth_getSession($auth_token, $generate_session_secret=false) {
-    if (!$this->pending_batch()) {
-      $result = $this->call_method('facebook.auth.getSession',
-          array('auth_token' => $auth_token,
-                'generate_session_secret' => $generate_session_secret));
-      $this->session_key = $result['session_key'];
-
-    if (!empty($result['secret']) && !$generate_session_secret) {
-      // desktop apps have a special secret
-      $this->secret = $result['secret'];
-    }
-      return $result;
-    }
-  }
-
-  /**
-   * Generates a session-specific secret. This is for integration with
-   * client-side API calls, such as the JS library.
-   *
-   * @return array  A session secret for the current promoted session
-   *
-   * @error API_EC_PARAM_SESSION_KEY
-   *        API_EC_PARAM_UNKNOWN
-   */
-  public function auth_promoteSession() {
-      return $this->call_method('facebook.auth.promoteSession');
-  }
-
-  /**
-   * Expires the session that is currently being used.  If this call is
-   * successful, no further calls to the API (which require a session) can be
-   * made until a valid session is created.
-   *
-   * @return bool  true if session expiration was successful, false otherwise
-   */
-  public function auth_expireSession() {
-      return $this->call_method('facebook.auth.expireSession');
-  }
-
-  /**
-   *  Revokes the given extended permission that the user granted at some
-   *  prior time (for instance, offline_access or email).  If no user is
-   *  provided, it will be revoked for the user of the current session.
-   *
-   *  @param  string  $perm  The permission to revoke
-   *  @param  int     $uid   The user for whom to revoke the permission.
-   */
-  public function auth_revokeExtendedPermission($perm, $uid=null) {
-    return $this->call_method('facebook.auth.revokeExtendedPermission',
-        array('perm' => $perm, 'uid' => $uid));
-  }
-
-  /**
-   * Revokes the user's agreement to the Facebook Terms of Service for your
-   * application.  If you call this method for one of your users, you will no
-   * longer be able to make API requests on their behalf until they again
-   * authorize your application.  Use with care.  Note that if this method is
-   * called without a user parameter, then it will revoke access for the
-   * current session's user.
-   *
-   * @param int $uid  (Optional) User to revoke
-   *
-   * @return bool  true if revocation succeeds, false otherwise
-   */
-  public function auth_revokeAuthorization($uid=null) {
-      return $this->call_method('facebook.auth.revokeAuthorization',
-          array('uid' => $uid));
-  }
-
-  /**
-   * Get public key that is needed to verify digital signature
-   * an app may pass to other apps. The public key is only used by
-   * other apps for verification purposes.
-   * @param  string  API key of an app
-   * @return string  The public key for the app.
-   */
-  public function auth_getAppPublicKey($target_app_key) {
-    return $this->call_method('facebook.auth.getAppPublicKey',
-          array('target_app_key' => $target_app_key));
-  }
-
-  /**
-   * Get a structure that can be passed to another app
-   * as proof of session. The other app can verify it using public
-   * key of this app.
-   *
-   * @return signed public session data structure.
-   */
-  public function auth_getSignedPublicSessionData() {
-    return $this->call_method('facebook.auth.getSignedPublicSessionData',
-                              array());
-  }
-
-  /**
-   * Returns the number of unconnected friends that exist in this application.
-   * This number is determined based on the accounts registered through
-   * connect.registerUsers() (see below).
-   */
-  public function connect_getUnconnectedFriendsCount() {
-    return $this->call_method('facebook.connect.getUnconnectedFriendsCount',
-        array());
-  }
-
- /**
-  * This method is used to create an association between an external user
-  * account and a Facebook user account, as per Facebook Connect.
-  *
-  * This method takes an array of account data, including a required email_hash
-  * and optional account data. For each connected account, if the user exists,
-  * the information is added to the set of the user's connected accounts.
-  * If the user has already authorized the site, the connected account is added
-  * in the confirmed state. If the user has not yet authorized the site, the
-  * connected account is added in the pending state.
-  *
-  * This is designed to help Facebook Connect recognize when two Facebook
-  * friends are both members of a external site, but perhaps are not aware of
-  * it.  The Connect dialog (see fb:connect-form) is used when friends can be
-  * identified through these email hashes. See the following url for details:
-  *
-  *   http://wiki.developers.facebook.com/index.php/Connect.registerUsers
-  *
-  * @param mixed $accounts A (JSON-encoded) array of arrays, where each array
-  *                        has three properties:
-  *                        'email_hash'  (req) - public email hash of account
-  *                        'account_id'  (opt) - remote account id;
-  *                        'account_url' (opt) - url to remote account;
-  *
-  * @return array  The list of email hashes for the successfully registered
-  *                accounts.
-  */
-  public function connect_registerUsers($accounts) {
-    return $this->call_method('facebook.connect.registerUsers',
-        array('accounts' => $accounts));
-  }
-
- /**
-  * Unregisters a set of accounts registered using connect.registerUsers.
-  *
-  * @param array $email_hashes  The (JSON-encoded) list of email hashes to be
-  *                             unregistered.
-  *
-  * @return array  The list of email hashes which have been successfully
-  *                unregistered.
-  */
-  public function connect_unregisterUsers($email_hashes) {
-    return $this->call_method('facebook.connect.unregisterUsers',
-        array('email_hashes' => $email_hashes));
-  }
-
-  /**
-   * Returns events according to the filters specified.
-   *
-   * @param int $uid            (Optional) User associated with events. A null
-   *                            parameter will default to the session user.
-   * @param array/string $eids  (Optional) Filter by these event
-   *                            ids. A null parameter will get all events for
-   *                            the user. (A csv list will work but is deprecated)
-   * @param int $start_time     (Optional) Filter with this unix time as lower
-   *                            bound.  A null or zero parameter indicates no
-   *                            lower bound.
-   * @param int $end_time       (Optional) Filter with this UTC as upper bound.
-   *                            A null or zero parameter indicates no upper
-   *                            bound.
-   * @param string $rsvp_status (Optional) Only show events where the given uid
-   *                            has this rsvp status.  This only works if you
-   *                            have specified a value for $uid.  Values are as
-   *                            in events.getMembers.  Null indicates to ignore
-   *                            rsvp status when filtering.
-   *
-   * @return array  The events matching the query.
-   */
-  public function &events_get($uid=null,
-                              $eids=null,
-                              $start_time=null,
-                              $end_time=null,
-                              $rsvp_status=null) {
-    return $this->call_method('facebook.events.get',
-        array('uid' => $uid,
-              'eids' => $eids,
-              'start_time' => $start_time,
-              'end_time' => $end_time,
-              'rsvp_status' => $rsvp_status));
-  }
-
-  /**
-   * Returns membership list data associated with an event.
-   *
-   * @param int $eid  event id
-   *
-   * @return array  An assoc array of four membership lists, with keys
-   *                'attending', 'unsure', 'declined', and 'not_replied'
-   */
-  public function &events_getMembers($eid) {
-    return $this->call_method('facebook.events.getMembers',
-      array('eid' => $eid));
-  }
-
-  /**
-   * RSVPs the current user to this event.
-   *
-   * @param int $eid             event id
-   * @param string $rsvp_status  'attending', 'unsure', or 'declined'
-   *
-   * @return bool  true if successful
-   */
-  public function &events_rsvp($eid, $rsvp_status) {
-    return $this->call_method('facebook.events.rsvp',
-        array(
-        'eid' => $eid,
-        'rsvp_status' => $rsvp_status));
-  }
-
-  /**
-   * Cancels an event. Only works for events where application is the admin.
-   *
-   * @param int $eid                event id
-   * @param string $cancel_message  (Optional) message to send to members of
-   *                                the event about why it is cancelled
-   *
-   * @return bool  true if successful
-   */
-  public function &events_cancel($eid, $cancel_message='') {
-    return $this->call_method('facebook.events.cancel',
-        array('eid' => $eid,
-              'cancel_message' => $cancel_message));
-  }
-
-  /**
-   * Creates an event on behalf of the user is there is a session, otherwise on
-   * behalf of app.  Successful creation guarantees app will be admin.
-   *
-   * @param assoc array $event_info  json encoded event information
-   * @param string $file             (Optional) filename of picture to set
-   *
-   * @return int  event id
-   */
-  public function events_create($event_info, $file = null) {
-    if ($file) {
-      return $this->call_upload_method('facebook.events.create',
-        array('event_info' => $event_info),
-        $file,
-        Facebook::get_facebook_url('api-photo') . '/restserver.php');
-    } else {
-      return $this->call_method('facebook.events.create',
-        array('event_info' => $event_info));
-    }
-  }
-
-  /**
-   * Edits an existing event. Only works for events where application is admin.
-   *
-   * @param int $eid                 event id
-   * @param assoc array $event_info  json encoded event information
-   * @param string $file             (Optional) filename of new picture to set
-   *
-   * @return bool  true if successful
-   */
-  public function events_edit($eid, $event_info, $file = null) {
-    if ($file) {
-      return $this->call_upload_method('facebook.events.edit',
-        array('eid' => $eid, 'event_info' => $event_info),
-        $file,
-        Facebook::get_facebook_url('api-photo') . '/restserver.php');
-    } else {
-      return $this->call_method('facebook.events.edit',
-        array('eid' => $eid,
-        'event_info' => $event_info));
-    }
-  }
-
-  /**
-   * Fetches and re-caches the image stored at the given URL, for use in images
-   * published to non-canvas pages via the API (for example, to user profiles
-   * via profile.setFBML, or to News Feed via feed.publishUserAction).
-   *
-   * @param string $url  The absolute URL from which to refresh the image.
-   *
-   * @return bool  true on success
-   */
-  public function &fbml_refreshImgSrc($url) {
-    return $this->call_method('facebook.fbml.refreshImgSrc',
-        array('url' => $url));
-  }
-
-  /**
-   * Fetches and re-caches the content stored at the given URL, for use in an
-   * fb:ref FBML tag.
-   *
-   * @param string $url  The absolute URL from which to fetch content. This URL
-   *                     should be used in a fb:ref FBML tag.
-   *
-   * @return bool  true on success
-   */
-  public function &fbml_refreshRefUrl($url) {
-    return $this->call_method('facebook.fbml.refreshRefUrl',
-        array('url' => $url));
-  }
-
-  /**
-   * Lets you insert text strings in their native language into the Facebook
-   * Translations database so they can be translated.
-   *
-   * @param array $native_strings  An array of maps, where each map has a 'text'
-   *                               field and a 'description' field.
-   *
-   * @return int  Number of strings uploaded.
-   */
-  public function &fbml_uploadNativeStrings($native_strings) {
-    return $this->call_method('facebook.fbml.uploadNativeStrings',
-        array('native_strings' => json_encode($native_strings)));
-  }
-
-  /**
-   * Associates a given "handle" with FBML markup so that the handle can be
-   * used within the fb:ref FBML tag. A handle is unique within an application
-   * and allows an application to publish identical FBML to many user profiles
-   * and do subsequent updates without having to republish FBML on behalf of
-   * each user.
-   *
-   * @param string $handle  The handle to associate with the given FBML.
-   * @param string $fbml    The FBML to associate with the given handle.
-   *
-   * @return bool  true on success
-   */
-  public function &fbml_setRefHandle($handle, $fbml) {
-    return $this->call_method('facebook.fbml.setRefHandle',
-        array('handle' => $handle, 'fbml' => $fbml));
-  }
-
-  /**
-   * Register custom tags for the application. Custom tags can be used
-   * to extend the set of tags available to applications in FBML
-   * markup.
-   *
-   * Before you call this function,
-   * make sure you read the full documentation at
-   *
-   * http://wiki.developers.facebook.com/index.php/Fbml.RegisterCustomTags
-   *
-   * IMPORTANT: This function overwrites the values of
-   * existing tags if the names match. Use this function with care because
-   * it may break the FBML of any application that is using the
-   * existing version of the tags.
-   *
-   * @param mixed $tags an array of tag objects (the full description is on the
-   *   wiki page)
-   *
-   * @return int  the number of tags that were registered
-   */
-  public function &fbml_registerCustomTags($tags) {
-    $tags = json_encode($tags);
-    return $this->call_method('facebook.fbml.registerCustomTags',
-                              array('tags' => $tags));
-  }
-
-  /**
-   * Get the custom tags for an application. If $app_id
-   * is not specified, the calling app's tags are returned.
-   * If $app_id is different from the id of the calling app,
-   * only the app's public tags are returned.
-   * The return value is an array of the same type as
-   * the $tags parameter of fbml_registerCustomTags().
-   *
-   * @param int $app_id the application's id (optional)
-   *
-   * @return mixed  an array containing the custom tag  objects
-   */
-  public function &fbml_getCustomTags($app_id = null) {
-    return $this->call_method('facebook.fbml.getCustomTags',
-                              array('app_id' => $app_id));
-  }
-
-
-  /**
-   * Delete custom tags the application has registered. If
-   * $tag_names is null, all the application's custom tags will be
-   * deleted.
-   *
-   * IMPORTANT: If your application has registered public tags
-   * that other applications may be using, don't delete those tags!
-   * Doing so can break the FBML ofapplications that are using them.
-   *
-   * @param array $tag_names the names of the tags to delete (optinal)
-   * @return bool true on success
-   */
-  public function &fbml_deleteCustomTags($tag_names = null) {
-    return $this->call_method('facebook.fbml.deleteCustomTags',
-                              array('tag_names' => json_encode($tag_names)));
-  }
-
-
-
-  /**
-   * This method is deprecated for calls made on behalf of users. This method
-   * works only for publishing stories on a Facebook Page that has installed
-   * your application. To publish stories to a user's profile, use
-   * feed.publishUserAction instead.
-   *
-   * For more details on this call, please visit the wiki page:
-   *
-   * http://wiki.developers.facebook.com/index.php/Feed.publishTemplatizedAction
-   */
-  public function &feed_publishTemplatizedAction($title_template,
-                                                 $title_data,
-                                                 $body_template,
-                                                 $body_data,
-                                                 $body_general,
-                                                 $image_1=null,
-                                                 $image_1_link=null,
-                                                 $image_2=null,
-                                                 $image_2_link=null,
-                                                 $image_3=null,
-                                                 $image_3_link=null,
-                                                 $image_4=null,
-                                                 $image_4_link=null,
-                                                 $target_ids='',
-                                                 $page_actor_id=null) {
-    return $this->call_method('facebook.feed.publishTemplatizedAction',
-      array('title_template' => $title_template,
-            'title_data' => $title_data,
-            'body_template' => $body_template,
-            'body_data' => $body_data,
-            'body_general' => $body_general,
-            'image_1' => $image_1,
-            'image_1_link' => $image_1_link,
-            'image_2' => $image_2,
-            'image_2_link' => $image_2_link,
-            'image_3' => $image_3,
-            'image_3_link' => $image_3_link,
-            'image_4' => $image_4,
-            'image_4_link' => $image_4_link,
-            'target_ids' => $target_ids,
-            'page_actor_id' => $page_actor_id));
-  }
-
-  /**
-   * Registers a template bundle.  Template bundles are somewhat involved, so
-   * it's recommended you check out the wiki for more details:
-   *
-   *  http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle
-   *
-   * @return string  A template bundle id
-   */
-  public function &feed_registerTemplateBundle($one_line_story_templates,
-                                               $short_story_templates = array(),
-                                               $full_story_template = null,
-                                               $action_links = array()) {
-
-    $one_line_story_templates = json_encode($one_line_story_templates);
-
-    if (!empty($short_story_templates)) {
-      $short_story_templates = json_encode($short_story_templates);
-    }
-
-    if (isset($full_story_template)) {
-      $full_story_template = json_encode($full_story_template);
-    }
-
-    if (isset($action_links)) {
-      $action_links = json_encode($action_links);
-    }
-
-    return $this->call_method('facebook.feed.registerTemplateBundle',
-        array('one_line_story_templates' => $one_line_story_templates,
-              'short_story_templates' => $short_story_templates,
-              'full_story_template' => $full_story_template,
-              'action_links' => $action_links));
-  }
-
-  /**
-   * Retrieves the full list of active template bundles registered by the
-   * requesting application.
-   *
-   * @return array  An array of template bundles
-   */
-  public function &feed_getRegisteredTemplateBundles() {
-    return $this->call_method('facebook.feed.getRegisteredTemplateBundles',
-        array());
-  }
-
-  /**
-   * Retrieves information about a specified template bundle previously
-   * registered by the requesting application.
-   *
-   * @param string $template_bundle_id  The template bundle id
-   *
-   * @return array  Template bundle
-   */
-  public function &feed_getRegisteredTemplateBundleByID($template_bundle_id) {
-    return $this->call_method('facebook.feed.getRegisteredTemplateBundleByID',
-        array('template_bundle_id' => $template_bundle_id));
-  }
-
-  /**
-   * Deactivates a previously registered template bundle.
-   *
-   * @param string $template_bundle_id  The template bundle id
-   *
-   * @return bool  true on success
-   */
-  public function &feed_deactivateTemplateBundleByID($template_bundle_id) {
-    return $this->call_method('facebook.feed.deactivateTemplateBundleByID',
-        array('template_bundle_id' => $template_bundle_id));
-  }
-
-  const STORY_SIZE_ONE_LINE = 1;
-  const STORY_SIZE_SHORT = 2;
-  const STORY_SIZE_FULL = 4;
-
-  /**
-   * Publishes a story on behalf of the user owning the session, using the
-   * specified template bundle. This method requires an active session key in
-   * order to be called.
-   *
-   * The parameters to this method ($templata_data in particular) are somewhat
-   * involved.  It's recommended you visit the wiki for details:
-   *
-   *  http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
-   *
-   * @param int $template_bundle_id  A template bundle id previously registered
-   * @param array $template_data     See wiki article for syntax
-   * @param array $target_ids        (Optional) An array of friend uids of the
-   *                                 user who shared in this action.
-   * @param string $body_general     (Optional) Additional markup that extends
-   *                                 the body of a short story.
-   * @param int $story_size          (Optional) A story size (see above)
-   * @param string $user_message     (Optional) A user message for a short
-   *                                 story.
-   *
-   * @return bool  true on success
-   */
-  public function &feed_publishUserAction(
-      $template_bundle_id, $template_data, $target_ids='', $body_general='',
-      $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE,
-      $user_message='') {
-
-    if (is_array($template_data)) {
-      $template_data = json_encode($template_data);
-    } // allow client to either pass in JSON or an assoc that we JSON for them
-
-    if (is_array($target_ids)) {
-      $target_ids = json_encode($target_ids);
-      $target_ids = trim($target_ids, "[]"); // we don't want square brackets
-    }
-
-    return $this->call_method('facebook.feed.publishUserAction',
-        array('template_bundle_id' => $template_bundle_id,
-              'template_data' => $template_data,
-              'target_ids' => $target_ids,
-              'body_general' => $body_general,
-              'story_size' => $story_size,
-              'user_message' => $user_message));
-  }
-
-
-  /**
-   * Publish a post to the user's stream.
-   *
-   * @param $message        the user's message
-   * @param $attachment     the post's attachment (optional)
-   * @param $action links   the post's action links (optional)
-   * @param $target_id      the user on whose wall the post will be posted
-   *                        (optional)
-   * @param $uid            the actor (defaults to session user)
-   * @return string the post id
-   */
-  public function stream_publish(
-    $message, $attachment = null, $action_links = null, $target_id = null,
-    $uid = null) {
-
-    return $this->call_method(
-      'facebook.stream.publish',
-      array('message' => $message,
-            'attachment' => $attachment,
-            'action_links' => $action_links,
-            'target_id' => $target_id,
-            'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Remove a post from the user's stream.
-   * Currently, you may only remove stories you application created.
-   *
-   * @param $post_id  the post id
-   * @param $uid      the actor (defaults to session user)
-   * @return bool
-   */
-  public function stream_remove($post_id, $uid = null) {
-    return $this->call_method(
-      'facebook.stream.remove',
-      array('post_id' => $post_id,
-            'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Add a comment to a stream post
-   *
-   * @param $post_id  the post id
-   * @param $comment  the comment text
-   * @param $uid      the actor (defaults to session user)
-   * @return string the id of the created comment
-   */
-  public function stream_addComment($post_id, $comment, $uid = null) {
-    return $this->call_method(
-      'facebook.stream.addComment',
-      array('post_id' => $post_id,
-            'comment' => $comment,
-            'uid' => $this->get_uid($uid)));
-  }
-
-
-  /**
-   * Remove a comment from a stream post
-   *
-   * @param $comment_id  the comment id
-   * @param $uid      the actor (defaults to session user)
-   * @return bool
-   */
-  public function stream_removeComment($comment_id, $uid = null) {
-    return $this->call_method(
-      'facebook.stream.removeComment',
-      array('comment_id' => $comment_id,
-            'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Add a like to a stream post
-   *
-   * @param $post_id  the post id
-   * @param $uid      the actor (defaults to session user)
-   * @return bool
-   */
-  public function stream_addLike($post_id, $uid = null) {
-    return $this->call_method(
-      'facebook.stream.addLike',
-      array('post_id' => $post_id,
-            'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Remove a like from a stream post
-   *
-   * @param $post_id  the post id
-   * @param $uid      the actor (defaults to session user)
-   * @return bool
-   */
-  public function stream_removeLike($post_id, $uid = null) {
-    return $this->call_method(
-      'facebook.stream.removeLike',
-      array('post_id' => $post_id,
-            'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * For the current user, retrieves stories generated by the user's friends
-   * while using this application.  This can be used to easily create a
-   * "News Feed" like experience.
-   *
-   * @return array  An array of feed story objects.
-   */
-  public function &feed_getAppFriendStories() {
-    return $this->call_method('facebook.feed.getAppFriendStories');
-  }
-
-  /**
-   * Makes an FQL query.  This is a generalized way of accessing all the data
-   * in the API, as an alternative to most of the other method calls.  More
-   * info at http://wiki.developers.facebook.com/index.php/FQL
-   *
-   * @param string $query  the query to evaluate
-   *
-   * @return array  generalized array representing the results
-   */
-  public function &fql_query($query) {
-    return $this->call_method('facebook.fql.query',
-      array('query' => $query));
-  }
-
-  /**
-   * Makes a set of FQL queries in parallel.  This method takes a dictionary
-   * of FQL queries where the keys are names for the queries.  Results from
-   * one query can be used within another query to fetch additional data.  More
-   * info about FQL queries at http://wiki.developers.facebook.com/index.php/FQL
-   *
-   * @param string $queries  JSON-encoded dictionary of queries to evaluate
-   *
-   * @return array  generalized array representing the results
-   */
-  public function &fql_multiquery($queries) {
-    return $this->call_method('facebook.fql.multiquery',
-      array('queries' => $queries));
-  }
-
-  /**
-   * Returns whether or not pairs of users are friends.
-   * Note that the Facebook friend relationship is symmetric.
-   *
-   * @param array/string $uids1  list of ids (id_1, id_2,...)
-   *                       of some length X (csv is deprecated)
-   * @param array/string $uids2  list of ids (id_A, id_B,...)
-   *                       of SAME length X (csv is deprecated)
-   *
-   * @return array  An array with uid1, uid2, and bool if friends, e.g.:
-   *   array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1),
-   *         1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0)
-   *         ...)
-   * @error
-   *    API_EC_PARAM_USER_ID_LIST
-   */
-  public function &friends_areFriends($uids1, $uids2) {
-    return $this->call_method('facebook.friends.areFriends',
-                 array('uids1' => $uids1,
-                       'uids2' => $uids2));
-  }
-
-  /**
-   * Returns the friends of the current session user.
-   *
-   * @param int $flid  (Optional) Only return friends on this friend list.
-   * @param int $uid   (Optional) Return friends for this user.
-   *
-   * @return array  An array of friends
-   */
-  public function &friends_get($flid=null, $uid = null) {
-    if (isset($this->friends_list)) {
-      return $this->friends_list;
-    }
-    $params = array();
-    if (!$uid && isset($this->canvas_user)) {
-      $uid = $this->canvas_user;
-    }
-    if ($uid) {
-      $params['uid'] = $uid;
-    }
-    if ($flid) {
-      $params['flid'] = $flid;
-    }
-    return $this->call_method('facebook.friends.get', $params);
-
-  }
-
-  /**
-   * Returns the mutual friends between the target uid and a source uid or
-   * the current session user.
-   *
-   * @param int $target_uid Target uid for which mutual friends will be found.
-   * @param int $source_uid (optional) Source uid for which mutual friends will
-   *                                   be found. If no source_uid is specified,
-   *                                   source_id will default to the session
-   *                                   user.
-   * @return array  An array of friend uids
-   */
-  public function &friends_getMutualFriends($target_uid, $source_uid = null) {
-    return $this->call_method('facebook.friends.getMutualFriends',
-                              array("target_uid" => $target_uid,
-                                    "source_uid" => $source_uid));
-  }
-
-  /**
-   * Returns the set of friend lists for the current session user.
-   *
-   * @return array  An array of friend list objects
-   */
-  public function &friends_getLists() {
-    return $this->call_method('facebook.friends.getLists');
-  }
-
-  /**
-   * Returns the friends of the session user, who are also users
-   * of the calling application.
-   *
-   * @return array  An array of friends also using the app
-   */
-  public function &friends_getAppUsers() {
-    return $this->call_method('facebook.friends.getAppUsers');
-  }
-
-  /**
-   * Returns groups according to the filters specified.
-   *
-   * @param int $uid     (Optional) User associated with groups.  A null
-   *                     parameter will default to the session user.
-   * @param array/string $gids (Optional) Array of group ids to query. A null
-   *                     parameter will get all groups for the user.
-   *                     (csv is deprecated)
-   *
-   * @return array  An array of group objects
-   */
-  public function &groups_get($uid, $gids) {
-    return $this->call_method('facebook.groups.get',
-        array('uid' => $uid,
-              'gids' => $gids));
-  }
-
-  /**
-   * Returns the membership list of a group.
-   *
-   * @param int $gid  Group id
-   *
-   * @return array  An array with four membership lists, with keys 'members',
-   *                'admins', 'officers', and 'not_replied'
-   */
-  public function &groups_getMembers($gid) {
-    return $this->call_method('facebook.groups.getMembers',
-      array('gid' => $gid));
-  }
-
-  /**
-   * Returns cookies according to the filters specified.
-   *
-   * @param int $uid     User for which the cookies are needed.
-   * @param string $name (Optional) A null parameter will get all cookies
-   *                     for the user.
-   *
-   * @return array  Cookies!  Nom nom nom nom nom.
-   */
-  public function data_getCookies($uid, $name) {
-    return $this->call_method('facebook.data.getCookies',
-        array('uid' => $uid,
-              'name' => $name));
-  }
-
-  /**
-   * Sets cookies according to the params specified.
-   *
-   * @param int $uid       User for which the cookies are needed.
-   * @param string $name   Name of the cookie
-   * @param string $value  (Optional) if expires specified and is in the past
-   * @param int $expires   (Optional) Expiry time
-   * @param string $path   (Optional) Url path to associate with (default is /)
-   *
-   * @return bool  true on success
-   */
-  public function data_setCookie($uid, $name, $value, $expires, $path) {
-    return $this->call_method('facebook.data.setCookie',
-        array('uid' => $uid,
-              'name' => $name,
-              'value' => $value,
-              'expires' => $expires,
-              'path' => $path));
-  }
-
-  /**
-   * Retrieves links posted by the given user.
-   *
-   * @param int    $uid      The user whose links you wish to retrieve
-   * @param int    $limit    The maximimum number of links to retrieve
-   * @param array $link_ids (Optional) Array of specific link
-   *                          IDs to retrieve by this user
-   *
-   * @return array  An array of links.
-   */
-  public function &links_get($uid, $limit, $link_ids = null) {
-    return $this->call_method('links.get',
-        array('uid' => $uid,
-              'limit' => $limit,
-              'link_ids' => $link_ids));
-  }
-
-  /**
-   * Posts a link on Facebook.
-   *
-   * @param string $url     URL/link you wish to post
-   * @param string $comment (Optional) A comment about this link
-   * @param int    $uid     (Optional) User ID that is posting this link;
-   *                        defaults to current session user
-   *
-   * @return bool
-   */
-  public function &links_post($url, $comment='', $uid = null) {
-    return $this->call_method('links.post',
-        array('uid' => $uid,
-              'url' => $url,
-              'comment' => $comment));
-  }
-
-  /**
-   * Permissions API
-   */
-
-  /**
-   * Checks API-access granted by self to the specified application.
-   *
-   * @param string $permissions_apikey  Other application key
-   *
-   * @return array  API methods/namespaces which are allowed access
-   */
-  public function permissions_checkGrantedApiAccess($permissions_apikey) {
-    return $this->call_method('facebook.permissions.checkGrantedApiAccess',
-        array('permissions_apikey' => $permissions_apikey));
-  }
-
-  /**
-   * Checks API-access granted to self by the specified application.
-   *
-   * @param string $permissions_apikey  Other application key
-   *
-   * @return array  API methods/namespaces which are allowed access
-   */
-  public function permissions_checkAvailableApiAccess($permissions_apikey) {
-    return $this->call_method('facebook.permissions.checkAvailableApiAccess',
-        array('permissions_apikey' => $permissions_apikey));
-  }
-
-  /**
-   * Grant API-access to the specified methods/namespaces to the specified
-   * application.
-   *
-   * @param string $permissions_apikey  Other application key
-   * @param array(string) $method_arr   (Optional) API methods/namespaces
-   *                                    allowed
-   *
-   * @return array  API methods/namespaces which are allowed access
-   */
-  public function permissions_grantApiAccess($permissions_apikey, $method_arr) {
-    return $this->call_method('facebook.permissions.grantApiAccess',
-        array('permissions_apikey' => $permissions_apikey,
-              'method_arr' => $method_arr));
-  }
-
-  /**
-   * Revoke API-access granted to the specified application.
-   *
-   * @param string $permissions_apikey  Other application key
-   *
-   * @return bool  true on success
-   */
-  public function permissions_revokeApiAccess($permissions_apikey) {
-    return $this->call_method('facebook.permissions.revokeApiAccess',
-        array('permissions_apikey' => $permissions_apikey));
-  }
-
-  /**
-   * Payments Order API
-   */
-
-  /**
-   * Set Payments properties for an app.
-   *
-   * @param  properties  a map from property names to  values
-   * @return             true on success
-   */
-  public function payments_setProperties($properties) {
-    return $this->call_method ('facebook.payments.setProperties',
-        array('properties' => json_encode($properties)));
-  }
-
-  public function payments_getOrderDetails($order_id) {
-    return json_decode($this->call_method(
-        'facebook.payments.getOrderDetails',
-        array('order_id' => $order_id)), true);
-  }
-
-  public function payments_updateOrder($order_id, $status,
-                                         $params) {
-    return $this->call_method('facebook.payments.updateOrder',
-        array('order_id' => $order_id,
-              'status' => $status,
-              'params' => json_encode($params)));
-  }
-
-  public function payments_getOrders($status, $start_time,
-                                       $end_time, $test_mode=false) {
-    return json_decode($this->call_method('facebook.payments.getOrders',
-        array('status' => $status,
-              'start_time' => $start_time,
-              'end_time' => $end_time,
-              'test_mode' => $test_mode)), true);
-  }
-
-  /**
-   * Creates a note with the specified title and content.
-   *
-   * @param string $title   Title of the note.
-   * @param string $content Content of the note.
-   * @param int    $uid     (Optional) The user for whom you are creating a
-   *                        note; defaults to current session user
-   *
-   * @return int   The ID of the note that was just created.
-   */
-  public function &notes_create($title, $content, $uid = null) {
-    return $this->call_method('notes.create',
-        array('uid' => $uid,
-              'title' => $title,
-              'content' => $content));
-  }
-
-  /**
-   * Deletes the specified note.
-   *
-   * @param int $note_id  ID of the note you wish to delete
-   * @param int $uid      (Optional) Owner of the note you wish to delete;
-   *                      defaults to current session user
-   *
-   * @return bool
-   */
-  public function &notes_delete($note_id, $uid = null) {
-    return $this->call_method('notes.delete',
-        array('uid' => $uid,
-              'note_id' => $note_id));
-  }
-
-  /**
-   * Edits a note, replacing its title and contents with the title
-   * and contents specified.
-   *
-   * @param int    $note_id  ID of the note you wish to edit
-   * @param string $title    Replacement title for the note
-   * @param string $content  Replacement content for the note
-   * @param int    $uid      (Optional) Owner of the note you wish to edit;
-   *                         defaults to current session user
-   *
-   * @return bool
-   */
-  public function &notes_edit($note_id, $title, $content, $uid = null) {
-    return $this->call_method('notes.edit',
-        array('uid' => $uid,
-              'note_id' => $note_id,
-              'title' => $title,
-              'content' => $content));
-  }
-
-  /**
-   * Retrieves all notes by a user. If note_ids are specified,
-   * retrieves only those specific notes by that user.
-   *
-   * @param int    $uid      User whose notes you wish to retrieve
-   * @param array  $note_ids (Optional) List of specific note
-   *                         IDs by this user to retrieve
-   *
-   * @return array A list of all of the given user's notes, or an empty list
-   *               if the viewer lacks permissions or if there are no visible
-   *               notes.
-   */
-  public function &notes_get($uid, $note_ids = null) {
-    return $this->call_method('notes.get',
-        array('uid' => $uid,
-              'note_ids' => $note_ids));
-  }
-
-
-  /**
-   * Returns the outstanding notifications for the session user.
-   *
-   * @return array An assoc array of notification count objects for
-   *               'messages', 'pokes' and 'shares', a uid list of
-   *               'friend_requests', a gid list of 'group_invites',
-   *               and an eid list of 'event_invites'
-   */
-  public function &notifications_get() {
-    return $this->call_method('facebook.notifications.get');
-  }
-
-  /**
-   * Sends a notification to the specified users.
-   *
-   * @return A comma separated list of successful recipients
-   * @error
-   *    API_EC_PARAM_USER_ID_LIST
-   */
-  public function &notifications_send($to_ids, $notification, $type) {
-    return $this->call_method('facebook.notifications.send',
-        array('to_ids' => $to_ids,
-              'notification' => $notification,
-              'type' => $type));
-  }
-
-  /**
-   * Sends an email to the specified user of the application.
-   *
-   * @param array/string $recipients array of ids of the recipients (csv is deprecated)
-   * @param string $subject    subject of the email
-   * @param string $text       (plain text) body of the email
-   * @param string $fbml       fbml markup for an html version of the email
-   *
-   * @return string  A comma separated list of successful recipients
-   * @error
-   *    API_EC_PARAM_USER_ID_LIST
-   */
-  public function &notifications_sendEmail($recipients,
-                                           $subject,
-                                           $text,
-                                           $fbml) {
-    return $this->call_method('facebook.notifications.sendEmail',
-        array('recipients' => $recipients,
-              'subject' => $subject,
-              'text' => $text,
-              'fbml' => $fbml));
-  }
-
-  /**
-   * Returns the requested info fields for the requested set of pages.
-   *
-   * @param array/string $page_ids  an array of page ids (csv is deprecated)
-   * @param array/string  $fields    an array of strings describing the
-   *                           info fields desired (csv is deprecated)
-   * @param int    $uid       (Optional) limit results to pages of which this
-   *                          user is a fan.
-   * @param string type       limits results to a particular type of page.
-   *
-   * @return array  An array of pages
-   */
-  public function &pages_getInfo($page_ids, $fields, $uid, $type) {
-    return $this->call_method('facebook.pages.getInfo',
-        array('page_ids' => $page_ids,
-              'fields' => $fields,
-              'uid' => $uid,
-              'type' => $type));
-  }
-
-  /**
-   * Returns true if the given user is an admin for the passed page.
-   *
-   * @param int $page_id  target page id
-   * @param int $uid      (Optional) user id (defaults to the logged-in user)
-   *
-   * @return bool  true on success
-   */
-  public function &pages_isAdmin($page_id, $uid = null) {
-    return $this->call_method('facebook.pages.isAdmin',
-        array('page_id' => $page_id,
-              'uid' => $uid));
-  }
-
-  /**
-   * Returns whether or not the given page has added the application.
-   *
-   * @param int $page_id  target page id
-   *
-   * @return bool  true on success
-   */
-  public function &pages_isAppAdded($page_id) {
-    return $this->call_method('facebook.pages.isAppAdded',
-        array('page_id' => $page_id));
-  }
-
-  /**
-   * Returns true if logged in user is a fan for the passed page.
-   *
-   * @param int $page_id target page id
-   * @param int $uid user to compare.  If empty, the logged in user.
-   *
-   * @return bool  true on success
-   */
-  public function &pages_isFan($page_id, $uid = null) {
-    return $this->call_method('facebook.pages.isFan',
-        array('page_id' => $page_id,
-              'uid' => $uid));
-  }
-
-  /**
-   * Adds a tag with the given information to a photo. See the wiki for details:
-   *
-   *  http://wiki.developers.facebook.com/index.php/Photos.addTag
-   *
-   * @param int $pid          The ID of the photo to be tagged
-   * @param int $tag_uid      The ID of the user being tagged. You must specify
-   *                          either the $tag_uid or the $tag_text parameter
-   *                          (unless $tags is specified).
-   * @param string $tag_text  Some text identifying the person being tagged.
-   *                          You must specify either the $tag_uid or $tag_text
-   *                          parameter (unless $tags is specified).
-   * @param float $x          The horizontal position of the tag, as a
-   *                          percentage from 0 to 100, from the left of the
-   *                          photo.
-   * @param float $y          The vertical position of the tag, as a percentage
-   *                          from 0 to 100, from the top of the photo.
-   * @param array $tags       (Optional) An array of maps, where each map
-   *                          can contain the tag_uid, tag_text, x, and y
-   *                          parameters defined above.  If specified, the
-   *                          individual arguments are ignored.
-   * @param int $owner_uid    (Optional)  The user ID of the user whose photo
-   *                          you are tagging. If this parameter is not
-   *                          specified, then it defaults to the session user.
-   *
-   * @return bool  true on success
-   */
-  public function &photos_addTag($pid,
-                                 $tag_uid,
-                                 $tag_text,
-                                 $x,
-                                 $y,
-                                 $tags,
-                                 $owner_uid=0) {
-    return $this->call_method('facebook.photos.addTag',
-        array('pid' => $pid,
-              'tag_uid' => $tag_uid,
-              'tag_text' => $tag_text,
-              'x' => $x,
-              'y' => $y,
-              'tags' => (is_array($tags)) ? json_encode($tags) : null,
-              'owner_uid' => $this->get_uid($owner_uid)));
-  }
-
-  /**
-   * Creates and returns a new album owned by the specified user or the current
-   * session user.
-   *
-   * @param string $name         The name of the album.
-   * @param string $description  (Optional) A description of the album.
-   * @param string $location     (Optional) A description of the location.
-   * @param string $visible      (Optional) A privacy setting for the album.
-   *                             One of 'friends', 'friends-of-friends',
-   *                             'networks', or 'everyone'.  Default 'everyone'.
-   * @param int $uid             (Optional) User id for creating the album; if
-   *                             not specified, the session user is used.
-   *
-   * @return array  An album object
-   */
-  public function &photos_createAlbum($name,
-                                      $description='',
-                                      $location='',
-                                      $visible='',
-                                      $uid=0) {
-    return $this->call_method('facebook.photos.createAlbum',
-        array('name' => $name,
-              'description' => $description,
-              'location' => $location,
-              'visible' => $visible,
-              'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Returns photos according to the filters specified.
-   *
-   * @param int $subj_id  (Optional) Filter by uid of user tagged in the photos.
-   * @param int $aid      (Optional) Filter by an album, as returned by
-   *                      photos_getAlbums.
-   * @param array/string $pids   (Optional) Restrict to an array of pids
-   *                             (csv is deprecated)
-   *
-   * Note that at least one of these parameters needs to be specified, or an
-   * error is returned.
-   *
-   * @return array  An array of photo objects.
-   */
-  public function &photos_get($subj_id, $aid, $pids) {
-    return $this->call_method('facebook.photos.get',
-      array('subj_id' => $subj_id, 'aid' => $aid, 'pids' => $pids));
-  }
-
-  /**
-   * Returns the albums created by the given user.
-   *
-   * @param int $uid      (Optional) The uid of the user whose albums you want.
-   *                       A null will return the albums of the session user.
-   * @param string $aids  (Optional) An array of aids to restrict
-   *                       the query. (csv is deprecated)
-   *
-   * Note that at least one of the (uid, aids) parameters must be specified.
-   *
-   * @returns an array of album objects.
-   */
-  public function &photos_getAlbums($uid, $aids) {
-    return $this->call_method('facebook.photos.getAlbums',
-      array('uid' => $uid,
-            'aids' => $aids));
-  }
-
-  /**
-   * Returns the tags on all photos specified.
-   *
-   * @param string $pids  A list of pids to query
-   *
-   * @return array  An array of photo tag objects, which include pid,
-   *                subject uid, and two floating-point numbers (xcoord, ycoord)
-   *                for tag pixel location.
-   */
-  public function &photos_getTags($pids) {
-    return $this->call_method('facebook.photos.getTags',
-      array('pids' => $pids));
-  }
-
-  /**
-   * Uploads a photo.
-   *
-   * @param string $file     The location of the photo on the local filesystem.
-   * @param int $aid         (Optional) The album into which to upload the
-   *                         photo.
-   * @param string $caption  (Optional) A caption for the photo.
-   * @param int uid          (Optional) The user ID of the user whose photo you
-   *                         are uploading
-   *
-   * @return array  An array of user objects
-   */
-  public function photos_upload($file, $aid=null, $caption=null, $uid=null) {
-    return $this->call_upload_method('facebook.photos.upload',
-                                     array('aid' => $aid,
-                                           'caption' => $caption,
-                                           'uid' => $uid),
-                                     $file);
-  }
-
-
-  /**
-   * Uploads a video.
-   *
-   * @param  string $file        The location of the video on the local filesystem.
-   * @param  string $title       (Optional) A title for the video. Titles over 65 characters in length will be truncated.
-   * @param  string $description (Optional) A description for the video.
-   *
-   * @return array  An array with the video's ID, title, description, and a link to view it on Facebook.
-   */
-  public function video_upload($file, $title=null, $description=null) {
-    return $this->call_upload_method('facebook.video.upload',
-                                     array('title' => $title,
-                                           'description' => $description),
-                                     $file,
-                                     Facebook::get_facebook_url('api-video') . '/restserver.php');
-  }
-
-  /**
-   * Returns an array with the video limitations imposed on the current session's
-   * associated user. Maximum length is measured in seconds; maximum size is
-   * measured in bytes.
-   *
-   * @return array  Array with "length" and "size" keys
-   */
-  public function &video_getUploadLimits() {
-    return $this->call_method('facebook.video.getUploadLimits');
-  }
-
-  /**
-   * Returns the requested info fields for the requested set of users.
-   *
-   * @param array/string $uids    An array of user ids (csv is deprecated)
-   * @param array/string $fields  An array of info field names desired (csv is deprecated)
-   *
-   * @return array  An array of user objects
-   */
-  public function &users_getInfo($uids, $fields) {
-    return $this->call_method('facebook.users.getInfo',
-                  array('uids' => $uids,
-                        'fields' => $fields));
-  }
-
-  /**
-   * Returns the requested info fields for the requested set of users. A
-   * session key must not be specified. Only data about users that have
-   * authorized your application will be returned.
-   *
-   * Check the wiki for fields that can be queried through this API call.
-   * Data returned from here should not be used for rendering to application
-   * users, use users.getInfo instead, so that proper privacy rules will be
-   * applied.
-   *
-   * @param array/string $uids    An array of user ids (csv is deprecated)
-   * @param array/string $fields  An array of info field names desired (csv is deprecated)
-   *
-   * @return array  An array of user objects
-   */
-  public function &users_getStandardInfo($uids, $fields) {
-    return $this->call_method('facebook.users.getStandardInfo',
-                              array('uids' => $uids,
-                                    'fields' => $fields));
-  }
-
-  /**
-   * Returns the user corresponding to the current session object.
-   *
-   * @return integer  User id
-   */
-  public function &users_getLoggedInUser() {
-    return $this->call_method('facebook.users.getLoggedInUser');
-  }
-
-  /**
-   * Returns 1 if the user has the specified permission, 0 otherwise.
-   * http://wiki.developers.facebook.com/index.php/Users.hasAppPermission
-   *
-   * @return integer  1 or 0
-   */
-  public function &users_hasAppPermission($ext_perm, $uid=null) {
-    return $this->call_method('facebook.users.hasAppPermission',
-        array('ext_perm' => $ext_perm, 'uid' => $uid));
-  }
-
-  /**
-   * Returns whether or not the user corresponding to the current
-   * session object has the give the app basic authorization.
-   *
-   * @return boolean  true if the user has authorized the app
-   */
-  public function &users_isAppUser($uid=null) {
-    if ($uid === null && isset($this->is_user)) {
-      return $this->is_user;
-    }
-
-    return $this->call_method('facebook.users.isAppUser', array('uid' => $uid));
-  }
-
-  /**
-   * Returns whether or not the user corresponding to the current
-   * session object is verified by Facebook. See the documentation
-   * for Users.isVerified for details.
-   *
-   * @return boolean  true if the user is verified
-   */
-  public function &users_isVerified() {
-    return $this->call_method('facebook.users.isVerified');
-  }
-
-  /**
-   * Sets the users' current status message. Message does NOT contain the
-   * word "is" , so make sure to include a verb.
-   *
-   * Example: setStatus("is loving the API!")
-   * will produce the status "Luke is loving the API!"
-   *
-   * @param string $status                text-only message to set
-   * @param int    $uid                   user to set for (defaults to the
-   *                                      logged-in user)
-   * @param bool   $clear                 whether or not to clear the status,
-   *                                      instead of setting it
-   * @param bool   $status_includes_verb  if true, the word "is" will *not* be
-   *                                      prepended to the status message
-   *
-   * @return boolean
-   */
-  public function &users_setStatus($status,
-                                   $uid = null,
-                                   $clear = false,
-                                   $status_includes_verb = true) {
-    $args = array(
-      'status' => $status,
-      'uid' => $uid,
-      'clear' => $clear,
-      'status_includes_verb' => $status_includes_verb,
-    );
-    return $this->call_method('facebook.users.setStatus', $args);
-  }
-
-  /**
-   * Gets the comments for a particular xid. This is essentially a wrapper
-   * around the comment FQL table.
-   *
-   * @param string $xid external id associated with the comments
-   *
-   * @return array of comment objects
-   */
-  public function &comments_get($xid) {
-    $args = array('xid' => $xid);
-    return $this->call_method('facebook.comments.get', $args);
-  }
-
-  /**
-   * Add a comment to a particular xid on behalf of a user. If called
-   * without an app_secret (with session secret), this will only work
-   * for the session user.
-   *
-   * @param string $xid   external id associated with the comments
-   * @param string $text  text of the comment
-   * @param int    $uid   user adding the comment (def: session user)
-   * @param string $title optional title for the stream story
-   * @param string $url   optional url for the stream story
-   * @param bool   $publish_to_stream publish a feed story about this comment?
-   *                      a link will be generated to title/url in the story
-   *
-   * @return string comment_id associated with the comment
-   */
-  public function &comments_add($xid, $text, $uid=0, $title='', $url='',
-                                $publish_to_stream=false) {
-    $args = array(
-      'xid'               => $xid,
-      'uid'               => $this->get_uid($uid),
-      'text'              => $text,
-      'title'             => $title,
-      'url'               => $url,
-      'publish_to_stream' => $publish_to_stream);
-
-    return $this->call_method('facebook.comments.add', $args);
-  }
-
-  /**
-   * Remove a particular comment.
-   *
-   * @param string $xid        the external id associated with the comments
-   * @param string $comment_id id of the comment to remove (returned by
-   *                           comments.add and comments.get)
-   *
-   * @return boolean
-   */
-  public function &comments_remove($xid, $comment_id) {
-    $args = array(
-      'xid'        => $xid,
-      'comment_id' => $comment_id);
-    return $this->call_method('facebook.comments.remove', $args);
-  }
-
-  /**
-   * Gets the stream on behalf of a user using a set of users. This
-   * call will return the latest $limit queries between $start_time
-   * and $end_time.
-   *
-   * @param int    $viewer_id  user making the call (def: session)
-   * @param array  $source_ids users/pages to look at (def: all connections)
-   * @param int    $start_time start time to look for stories (def: 1 day ago)
-   * @param int    $end_time   end time to look for stories (def: now)
-   * @param int    $limit      number of stories to attempt to fetch (def: 30)
-   * @param string $filter_key key returned by stream.getFilters to fetch
-   * @param array  $metadata   metadata to include with the return, allows
-   *                           requested metadata to be returned, such as
-   *                           profiles, albums, photo_tags
-   *
-   * @return array(
-   *           'posts'      => array of posts,
-   *           // if requested, the following data may be returned
-   *           'profiles'   => array of profile metadata of users/pages in posts
-   *           'albums'     => array of album metadata in posts
-   *           'photo_tags' => array of photo_tags for photos in posts
-   *         )
-   */
-  public function &stream_get($viewer_id = null,
-                              $source_ids = null,
-                              $start_time = 0,
-                              $end_time = 0,
-                              $limit = 30,
-                              $filter_key = '') {
-    $args = array(
-      'viewer_id'  => $viewer_id,
-      'source_ids' => $source_ids,
-      'start_time' => $start_time,
-      'end_time'   => $end_time,
-      'limit'      => $limit,
-      'filter_key' => $filter_key);
-    return $this->call_method('facebook.stream.get', $args);
-  }
-
-  /**
-   * Gets the filters (with relevant filter keys for stream.get) for a
-   * particular user. These filters are typical things like news feed,
-   * friend lists, networks. They can be used to filter the stream
-   * without complex queries to determine which ids belong in which groups.
-   *
-   * @param int $uid user to get filters for
-   *
-   * @return array of stream filter objects
-   */
-  public function &stream_getFilters($uid = null) {
-    $args = array('uid' => $uid);
-    return $this->call_method('facebook.stream.getFilters', $args);
-  }
-
-  /**
-   * Gets the full comments given a post_id from stream.get or the
-   * stream FQL table. Initially, only a set of preview comments are
-   * returned because some posts can have many comments.
-   *
-   * @param string $post_id id of the post to get comments for
-   *
-   * @return array of comment objects
-   */
-  public function &stream_getComments($post_id) {
-    $args = array('post_id' => $post_id);
-    return $this->call_method('facebook.stream.getComments', $args);
-  }
-
-  /**
-   * Sets the FBML for the profile of the user attached to this session.
-   *
-   * @param   string   $markup           The FBML that describes the profile
-   *                                     presence of this app for the user
-   * @param   int      $uid              The user
-   * @param   string   $profile          Profile FBML
-   * @param   string   $profile_action   Profile action FBML (deprecated)
-   * @param   string   $mobile_profile   Mobile profile FBML
-   * @param   string   $profile_main     Main Tab profile FBML
-   *
-   * @return  array  A list of strings describing any compile errors for the
-   *                 submitted FBML
-   */
-  function profile_setFBML($markup,
-                           $uid=null,
-                           $profile='',
-                           $profile_action='',
-                           $mobile_profile='',
-                           $profile_main='') {
-    return $this->call_method('facebook.profile.setFBML',
-        array('markup' => $markup,
-              'uid' => $uid,
-              'profile' => $profile,
-              'profile_action' => $profile_action,
-              'mobile_profile' => $mobile_profile,
-              'profile_main' => $profile_main));
-  }
-
-  /**
-   * Gets the FBML for the profile box that is currently set for a user's
-   * profile (your application set the FBML previously by calling the
-   * profile.setFBML method).
-   *
-   * @param int $uid   (Optional) User id to lookup; defaults to session.
-   * @param int $type  (Optional) 1 for original style, 2 for profile_main boxes
-   *
-   * @return string  The FBML
-   */
-  public function &profile_getFBML($uid=null, $type=null) {
-    return $this->call_method('facebook.profile.getFBML',
-        array('uid' => $uid,
-              'type' => $type));
-  }
-
-  /**
-   * Returns the specified user's application info section for the calling
-   * application. These info sections have either been set via a previous
-   * profile.setInfo call or by the user editing them directly.
-   *
-   * @param int $uid  (Optional) User id to lookup; defaults to session.
-   *
-   * @return array  Info fields for the current user.  See wiki for structure:
-   *
-   *  http://wiki.developers.facebook.com/index.php/Profile.getInfo
-   *
-   */
-  public function &profile_getInfo($uid=null) {
-    return $this->call_method('facebook.profile.getInfo',
-        array('uid' => $uid));
-  }
-
-  /**
-   * Returns the options associated with the specified info field for an
-   * application info section.
-   *
-   * @param string $field  The title of the field
-   *
-   * @return array  An array of info options.
-   */
-  public function &profile_getInfoOptions($field) {
-    return $this->call_method('facebook.profile.getInfoOptions',
-        array('field' => $field));
-  }
-
-  /**
-   * Configures an application info section that the specified user can install
-   * on the Info tab of her profile.  For details on the structure of an info
-   * field, please see:
-   *
-   *  http://wiki.developers.facebook.com/index.php/Profile.setInfo
-   *
-   * @param string $title       Title / header of the info section
-   * @param int $type           1 for text-only, 5 for thumbnail views
-   * @param array $info_fields  An array of info fields. See wiki for details.
-   * @param int $uid            (Optional)
-   *
-   * @return bool  true on success
-   */
-  public function &profile_setInfo($title, $type, $info_fields, $uid=null) {
-    return $this->call_method('facebook.profile.setInfo',
-        array('uid' => $uid,
-              'type' => $type,
-              'title'   => $title,
-              'info_fields' => json_encode($info_fields)));
-  }
-
-  /**
-   * Specifies the objects for a field for an application info section. These
-   * options populate the typeahead for a thumbnail.
-   *
-   * @param string $field   The title of the field
-   * @param array $options  An array of items for a thumbnail, including
-   *                        'label', 'link', and optionally 'image',
-   *                        'description' and 'sublabel'
-   *
-   * @return bool  true on success
-   */
-  public function profile_setInfoOptions($field, $options) {
-    return $this->call_method('facebook.profile.setInfoOptions',
-        array('field'   => $field,
-              'options' => json_encode($options)));
-  }
-
-  /**
-   * Get all the marketplace categories.
-   *
-   * @return array  A list of category names
-   */
-  function marketplace_getCategories() {
-    return $this->call_method('facebook.marketplace.getCategories',
-        array());
-  }
-
-  /**
-   * Get all the marketplace subcategories for a particular category.
-   *
-   * @param  category  The category for which we are pulling subcategories
-   *
-   * @return array A list of subcategory names
-   */
-  function marketplace_getSubCategories($category) {
-    return $this->call_method('facebook.marketplace.getSubCategories',
-        array('category' => $category));
-  }
-
-  /**
-   * Get listings by either listing_id or user.
-   *
-   * @param listing_ids   An array of listing_ids (optional)
-   * @param uids          An array of user ids (optional)
-   *
-   * @return array  The data for matched listings
-   */
-  function marketplace_getListings($listing_ids, $uids) {
-    return $this->call_method('facebook.marketplace.getListings',
-        array('listing_ids' => $listing_ids, 'uids' => $uids));
-  }
-
-  /**
-   * Search for Marketplace listings.  All arguments are optional, though at
-   * least one must be filled out to retrieve results.
-   *
-   * @param category     The category in which to search (optional)
-   * @param subcategory  The subcategory in which to search (optional)
-   * @param query        A query string (optional)
-   *
-   * @return array  The data for matched listings
-   */
-  function marketplace_search($category, $subcategory, $query) {
-    return $this->call_method('facebook.marketplace.search',
-        array('category' => $category,
-              'subcategory' => $subcategory,
-              'query' => $query));
-  }
-
-  /**
-   * Remove a listing from Marketplace.
-   *
-   * @param listing_id  The id of the listing to be removed
-   * @param status      'SUCCESS', 'NOT_SUCCESS', or 'DEFAULT'
-   *
-   * @return bool  True on success
-   */
-  function marketplace_removeListing($listing_id,
-                                     $status='DEFAULT',
-                                     $uid=null) {
-    return $this->call_method('facebook.marketplace.removeListing',
-        array('listing_id' => $listing_id,
-              'status' => $status,
-              'uid' => $uid));
-  }
-
-  /**
-   * Create/modify a Marketplace listing for the loggedinuser.
-   *
-   * @param int              listing_id  The id of a listing to be modified, 0
-   *                                     for a new listing.
-   * @param show_on_profile  bool        Should we show this listing on the
-   *                                     user's profile
-   * @param listing_attrs    array       An array of the listing data
-   *
-   * @return int  The listing_id (unchanged if modifying an existing listing).
-   */
-  function marketplace_createListing($listing_id,
-                                     $show_on_profile,
-                                     $attrs,
-                                     $uid=null) {
-    return $this->call_method('facebook.marketplace.createListing',
-        array('listing_id' => $listing_id,
-              'show_on_profile' => $show_on_profile,
-              'listing_attrs' => json_encode($attrs),
-              'uid' => $uid));
-  }
-
-  /////////////////////////////////////////////////////////////////////////////
-  // Data Store API
-
-  /**
-   * Set a user preference.
-   *
-   * @param  pref_id    preference identifier (0-200)
-   * @param  value      preferece's value
-   * @param  uid        the user id (defaults to current session user)
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   *    API_EC_PERMISSION_OTHER_USER
-   */
-  public function &data_setUserPreference($pref_id, $value, $uid = null) {
-    return $this->call_method('facebook.data.setUserPreference',
-       array('pref_id' => $pref_id,
-             'value' => $value,
-             'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Set a user's all preferences for this application.
-   *
-   * @param  values     preferece values in an associative arrays
-   * @param  replace    whether to replace all existing preferences or
-   *                    merge into them.
-   * @param  uid        the user id (defaults to current session user)
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   *    API_EC_PERMISSION_OTHER_USER
-   */
-  public function &data_setUserPreferences($values,
-                                           $replace = false,
-                                           $uid = null) {
-    return $this->call_method('facebook.data.setUserPreferences',
-       array('values' => json_encode($values),
-             'replace' => $replace,
-             'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Get a user preference.
-   *
-   * @param  pref_id    preference identifier (0-200)
-   * @param  uid        the user id (defaults to current session user)
-   * @return            preference's value
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   *    API_EC_PERMISSION_OTHER_USER
-   */
-  public function &data_getUserPreference($pref_id, $uid = null) {
-    return $this->call_method('facebook.data.getUserPreference',
-       array('pref_id' => $pref_id,
-             'uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Get a user preference.
-   *
-   * @param  uid        the user id (defaults to current session user)
-   * @return            preference values
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   *    API_EC_PERMISSION_OTHER_USER
-   */
-  public function &data_getUserPreferences($uid = null) {
-    return $this->call_method('facebook.data.getUserPreferences',
-       array('uid' => $this->get_uid($uid)));
-  }
-
-  /**
-   * Create a new object type.
-   *
-   * @param  name       object type's name
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_createObjectType($name) {
-    return $this->call_method('facebook.data.createObjectType',
-       array('name' => $name));
-  }
-
-  /**
-   * Delete an object type.
-   *
-   * @param  obj_type       object type's name
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_dropObjectType($obj_type) {
-    return $this->call_method('facebook.data.dropObjectType',
-       array('obj_type' => $obj_type));
-  }
-
-  /**
-   * Rename an object type.
-   *
-   * @param  obj_type       object type's name
-   * @param  new_name       new object type's name
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_renameObjectType($obj_type, $new_name) {
-    return $this->call_method('facebook.data.renameObjectType',
-       array('obj_type' => $obj_type,
-             'new_name' => $new_name));
-  }
-
-  /**
-   * Add a new property to an object type.
-   *
-   * @param  obj_type       object type's name
-   * @param  prop_name      name of the property to add
-   * @param  prop_type      1: integer; 2: string; 3: text blob
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_defineObjectProperty($obj_type,
-                                             $prop_name,
-                                             $prop_type) {
-    return $this->call_method('facebook.data.defineObjectProperty',
-       array('obj_type' => $obj_type,
-             'prop_name' => $prop_name,
-             'prop_type' => $prop_type));
-  }
-
-  /**
-   * Remove a previously defined property from an object type.
-   *
-   * @param  obj_type      object type's name
-   * @param  prop_name     name of the property to remove
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_undefineObjectProperty($obj_type, $prop_name) {
-    return $this->call_method('facebook.data.undefineObjectProperty',
-       array('obj_type' => $obj_type,
-             'prop_name' => $prop_name));
-  }
-
-  /**
-   * Rename a previously defined property of an object type.
-   *
-   * @param  obj_type      object type's name
-   * @param  prop_name     name of the property to rename
-   * @param  new_name      new name to use
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_renameObjectProperty($obj_type, $prop_name,
-                                            $new_name) {
-    return $this->call_method('facebook.data.renameObjectProperty',
-       array('obj_type' => $obj_type,
-             'prop_name' => $prop_name,
-             'new_name' => $new_name));
-  }
-
-  /**
-   * Retrieve a list of all object types that have defined for the application.
-   *
-   * @return               a list of object type names
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getObjectTypes() {
-    return $this->call_method('facebook.data.getObjectTypes');
-  }
-
-  /**
-   * Get definitions of all properties of an object type.
-   *
-   * @param obj_type       object type's name
-   * @return               pairs of property name and property types
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getObjectType($obj_type) {
-    return $this->call_method('facebook.data.getObjectType',
-       array('obj_type' => $obj_type));
-  }
-
-  /**
-   * Create a new object.
-   *
-   * @param  obj_type      object type's name
-   * @param  properties    (optional) properties to set initially
-   * @return               newly created object's id
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_createObject($obj_type, $properties = null) {
-    return $this->call_method('facebook.data.createObject',
-       array('obj_type' => $obj_type,
-             'properties' => json_encode($properties)));
-  }
-
-  /**
-   * Update an existing object.
-   *
-   * @param  obj_id        object's id
-   * @param  properties    new properties
-   * @param  replace       true for replacing existing properties;
-   *                       false for merging
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_updateObject($obj_id, $properties, $replace = false) {
-    return $this->call_method('facebook.data.updateObject',
-       array('obj_id' => $obj_id,
-             'properties' => json_encode($properties),
-             'replace' => $replace));
-  }
-
-  /**
-   * Delete an existing object.
-   *
-   * @param  obj_id        object's id
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_deleteObject($obj_id) {
-    return $this->call_method('facebook.data.deleteObject',
-       array('obj_id' => $obj_id));
-  }
-
-  /**
-   * Delete a list of objects.
-   *
-   * @param  obj_ids       objects to delete
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_deleteObjects($obj_ids) {
-    return $this->call_method('facebook.data.deleteObjects',
-       array('obj_ids' => json_encode($obj_ids)));
-  }
-
-  /**
-   * Get a single property value of an object.
-   *
-   * @param  obj_id        object's id
-   * @param  prop_name     individual property's name
-   * @return               individual property's value
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getObjectProperty($obj_id, $prop_name) {
-    return $this->call_method('facebook.data.getObjectProperty',
-       array('obj_id' => $obj_id,
-             'prop_name' => $prop_name));
-  }
-
-  /**
-   * Get properties of an object.
-   *
-   * @param  obj_id      object's id
-   * @param  prop_names  (optional) properties to return; null for all.
-   * @return             specified properties of an object
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getObject($obj_id, $prop_names = null) {
-    return $this->call_method('facebook.data.getObject',
-       array('obj_id' => $obj_id,
-             'prop_names' => json_encode($prop_names)));
-  }
-
-  /**
-   * Get properties of a list of objects.
-   *
-   * @param  obj_ids     object ids
-   * @param  prop_names  (optional) properties to return; null for all.
-   * @return             specified properties of an object
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getObjects($obj_ids, $prop_names = null) {
-    return $this->call_method('facebook.data.getObjects',
-       array('obj_ids' => json_encode($obj_ids),
-             'prop_names' => json_encode($prop_names)));
-  }
-
-  /**
-   * Set a single property value of an object.
-   *
-   * @param  obj_id        object's id
-   * @param  prop_name     individual property's name
-   * @param  prop_value    new value to set
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_setObjectProperty($obj_id, $prop_name,
-                                         $prop_value) {
-    return $this->call_method('facebook.data.setObjectProperty',
-       array('obj_id' => $obj_id,
-             'prop_name' => $prop_name,
-             'prop_value' => $prop_value));
-  }
-
-  /**
-   * Read hash value by key.
-   *
-   * @param  obj_type      object type's name
-   * @param  key           hash key
-   * @param  prop_name     (optional) individual property's name
-   * @return               hash value
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getHashValue($obj_type, $key, $prop_name = null) {
-    return $this->call_method('facebook.data.getHashValue',
-       array('obj_type' => $obj_type,
-             'key' => $key,
-             'prop_name' => $prop_name));
-  }
-
-  /**
-   * Write hash value by key.
-   *
-   * @param  obj_type      object type's name
-   * @param  key           hash key
-   * @param  value         hash value
-   * @param  prop_name     (optional) individual property's name
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_setHashValue($obj_type,
-                                     $key,
-                                     $value,
-                                     $prop_name = null) {
-    return $this->call_method('facebook.data.setHashValue',
-       array('obj_type' => $obj_type,
-             'key' => $key,
-             'value' => $value,
-             'prop_name' => $prop_name));
-  }
-
-  /**
-   * Increase a hash value by specified increment atomically.
-   *
-   * @param  obj_type      object type's name
-   * @param  key           hash key
-   * @param  prop_name     individual property's name
-   * @param  increment     (optional) default is 1
-   * @return               incremented hash value
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_incHashValue($obj_type,
-                                     $key,
-                                     $prop_name,
-                                     $increment = 1) {
-    return $this->call_method('facebook.data.incHashValue',
-       array('obj_type' => $obj_type,
-             'key' => $key,
-             'prop_name' => $prop_name,
-             'increment' => $increment));
-  }
-
-  /**
-   * Remove a hash key and its values.
-   *
-   * @param  obj_type    object type's name
-   * @param  key         hash key
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_removeHashKey($obj_type, $key) {
-    return $this->call_method('facebook.data.removeHashKey',
-       array('obj_type' => $obj_type,
-             'key' => $key));
-  }
-
-  /**
-   * Remove hash keys and their values.
-   *
-   * @param  obj_type    object type's name
-   * @param  keys        hash keys
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_removeHashKeys($obj_type, $keys) {
-    return $this->call_method('facebook.data.removeHashKeys',
-       array('obj_type' => $obj_type,
-             'keys' => json_encode($keys)));
-  }
-
-  /**
-   * Define an object association.
-   *
-   * @param  name        name of this association
-   * @param  assoc_type  1: one-way 2: two-way symmetric 3: two-way asymmetric
-   * @param  assoc_info1 needed info about first object type
-   * @param  assoc_info2 needed info about second object type
-   * @param  inverse     (optional) name of reverse association
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_defineAssociation($name, $assoc_type, $assoc_info1,
-                                         $assoc_info2, $inverse = null) {
-    return $this->call_method('facebook.data.defineAssociation',
-       array('name' => $name,
-             'assoc_type' => $assoc_type,
-             'assoc_info1' => json_encode($assoc_info1),
-             'assoc_info2' => json_encode($assoc_info2),
-             'inverse' => $inverse));
-  }
-
-  /**
-   * Undefine an object association.
-   *
-   * @param  name        name of this association
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_undefineAssociation($name) {
-    return $this->call_method('facebook.data.undefineAssociation',
-       array('name' => $name));
-  }
-
-  /**
-   * Rename an object association or aliases.
-   *
-   * @param  name        name of this association
-   * @param  new_name    (optional) new name of this association
-   * @param  new_alias1  (optional) new alias for object type 1
-   * @param  new_alias2  (optional) new alias for object type 2
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_renameAssociation($name, $new_name, $new_alias1 = null,
-                                         $new_alias2 = null) {
-    return $this->call_method('facebook.data.renameAssociation',
-       array('name' => $name,
-             'new_name' => $new_name,
-             'new_alias1' => $new_alias1,
-             'new_alias2' => $new_alias2));
-  }
-
-  /**
-   * Get definition of an object association.
-   *
-   * @param  name        name of this association
-   * @return             specified association
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getAssociationDefinition($name) {
-    return $this->call_method('facebook.data.getAssociationDefinition',
-       array('name' => $name));
-  }
-
-  /**
-   * Get definition of all associations.
-   *
-   * @return             all defined associations
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getAssociationDefinitions() {
-    return $this->call_method('facebook.data.getAssociationDefinitions',
-       array());
-  }
-
-  /**
-   * Create or modify an association between two objects.
-   *
-   * @param  name        name of association
-   * @param  obj_id1     id of first object
-   * @param  obj_id2     id of second object
-   * @param  data        (optional) extra string data to store
-   * @param  assoc_time  (optional) extra time data; default to creation time
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_setAssociation($name, $obj_id1, $obj_id2, $data = null,
-                                      $assoc_time = null) {
-    return $this->call_method('facebook.data.setAssociation',
-       array('name' => $name,
-             'obj_id1' => $obj_id1,
-             'obj_id2' => $obj_id2,
-             'data' => $data,
-             'assoc_time' => $assoc_time));
-  }
-
-  /**
-   * Create or modify associations between objects.
-   *
-   * @param  assocs      associations to set
-   * @param  name        (optional) name of association
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_setAssociations($assocs, $name = null) {
-    return $this->call_method('facebook.data.setAssociations',
-       array('assocs' => json_encode($assocs),
-             'name' => $name));
-  }
-
-  /**
-   * Remove an association between two objects.
-   *
-   * @param  name        name of association
-   * @param  obj_id1     id of first object
-   * @param  obj_id2     id of second object
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_removeAssociation($name, $obj_id1, $obj_id2) {
-    return $this->call_method('facebook.data.removeAssociation',
-       array('name' => $name,
-             'obj_id1' => $obj_id1,
-             'obj_id2' => $obj_id2));
-  }
-
-  /**
-   * Remove associations between objects by specifying pairs of object ids.
-   *
-   * @param  assocs      associations to remove
-   * @param  name        (optional) name of association
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_removeAssociations($assocs, $name = null) {
-    return $this->call_method('facebook.data.removeAssociations',
-       array('assocs' => json_encode($assocs),
-             'name' => $name));
-  }
-
-  /**
-   * Remove associations between objects by specifying one object id.
-   *
-   * @param  name        name of association
-   * @param  obj_id      who's association to remove
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_removeAssociatedObjects($name, $obj_id) {
-    return $this->call_method('facebook.data.removeAssociatedObjects',
-       array('name' => $name,
-             'obj_id' => $obj_id));
-  }
-
-  /**
-   * Retrieve a list of associated objects.
-   *
-   * @param  name        name of association
-   * @param  obj_id      who's association to retrieve
-   * @param  no_data     only return object ids
-   * @return             associated objects
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getAssociatedObjects($name, $obj_id, $no_data = true) {
-    return $this->call_method('facebook.data.getAssociatedObjects',
-       array('name' => $name,
-             'obj_id' => $obj_id,
-             'no_data' => $no_data));
-  }
-
-  /**
-   * Count associated objects.
-   *
-   * @param  name        name of association
-   * @param  obj_id      who's association to retrieve
-   * @return             associated object's count
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getAssociatedObjectCount($name, $obj_id) {
-    return $this->call_method('facebook.data.getAssociatedObjectCount',
-       array('name' => $name,
-             'obj_id' => $obj_id));
-  }
-
-  /**
-   * Get a list of associated object counts.
-   *
-   * @param  name        name of association
-   * @param  obj_ids     whose association to retrieve
-   * @return             associated object counts
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_DATA_OBJECT_NOT_FOUND
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_INVALID_OPERATION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getAssociatedObjectCounts($name, $obj_ids) {
-    return $this->call_method('facebook.data.getAssociatedObjectCounts',
-       array('name' => $name,
-             'obj_ids' => json_encode($obj_ids)));
-  }
-
-  /**
-   * Find all associations between two objects.
-   *
-   * @param  obj_id1     id of first object
-   * @param  obj_id2     id of second object
-   * @param  no_data     only return association names without data
-   * @return             all associations between objects
-   * @error
-   *    API_EC_DATA_DATABASE_ERROR
-   *    API_EC_PARAM
-   *    API_EC_PERMISSION
-   *    API_EC_DATA_QUOTA_EXCEEDED
-   *    API_EC_DATA_UNKNOWN_ERROR
-   */
-  public function &data_getAssociations($obj_id1, $obj_id2, $no_data = true) {
-    return $this->call_method('facebook.data.getAssociations',
-       array('obj_id1' => $obj_id1,
-             'obj_id2' => $obj_id2,
-             'no_data' => $no_data));
-  }
-
-  /**
-   * Get the properties that you have set for an app.
-   *
-   * @param properties  List of properties names to fetch
-   *
-   * @return array  A map from property name to value
-   */
-  public function admin_getAppProperties($properties) {
-    return json_decode(
-        $this->call_method('facebook.admin.getAppProperties',
-            array('properties' => json_encode($properties))), true);
-  }
-
-  /**
-   * Set properties for an app.
-   *
-   * @param properties  A map from property names to values
-   *
-   * @return bool  true on success
-   */
-  public function admin_setAppProperties($properties) {
-    return $this->call_method('facebook.admin.setAppProperties',
-       array('properties' => json_encode($properties)));
-  }
-
-  /**
-   * Returns the allocation limit value for a specified integration point name
-   * Integration point names are defined in lib/api/karma/constants.php in the
-   * limit_map.
-   *
-   * @param string $integration_point_name  Name of an integration point
-   *                                        (see developer wiki for list).
-   * @param int    $uid                     Specific user to check the limit.
-   *
-   * @return int  Integration point allocation value
-   */
-  public function &admin_getAllocation($integration_point_name, $uid=null) {
-    return $this->call_method('facebook.admin.getAllocation',
-        array('integration_point_name' => $integration_point_name,
-              'uid' => $uid));
-  }
-
-  /**
-   * Returns values for the specified metrics for the current application, in
-   * the given time range.  The metrics are collected for fixed-length periods,
-   * and the times represent midnight at the end of each period.
-   *
-   * @param start_time  unix time for the start of the range
-   * @param end_time    unix time for the end of the range
-   * @param period      number of seconds in the desired period
-   * @param metrics     list of metrics to look up
-   *
-   * @return array  A map of the names and values for those metrics
-   */
-  public function &admin_getMetrics($start_time, $end_time, $period, $metrics) {
-    return $this->call_method('admin.getMetrics',
-        array('start_time' => $start_time,
-              'end_time' => $end_time,
-              'period' => $period,
-              'metrics' => json_encode($metrics)));
-  }
-
-  /**
-   * Sets application restriction info.
-   *
-   * Applications can restrict themselves to only a limited user demographic
-   * based on users' age and/or location or based on static predefined types
-   * specified by facebook for specifying diff age restriction for diff
-   * locations.
-   *
-   * @param array $restriction_info  The age restriction settings to set.
-   *
-   * @return bool  true on success
-   */
-  public function admin_setRestrictionInfo($restriction_info = null) {
-    $restriction_str = null;
-    if (!empty($restriction_info)) {
-      $restriction_str = json_encode($restriction_info);
-    }
-    return $this->call_method('admin.setRestrictionInfo',
-        array('restriction_str' => $restriction_str));
-  }
-
-  /**
-   * Gets application restriction info.
-   *
-   * Applications can restrict themselves to only a limited user demographic
-   * based on users' age and/or location or based on static predefined types
-   * specified by facebook for specifying diff age restriction for diff
-   * locations.
-   *
-   * @return array  The age restriction settings for this application.
-   */
-  public function admin_getRestrictionInfo() {
-    return json_decode(
-        $this->call_method('admin.getRestrictionInfo'),
-        true);
-  }
-
-
-  /**
-   * Bans a list of users from the app. Banned users can't
-   * access the app's canvas page and forums.
-   *
-   * @param array $uids an array of user ids
-   * @return bool true on success
-   */
-  public function admin_banUsers($uids) {
-    return $this->call_method(
-      'admin.banUsers', array('uids' => json_encode($uids)));
-  }
-
-  /**
-   * Unban users that have been previously banned with
-   * admin_banUsers().
-   *
-   * @param array $uids an array of user ids
-   * @return bool true on success
-   */
-  public function admin_unbanUsers($uids) {
-    return $this->call_method(
-      'admin.unbanUsers', array('uids' => json_encode($uids)));
-  }
-
-  /**
-   * Gets the list of users that have been banned from the application.
-   * $uids is an optional parameter that filters the result with the list
-   * of provided user ids. If $uids is provided,
-   * only banned user ids that are contained in $uids are returned.
-   *
-   * @param array $uids an array of user ids to filter by
-   * @return bool true on success
-   */
-
-  public function admin_getBannedUsers($uids = null) {
-    return $this->call_method(
-      'admin.getBannedUsers',
-      array('uids' => $uids ? json_encode($uids) : null));
-  }
-
-
-  /* UTILITY FUNCTIONS */
-
-  /**
-   * Calls the specified normal POST method with the specified parameters.
-   *
-   * @param string $method  Name of the Facebook method to invoke
-   * @param array $params   A map of param names => param values
-   *
-   * @return mixed  Result of method call; this returns a reference to support
-   *                'delayed returns' when in a batch context.
-   *     See: http://wiki.developers.facebook.com/index.php/Using_batching_API
-   */
-  public function &call_method($method, $params = array()) {
-    if ($this->format) {
-      $params['format'] = $this->format;
-    }
-    if (!$this->pending_batch()) {
-      if ($this->call_as_apikey) {
-        $params['call_as_apikey'] = $this->call_as_apikey;
-      }
-      $data = $this->post_request($method, $params);
-      $result = $this->convert_result($data, $method, $params);
-      if (is_array($result) && isset($result['error_code'])) {
-        throw new FacebookRestClientException($result['error_msg'],
-                                              $result['error_code']);
-      }
-    }
-    else {
-      $result = null;
-      $batch_item = array('m' => $method, 'p' => $params, 'r' => & $result);
-      $this->batch_queue[] = $batch_item;
-    }
-
-    return $result;
-  }
-
-  protected function convert_result($data, $method, $params) {
-    $is_xml = (empty($params['format']) ||
-               strtolower($params['format']) != 'json');
-    return ($is_xml) ? $this->convert_xml_to_result($data, $method, $params)
-                     : json_decode($data, true);
-  }
-
-  /**
-   * Change the response format
-   *
-   * @param string $format The response format (json, xml)
-   */
-  public function setFormat($format) {
-    $this->format = $format;
-    return $this;
-  }
-
-  /**
-   * get the current response serialization format
-   *
-   * @return string 'xml', 'json', or null (which means 'xml')
-   */
-  public function getFormat() {
-    return $this->format;
-  }
-
-  /**
-   * Calls the specified file-upload POST method with the specified parameters
-   *
-   * @param string $method Name of the Facebook method to invoke
-   * @param array  $params A map of param names => param values
-   * @param string $file   A path to the file to upload (required)
-   *
-   * @return array A dictionary representing the response.
-   */
-  public function call_upload_method($method, $params, $file, $server_addr = null) {
-    if (!$this->pending_batch()) {
-      if (!file_exists($file)) {
-        $code =
-          FacebookAPIErrorCodes::API_EC_PARAM;
-        $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
-        throw new FacebookRestClientException($description, $code);
-      }
-
-      if ($this->format) {
-        $params['format'] = $this->format;
-      }
-      $data = $this->post_upload_request($method,
-                                         $params,
-                                         $file,
-                                         $server_addr);
-      $result = $this->convert_result($data, $method, $params);
-
-      if (is_array($result) && isset($result['error_code'])) {
-        throw new FacebookRestClientException($result['error_msg'],
-                                              $result['error_code']);
-      }
-    }
-    else {
-      $code =
-        FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
-      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
-      throw new FacebookRestClientException($description, $code);
-    }
-
-    return $result;
-  }
-
-  protected function convert_xml_to_result($xml, $method, $params) {
-    $sxml = simplexml_load_string($xml);
-    $result = self::convert_simplexml_to_array($sxml);
-
-    if (!empty($GLOBALS['facebook_config']['debug'])) {
-      // output the raw xml and its corresponding php object, for debugging:
-      print '<div style="margin: 10px 30px; padding: 5px; border: 2px solid black; background: gray; color: white; font-size: 12px; font-weight: bold;">';
-      $this->cur_id++;
-      print $this->cur_id . ': Called ' . $method . ', show ' .
-            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'params\');">Params</a> | '.
-            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'xml\');">XML</a> | '.
-            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'sxml\');">SXML</a> | '.
-            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'php\');">PHP</a>';
-      print '<pre id="params'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($params, true).'</pre>';
-      print '<pre id="xml'.$this->cur_id.'" style="display: none; overflow: auto;">'.htmlspecialchars($xml).'</pre>';
-      print '<pre id="php'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($result, true).'</pre>';
-      print '<pre id="sxml'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($sxml, true).'</pre>';
-      print '</div>';
-    }
-    return $result;
-  }
-
-  protected function finalize_params($method, $params) {
-    list($get, $post) = $this->add_standard_params($method, $params);
-    // we need to do this before signing the params
-    $this->convert_array_values_to_json($post);
-    $post['sig'] = Facebook::generate_sig(array_merge($get, $post),
-                                          $this->secret);
-    return array($get, $post);
-  }
-
-  private function convert_array_values_to_json(&$params) {
-    foreach ($params as $key => &$val) {
-      if (is_array($val)) {
-        $val = json_encode($val);
-      }
-    }
-  }
-
-  /**
-   * Add the generally required params to our request.
-   * Params method, api_key, and v should be sent over as get.
-   */
-  private function add_standard_params($method, $params) {
-    $post = $params;
-    $get = array();
-    if ($this->call_as_apikey) {
-      $get['call_as_apikey'] = $this->call_as_apikey;
-    }
-    $get['method'] = $method;
-    $get['session_key'] = $this->session_key;
-    $get['api_key'] = $this->api_key;
-    $post['call_id'] = microtime(true);
-    if ($post['call_id'] <= $this->last_call_id) {
-      $post['call_id'] = $this->last_call_id + 0.001;
-    }
-    $this->last_call_id = $post['call_id'];
-    if (isset($post['v'])) {
-      $get['v'] = $post['v'];
-      unset($post['v']);
-    } else {
-      $get['v'] = '1.0';
-    }
-    if (isset($this->use_ssl_resources) &&
-        $this->use_ssl_resources) {
-      $post['return_ssl_resources'] = true;
-    }
-    return array($get, $post);
-  }
-
-  private function create_url_string($params) {
-    $post_params = array();
-    foreach ($params as $key => &$val) {
-      $post_params[] = $key.'='.urlencode($val);
-    }
-    return implode('&', $post_params);
-  }
-
-  private function run_multipart_http_transaction($method, $params, $file, $server_addr) {
-
-    // the format of this message is specified in RFC1867/RFC1341.
-    // we add twenty pseudo-random digits to the end of the boundary string.
-    $boundary = '--------------------------FbMuLtIpArT' .
-                sprintf("%010d", mt_rand()) .
-                sprintf("%010d", mt_rand());
-    $content_type = 'multipart/form-data; boundary=' . $boundary;
-    // within the message, we prepend two extra hyphens.
-    $delimiter = '--' . $boundary;
-    $close_delimiter = $delimiter . '--';
-    $content_lines = array();
-    foreach ($params as $key => &$val) {
-      $content_lines[] = $delimiter;
-      $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"';
-      $content_lines[] = '';
-      $content_lines[] = $val;
-    }
-    // now add the file data
-    $content_lines[] = $delimiter;
-    $content_lines[] =
-      'Content-Disposition: form-data; filename="' . $file . '"';
-    $content_lines[] = 'Content-Type: application/octet-stream';
-    $content_lines[] = '';
-    $content_lines[] = file_get_contents($file);
-    $content_lines[] = $close_delimiter;
-    $content_lines[] = '';
-    $content = implode("\r\n", $content_lines);
-    return $this->run_http_post_transaction($content_type, $content, $server_addr);
-  }
-
-  public function post_request($method, $params) {
-    list($get, $post) = $this->finalize_params($method, $params);
-    $post_string = $this->create_url_string($post);
-    $get_string = $this->create_url_string($get);
-    $url_with_get = $this->server_addr . '?' . $get_string;
-    if ($this->use_curl_if_available && function_exists('curl_init')) {
-      $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
-      $ch = curl_init();
-      curl_setopt($ch, CURLOPT_URL, $url_with_get);
-      curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
-      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-      curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
-      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
-      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
-      $result = $this->curl_exec($ch);
-      curl_close($ch);
-    } else {
-      $content_type = 'application/x-www-form-urlencoded';
-      $content = $post_string;
-      $result = $this->run_http_post_transaction($content_type,
-                                                 $content,
-                                                 $url_with_get);
-    }
-    return $result;
-  }
-
-  /**
-   * execute a curl transaction -- this exists mostly so subclasses can add
-   * extra options and/or process the response, if they wish.
-   *
-   * @param resource $ch a curl handle
-   */
-  protected function curl_exec($ch) {
-      $result = curl_exec($ch);
-      return $result;
-  }
-
-  private function post_upload_request($method, $params, $file, $server_addr = null) {
-    $server_addr = $server_addr ? $server_addr : $this->server_addr;
-    list($get, $post) = $this->finalize_params($method, $params);
-    $get_string = $this->create_url_string($get);
-    $url_with_get = $server_addr . '?' . $get_string;
-    if ($this->use_curl_if_available && function_exists('curl_init')) {
-      // prepending '@' causes cURL to upload the file; the key is ignored.
-      $post['_file'] = '@' . $file;
-      $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
-      $ch = curl_init();
-      curl_setopt($ch, CURLOPT_URL, $url_with_get);
-      // this has to come before the POSTFIELDS set!
-      curl_setopt($ch, CURLOPT_POST, 1);
-      // passing an array gets curl to use the multipart/form-data content type
-      curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
-      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-      curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
-      $result = $this->curl_exec($ch);
-      curl_close($ch);
-    } else {
-      $result = $this->run_multipart_http_transaction($method, $post,
-                                                      $file, $url_with_get);
-    }
-    return $result;
-  }
-
-  private function run_http_post_transaction($content_type, $content, $server_addr) {
-
-    $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion();
-    $content_length = strlen($content);
-    $context =
-      array('http' =>
-              array('method' => 'POST',
-                    'user_agent' => $user_agent,
-                    'header' => 'Content-Type: ' . $content_type . "\r\n" .
-                                'Content-Length: ' . $content_length,
-                    'content' => $content));
-    $context_id = stream_context_create($context);
-    $sock = fopen($server_addr, 'r', false, $context_id);
-
-    $result = '';
-    if ($sock) {
-      while (!feof($sock)) {
-        $result .= fgets($sock, 4096);
-      }
-      fclose($sock);
-    }
-    return $result;
-  }
-
-  public static function convert_simplexml_to_array($sxml) {
-    $arr = array();
-    if ($sxml) {
-      foreach ($sxml as $k => $v) {
-        if ($sxml['list']) {
-          $arr[] = self::convert_simplexml_to_array($v);
-        } else {
-          $arr[$k] = self::convert_simplexml_to_array($v);
-        }
-      }
-    }
-    if (sizeof($arr) > 0) {
-      return $arr;
-    } else {
-      return (string)$sxml;
-    }
-  }
-
-  protected function get_uid($uid) {
-    return $uid ? $uid : $this->user;
-  }
-}
-
-
-class FacebookRestClientException extends Exception {
-}
-
-// Supporting methods and values------
-
-/**
- * Error codes and descriptions for the Facebook API.
- */
-
-class FacebookAPIErrorCodes {
-
-  const API_EC_SUCCESS = 0;
-
-  /*
-   * GENERAL ERRORS
-   */
-  const API_EC_UNKNOWN = 1;
-  const API_EC_SERVICE = 2;
-  const API_EC_METHOD = 3;
-  const API_EC_TOO_MANY_CALLS = 4;
-  const API_EC_BAD_IP = 5;
-  const API_EC_HOST_API = 6;
-  const API_EC_HOST_UP = 7;
-  const API_EC_SECURE = 8;
-  const API_EC_RATE = 9;
-  const API_EC_PERMISSION_DENIED = 10;
-  const API_EC_DEPRECATED = 11;
-  const API_EC_VERSION = 12;
-  const API_EC_INTERNAL_FQL_ERROR = 13;
-  const API_EC_HOST_PUP = 14;
-
-  /*
-   * PARAMETER ERRORS
-   */
-  const API_EC_PARAM = 100;
-  const API_EC_PARAM_API_KEY = 101;
-  const API_EC_PARAM_SESSION_KEY = 102;
-  const API_EC_PARAM_CALL_ID = 103;
-  const API_EC_PARAM_SIGNATURE = 104;
-  const API_EC_PARAM_TOO_MANY = 105;
-  const API_EC_PARAM_USER_ID = 110;
-  const API_EC_PARAM_USER_FIELD = 111;
-  const API_EC_PARAM_SOCIAL_FIELD = 112;
-  const API_EC_PARAM_EMAIL = 113;
-  const API_EC_PARAM_USER_ID_LIST = 114;
-  const API_EC_PARAM_FIELD_LIST = 115;
-  const API_EC_PARAM_ALBUM_ID = 120;
-  const API_EC_PARAM_PHOTO_ID = 121;
-  const API_EC_PARAM_FEED_PRIORITY = 130;
-  const API_EC_PARAM_CATEGORY = 140;
-  const API_EC_PARAM_SUBCATEGORY = 141;
-  const API_EC_PARAM_TITLE = 142;
-  const API_EC_PARAM_DESCRIPTION = 143;
-  const API_EC_PARAM_BAD_JSON = 144;
-  const API_EC_PARAM_BAD_EID = 150;
-  const API_EC_PARAM_UNKNOWN_CITY = 151;
-  const API_EC_PARAM_BAD_PAGE_TYPE = 152;
-
-  /*
-   * USER PERMISSIONS ERRORS
-   */
-  const API_EC_PERMISSION = 200;
-  const API_EC_PERMISSION_USER = 210;
-  const API_EC_PERMISSION_NO_DEVELOPERS = 211;
-  const API_EC_PERMISSION_OFFLINE_ACCESS = 212;
-  const API_EC_PERMISSION_ALBUM = 220;
-  const API_EC_PERMISSION_PHOTO = 221;
-  const API_EC_PERMISSION_MESSAGE = 230;
-  const API_EC_PERMISSION_OTHER_USER = 240;
-  const API_EC_PERMISSION_STATUS_UPDATE = 250;
-  const API_EC_PERMISSION_PHOTO_UPLOAD = 260;
-  const API_EC_PERMISSION_VIDEO_UPLOAD = 261;
-  const API_EC_PERMISSION_SMS = 270;
-  const API_EC_PERMISSION_CREATE_LISTING = 280;
-  const API_EC_PERMISSION_CREATE_NOTE = 281;
-  const API_EC_PERMISSION_SHARE_ITEM = 282;
-  const API_EC_PERMISSION_EVENT = 290;
-  const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291;
-  const API_EC_PERMISSION_LIVEMESSAGE = 292;
-  const API_EC_PERMISSION_RSVP_EVENT = 299;
-
-  /*
-   * DATA EDIT ERRORS
-   */
-  const API_EC_EDIT = 300;
-  const API_EC_EDIT_USER_DATA = 310;
-  const API_EC_EDIT_PHOTO = 320;
-  const API_EC_EDIT_ALBUM_SIZE = 321;
-  const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322;
-  const API_EC_EDIT_PHOTO_TAG_PHOTO = 323;
-  const API_EC_EDIT_PHOTO_FILE = 324;
-  const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325;
-  const API_EC_EDIT_PHOTO_TAG_LIMIT = 326;
-  const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327;
-  const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328;
-
-  const API_EC_MALFORMED_MARKUP = 329;
-  const API_EC_EDIT_MARKUP = 330;
-
-  const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340;
-  const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341;
-  const API_EC_EDIT_FEED_TITLE_LINK = 342;
-  const API_EC_EDIT_FEED_TITLE_LENGTH = 343;
-  const API_EC_EDIT_FEED_TITLE_NAME = 344;
-  const API_EC_EDIT_FEED_TITLE_BLANK = 345;
-  const API_EC_EDIT_FEED_BODY_LENGTH = 346;
-  const API_EC_EDIT_FEED_PHOTO_SRC = 347;
-  const API_EC_EDIT_FEED_PHOTO_LINK = 348;
-
-  const API_EC_EDIT_VIDEO_SIZE = 350;
-  const API_EC_EDIT_VIDEO_INVALID_FILE = 351;
-  const API_EC_EDIT_VIDEO_INVALID_TYPE = 352;
-  const API_EC_EDIT_VIDEO_FILE = 353;
-
-  const API_EC_EDIT_FEED_TITLE_ARRAY = 360;
-  const API_EC_EDIT_FEED_TITLE_PARAMS = 361;
-  const API_EC_EDIT_FEED_BODY_ARRAY = 362;
-  const API_EC_EDIT_FEED_BODY_PARAMS = 363;
-  const API_EC_EDIT_FEED_PHOTO = 364;
-  const API_EC_EDIT_FEED_TEMPLATE = 365;
-  const API_EC_EDIT_FEED_TARGET = 366;
-  const API_EC_EDIT_FEED_MARKUP = 367;
-
-  /**
-   * SESSION ERRORS
-   */
-  const API_EC_SESSION_TIMED_OUT = 450;
-  const API_EC_SESSION_METHOD = 451;
-  const API_EC_SESSION_INVALID = 452;
-  const API_EC_SESSION_REQUIRED = 453;
-  const API_EC_SESSION_REQUIRED_FOR_SECRET = 454;
-  const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455;
-
-
-  /**
-   * FQL ERRORS
-   */
-  const FQL_EC_UNKNOWN_ERROR = 600;
-  const FQL_EC_PARSER = 601; // backwards compatibility
-  const FQL_EC_PARSER_ERROR = 601;
-  const FQL_EC_UNKNOWN_FIELD = 602;
-  const FQL_EC_UNKNOWN_TABLE = 603;
-  const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility
-  const FQL_EC_NO_INDEX = 604;
-  const FQL_EC_UNKNOWN_FUNCTION = 605;
-  const FQL_EC_INVALID_PARAM = 606;
-  const FQL_EC_INVALID_FIELD = 607;
-  const FQL_EC_INVALID_SESSION = 608;
-  const FQL_EC_UNSUPPORTED_APP_TYPE = 609;
-  const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610;
-  const FQL_EC_DEPRECATED_TABLE = 611;
-  const FQL_EC_EXTENDED_PERMISSION = 612;
-  const FQL_EC_RATE_LIMIT_EXCEEDED = 613;
-  const FQL_EC_UNRESOLVED_DEPENDENCY = 614;
-
-  const API_EC_REF_SET_FAILED = 700;
-
-  /**
-   * DATA STORE API ERRORS
-   */
-  const API_EC_DATA_UNKNOWN_ERROR = 800;
-  const API_EC_DATA_INVALID_OPERATION = 801;
-  const API_EC_DATA_QUOTA_EXCEEDED = 802;
-  const API_EC_DATA_OBJECT_NOT_FOUND = 803;
-  const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804;
-  const API_EC_DATA_DATABASE_ERROR = 805;
-  const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806;
-  const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807;
-  const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808;
-  const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809;
-  const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810;
-  const API_EC_DATA_MALFORMED_ACTION_LINK = 811;
-  const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812;
-
-  /*
-   * APPLICATION INFO ERRORS
-   */
-  const API_EC_NO_SUCH_APP = 900;
-
-  /*
-   * BATCH ERRORS
-   */
-  const API_EC_BATCH_TOO_MANY_ITEMS = 950;
-  const API_EC_BATCH_ALREADY_STARTED = 951;
-  const API_EC_BATCH_NOT_STARTED = 952;
-  const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953;
-
-  /*
-   * EVENT API ERRORS
-   */
-  const API_EC_EVENT_INVALID_TIME = 1000;
-
-  /*
-   * INFO BOX ERRORS
-   */
-  const API_EC_INFO_NO_INFORMATION = 1050;
-  const API_EC_INFO_SET_FAILED = 1051;
-
-  /*
-   * LIVEMESSAGE API ERRORS
-   */
-  const API_EC_LIVEMESSAGE_SEND_FAILED = 1100;
-  const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101;
-  const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102;
-
-  /*
-   * PAYMENTS API ERRORS
-   */
-  const API_EC_PAYMENTS_UNKNOWN = 1150;
-  const API_EC_PAYMENTS_APP_INVALID = 1151;
-  const API_EC_PAYMENTS_DATABASE = 1152;
-  const API_EC_PAYMENTS_PERMISSION_DENIED = 1153;
-  const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154;
-  const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155;
-  const API_EC_PAYMENTS_INVALID_ORDER = 1156;
-  const API_EC_PAYMENTS_INVALID_PARAM = 1157;
-  const API_EC_PAYMENTS_INVALID_OPERATION = 1158;
-  const API_EC_PAYMENTS_PAYMENT_FAILED = 1159;
-  const API_EC_PAYMENTS_DISABLED = 1160;
-
-  /*
-   * CONNECT SESSION ERRORS
-   */
-  const API_EC_CONNECT_FEED_DISABLED = 1300;
-
-  /*
-   * Platform tag bundles errors
-   */
-  const API_EC_TAG_BUNDLE_QUOTA = 1400;
-
-  /*
-   * SHARE
-   */
-  const API_EC_SHARE_BAD_URL = 1500;
-
-  /*
-   * NOTES
-   */
-  const API_EC_NOTE_CANNOT_MODIFY = 1600;
-
-  /*
-   * COMMENTS
-   */
-  const API_EC_COMMENTS_UNKNOWN = 1700;
-  const API_EC_COMMENTS_POST_TOO_LONG = 1701;
-  const API_EC_COMMENTS_DB_DOWN = 1702;
-  const API_EC_COMMENTS_INVALID_XID = 1703;
-  const API_EC_COMMENTS_INVALID_UID = 1704;
-  const API_EC_COMMENTS_INVALID_POST = 1705;
-  const API_EC_COMMENTS_INVALID_REMOVE = 1706;
-
-  /**
-   * This array is no longer maintained; to view the description of an error
-   * code, please look at the message element of the API response or visit
-   * the developer wiki at http://wiki.developers.facebook.com/.
-   */
-  public static $api_error_descriptions = array(
-      self::API_EC_SUCCESS           => 'Success',
-      self::API_EC_UNKNOWN           => 'An unknown error occurred',
-      self::API_EC_SERVICE           => 'Service temporarily unavailable',
-      self::API_EC_METHOD            => 'Unknown method',
-      self::API_EC_TOO_MANY_CALLS    => 'Application request limit reached',
-      self::API_EC_BAD_IP            => 'Unauthorized source IP address',
-      self::API_EC_PARAM             => 'Invalid parameter',
-      self::API_EC_PARAM_API_KEY     => 'Invalid API key',
-      self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
-      self::API_EC_PARAM_CALL_ID     => 'Call_id must be greater than previous',
-      self::API_EC_PARAM_SIGNATURE   => 'Incorrect signature',
-      self::API_EC_PARAM_USER_ID     => 'Invalid user id',
-      self::API_EC_PARAM_USER_FIELD  => 'Invalid user info field',
-      self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
-      self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list',
-      self::API_EC_PARAM_FIELD_LIST => 'Invalid field list',
-      self::API_EC_PARAM_ALBUM_ID    => 'Invalid album id',
-      self::API_EC_PARAM_BAD_EID     => 'Invalid eid',
-      self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
-      self::API_EC_PERMISSION        => 'Permissions error',
-      self::API_EC_PERMISSION_USER   => 'User not visible',
-      self::API_EC_PERMISSION_NO_DEVELOPERS  => 'Application has no developers',
-      self::API_EC_PERMISSION_ALBUM  => 'Album not visible',
-      self::API_EC_PERMISSION_PHOTO  => 'Photo not visible',
-      self::API_EC_PERMISSION_EVENT  => 'Creating and modifying events required the extended permission create_event',
-      self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
-      self::API_EC_EDIT_ALBUM_SIZE   => 'Album is full',
-      self::FQL_EC_PARSER            => 'FQL: Parser Error',
-      self::FQL_EC_UNKNOWN_FIELD     => 'FQL: Unknown Field',
-      self::FQL_EC_UNKNOWN_TABLE     => 'FQL: Unknown Table',
-      self::FQL_EC_NOT_INDEXABLE     => 'FQL: Statement not indexable',
-      self::FQL_EC_UNKNOWN_FUNCTION  => 'FQL: Attempted to call unknown function',
-      self::FQL_EC_INVALID_PARAM     => 'FQL: Invalid parameter passed in',
-      self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
-      self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
-      self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
-      self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
-      self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
-      self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
-      self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
-      self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch',
-      self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode'
-  );
-}
diff --git a/extlib/facebook/jsonwrapper/JSON/JSON.php b/extlib/facebook/jsonwrapper/JSON/JSON.php
deleted file mode 100644 (file)
index 0cddbdd..0000000
+++ /dev/null
@@ -1,806 +0,0 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Converts to and from JSON format.
- *
- * JSON (JavaScript Object Notation) is a lightweight data-interchange
- * format. It is easy for humans to read and write. It is easy for machines
- * to parse and generate. It is based on a subset of the JavaScript
- * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
- * This feature can also be found in  Python. JSON is a text format that is
- * completely language independent but uses conventions that are familiar
- * to programmers of the C-family of languages, including C, C++, C#, Java,
- * JavaScript, Perl, TCL, and many others. These properties make JSON an
- * ideal data-interchange language.
- *
- * This package provides a simple encoder and decoder for JSON notation. It
- * is intended for use with client-side Javascript applications that make
- * use of HTTPRequest to perform server communication functions - data can
- * be encoded into JSON notation for use in a client-side javascript, or
- * decoded from incoming Javascript requests. JSON format is native to
- * Javascript, and can be directly eval()'ed with no further parsing
- * overhead
- *
- * All strings should be in ASCII or UTF-8 format!
- *
- * LICENSE: Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met: Redistributions of source code must retain the
- * above copyright notice, this list of conditions and the following
- * disclaimer. Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
- * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
- * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- * @category
- * @package     Services_JSON
- * @author      Michal Migurski <mike-json@teczno.com>
- * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
- * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
- * @copyright   2005 Michal Migurski
- * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
- * @license     http://www.opensource.org/licenses/bsd-license.php
- * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
- */
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_SLICE',   1);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_STR',  2);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_ARR',  3);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_OBJ',  4);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_CMT', 5);
-
-/**
- * Behavior switch for Services_JSON::decode()
- */
-define('SERVICES_JSON_LOOSE_TYPE', 16);
-
-/**
- * Behavior switch for Services_JSON::decode()
- */
-define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
-
-/**
- * Converts to and from JSON format.
- *
- * Brief example of use:
- *
- * <code>
- * // create a new instance of Services_JSON
- * $json = new Services_JSON();
- *
- * // convert a complexe value to JSON notation, and send it to the browser
- * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
- * $output = $json->encode($value);
- *
- * print($output);
- * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
- *
- * // accept incoming POST data, assumed to be in JSON notation
- * $input = file_get_contents('php://input', 1000000);
- * $value = $json->decode($input);
- * </code>
- */
-class Services_JSON
-{
-   /**
-    * constructs a new JSON instance
-    *
-    * @param    int     $use    object behavior flags; combine with boolean-OR
-    *
-    *                           possible values:
-    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
-    *                                   "{...}" syntax creates associative arrays
-    *                                   instead of objects in decode().
-    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
-    *                                   Values which can't be encoded (e.g. resources)
-    *                                   appear as NULL instead of throwing errors.
-    *                                   By default, a deeply-nested resource will
-    *                                   bubble up with an error, so all return values
-    *                                   from encode() should be checked with isError()
-    */
-    function Services_JSON($use = 0)
-    {
-        $this->use = $use;
-    }
-
-   /**
-    * convert a string from one UTF-16 char to one UTF-8 char
-    *
-    * Normally should be handled by mb_convert_encoding, but
-    * provides a slower PHP-only method for installations
-    * that lack the multibye string extension.
-    *
-    * @param    string  $utf16  UTF-16 character
-    * @return   string  UTF-8 character
-    * @access   private
-    */
-    function utf162utf8($utf16)
-    {
-        // oh please oh please oh please oh please oh please
-        if(function_exists('mb_convert_encoding')) {
-            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
-        }
-
-        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
-
-        switch(true) {
-            case ((0x7F & $bytes) == $bytes):
-                // this case should never be reached, because we are in ASCII range
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0x7F & $bytes);
-
-            case (0x07FF & $bytes) == $bytes:
-                // return a 2-byte UTF-8 character
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0xC0 | (($bytes >> 6) & 0x1F))
-                     . chr(0x80 | ($bytes & 0x3F));
-
-            case (0xFFFF & $bytes) == $bytes:
-                // return a 3-byte UTF-8 character
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0xE0 | (($bytes >> 12) & 0x0F))
-                     . chr(0x80 | (($bytes >> 6) & 0x3F))
-                     . chr(0x80 | ($bytes & 0x3F));
-        }
-
-        // ignoring UTF-32 for now, sorry
-        return '';
-    }
-
-   /**
-    * convert a string from one UTF-8 char to one UTF-16 char
-    *
-    * Normally should be handled by mb_convert_encoding, but
-    * provides a slower PHP-only method for installations
-    * that lack the multibye string extension.
-    *
-    * @param    string  $utf8   UTF-8 character
-    * @return   string  UTF-16 character
-    * @access   private
-    */
-    function utf82utf16($utf8)
-    {
-        // oh please oh please oh please oh please oh please
-        if(function_exists('mb_convert_encoding')) {
-            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
-        }
-
-        switch(strlen($utf8)) {
-            case 1:
-                // this case should never be reached, because we are in ASCII range
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return $utf8;
-
-            case 2:
-                // return a UTF-16 character from a 2-byte UTF-8 char
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0x07 & (ord($utf8{0}) >> 2))
-                     . chr((0xC0 & (ord($utf8{0}) << 6))
-                         | (0x3F & ord($utf8{1})));
-
-            case 3:
-                // return a UTF-16 character from a 3-byte UTF-8 char
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr((0xF0 & (ord($utf8{0}) << 4))
-                         | (0x0F & (ord($utf8{1}) >> 2)))
-                     . chr((0xC0 & (ord($utf8{1}) << 6))
-                         | (0x7F & ord($utf8{2})));
-        }
-
-        // ignoring UTF-32 for now, sorry
-        return '';
-    }
-
-   /**
-    * encodes an arbitrary variable into JSON format
-    *
-    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
-    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
-    *                           if var is a strng, note that encode() always expects it
-    *                           to be in ASCII or UTF-8 format!
-    *
-    * @return   mixed   JSON string representation of input var or an error if a problem occurs
-    * @access   public
-    */
-    function encode($var)
-    {
-        switch (gettype($var)) {
-            case 'boolean':
-                return $var ? 'true' : 'false';
-
-            case 'NULL':
-                return 'null';
-
-            case 'integer':
-                return (int) $var;
-
-            case 'double':
-            case 'float':
-                return (float) $var;
-
-            case 'string':
-                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
-                $ascii = '';
-                $strlen_var = strlen($var);
-
-               /*
-                * Iterate over every character in the string,
-                * escaping with a slash or encoding to UTF-8 where necessary
-                */
-                for ($c = 0; $c < $strlen_var; ++$c) {
-
-                    $ord_var_c = ord($var{$c});
-
-                    switch (true) {
-                        case $ord_var_c == 0x08:
-                            $ascii .= '\b';
-                            break;
-                        case $ord_var_c == 0x09:
-                            $ascii .= '\t';
-                            break;
-                        case $ord_var_c == 0x0A:
-                            $ascii .= '\n';
-                            break;
-                        case $ord_var_c == 0x0C:
-                            $ascii .= '\f';
-                            break;
-                        case $ord_var_c == 0x0D:
-                            $ascii .= '\r';
-                            break;
-
-                        case $ord_var_c == 0x22:
-                        case $ord_var_c == 0x2F:
-                        case $ord_var_c == 0x5C:
-                            // double quote, slash, slosh
-                            $ascii .= '\\'.$var{$c};
-                            break;
-
-                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
-                            // characters U-00000000 - U-0000007F (same as ASCII)
-                            $ascii .= $var{$c};
-                            break;
-
-                        case (($ord_var_c & 0xE0) == 0xC0):
-                            // characters U-00000080 - U-000007FF, mask 110XXXXX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
-                            $c += 1;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xF0) == 0xE0):
-                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}));
-                            $c += 2;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xF8) == 0xF0):
-                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}),
-                                         ord($var{$c + 3}));
-                            $c += 3;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xFC) == 0xF8):
-                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}),
-                                         ord($var{$c + 3}),
-                                         ord($var{$c + 4}));
-                            $c += 4;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xFE) == 0xFC):
-                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}),
-                                         ord($var{$c + 3}),
-                                         ord($var{$c + 4}),
-                                         ord($var{$c + 5}));
-                            $c += 5;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-                    }
-                }
-
-                return '"'.$ascii.'"';
-
-            case 'array':
-               /*
-                * As per JSON spec if any array key is not an integer
-                * we must treat the the whole array as an object. We
-                * also try to catch a sparsely populated associative
-                * array with numeric keys here because some JS engines
-                * will create an array with empty indexes up to
-                * max_index which can cause memory issues and because
-                * the keys, which may be relevant, will be remapped
-                * otherwise.
-                *
-                * As per the ECMA and JSON specification an object may
-                * have any string as a property. Unfortunately due to
-                * a hole in the ECMA specification if the key is a
-                * ECMA reserved word or starts with a digit the
-                * parameter is only accessible using ECMAScript's
-                * bracket notation.
-                */
-
-                // treat as a JSON object
-                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
-                    $properties = array_map(array($this, 'name_value'),
-                                            array_keys($var),
-                                            array_values($var));
-
-                    foreach($properties as $property) {
-                        if(Services_JSON::isError($property)) {
-                            return $property;
-                        }
-                    }
-
-                    return '{' . join(',', $properties) . '}';
-                }
-
-                // treat it like a regular array
-                $elements = array_map(array($this, 'encode'), $var);
-
-                foreach($elements as $element) {
-                    if(Services_JSON::isError($element)) {
-                        return $element;
-                    }
-                }
-
-                return '[' . join(',', $elements) . ']';
-
-            case 'object':
-                $vars = get_object_vars($var);
-
-                $properties = array_map(array($this, 'name_value'),
-                                        array_keys($vars),
-                                        array_values($vars));
-
-                foreach($properties as $property) {
-                    if(Services_JSON::isError($property)) {
-                        return $property;
-                    }
-                }
-
-                return '{' . join(',', $properties) . '}';
-
-            default:
-                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
-                    ? 'null'
-                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
-        }
-    }
-
-   /**
-    * array-walking function for use in generating JSON-formatted name-value pairs
-    *
-    * @param    string  $name   name of key to use
-    * @param    mixed   $value  reference to an array element to be encoded
-    *
-    * @return   string  JSON-formatted name-value pair, like '"name":value'
-    * @access   private
-    */
-    function name_value($name, $value)
-    {
-        $encoded_value = $this->encode($value);
-
-        if(Services_JSON::isError($encoded_value)) {
-            return $encoded_value;
-        }
-
-        return $this->encode(strval($name)) . ':' . $encoded_value;
-    }
-
-   /**
-    * reduce a string by removing leading and trailing comments and whitespace
-    *
-    * @param    $str    string      string value to strip of comments and whitespace
-    *
-    * @return   string  string value stripped of comments and whitespace
-    * @access   private
-    */
-    function reduce_string($str)
-    {
-        $str = preg_replace(array(
-
-                // eliminate single line comments in '// ...' form
-                '#^\s*//(.+)$#m',
-
-                // eliminate multi-line comments in '/* ... */' form, at start of string
-                '#^\s*/\*(.+)\*/#Us',
-
-                // eliminate multi-line comments in '/* ... */' form, at end of string
-                '#/\*(.+)\*/\s*$#Us'
-
-            ), '', $str);
-
-        // eliminate extraneous space
-        return trim($str);
-    }
-
-   /**
-    * decodes a JSON string into appropriate variable
-    *
-    * @param    string  $str    JSON-formatted string
-    *
-    * @return   mixed   number, boolean, string, array, or object
-    *                   corresponding to given JSON input string.
-    *                   See argument 1 to Services_JSON() above for object-output behavior.
-    *                   Note that decode() always returns strings
-    *                   in ASCII or UTF-8 format!
-    * @access   public
-    */
-    function decode($str)
-    {
-        $str = $this->reduce_string($str);
-
-        switch (strtolower($str)) {
-            case 'true':
-                return true;
-
-            case 'false':
-                return false;
-
-            case 'null':
-                return null;
-
-            default:
-                $m = array();
-
-                if (is_numeric($str)) {
-                    // Lookie-loo, it's a number
-
-                    // This would work on its own, but I'm trying to be
-                    // good about returning integers where appropriate:
-                    // return (float)$str;
-
-                    // Return float or int, as appropriate
-                    return ((float)$str == (integer)$str)
-                        ? (integer)$str
-                        : (float)$str;
-
-                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
-                    // STRINGS RETURNED IN UTF-8 FORMAT
-                    $delim = substr($str, 0, 1);
-                    $chrs = substr($str, 1, -1);
-                    $utf8 = '';
-                    $strlen_chrs = strlen($chrs);
-
-                    for ($c = 0; $c < $strlen_chrs; ++$c) {
-
-                        $substr_chrs_c_2 = substr($chrs, $c, 2);
-                        $ord_chrs_c = ord($chrs{$c});
-
-                        switch (true) {
-                            case $substr_chrs_c_2 == '\b':
-                                $utf8 .= chr(0x08);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\t':
-                                $utf8 .= chr(0x09);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\n':
-                                $utf8 .= chr(0x0A);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\f':
-                                $utf8 .= chr(0x0C);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\r':
-                                $utf8 .= chr(0x0D);
-                                ++$c;
-                                break;
-
-                            case $substr_chrs_c_2 == '\\"':
-                            case $substr_chrs_c_2 == '\\\'':
-                            case $substr_chrs_c_2 == '\\\\':
-                            case $substr_chrs_c_2 == '\\/':
-                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
-                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
-                                    $utf8 .= $chrs{++$c};
-                                }
-                                break;
-
-                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
-                                // single, escaped unicode character
-                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
-                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
-                                $utf8 .= $this->utf162utf8($utf16);
-                                $c += 5;
-                                break;
-
-                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
-                                $utf8 .= $chrs{$c};
-                                break;
-
-                            case ($ord_chrs_c & 0xE0) == 0xC0:
-                                // characters U-00000080 - U-000007FF, mask 110XXXXX
-                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 2);
-                                ++$c;
-                                break;
-
-                            case ($ord_chrs_c & 0xF0) == 0xE0:
-                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 3);
-                                $c += 2;
-                                break;
-
-                            case ($ord_chrs_c & 0xF8) == 0xF0:
-                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 4);
-                                $c += 3;
-                                break;
-
-                            case ($ord_chrs_c & 0xFC) == 0xF8:
-                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 5);
-                                $c += 4;
-                                break;
-
-                            case ($ord_chrs_c & 0xFE) == 0xFC:
-                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 6);
-                                $c += 5;
-                                break;
-
-                        }
-
-                    }
-
-                    return $utf8;
-
-                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
-                    // array, or object notation
-
-                    if ($str{0} == '[') {
-                        $stk = array(SERVICES_JSON_IN_ARR);
-                        $arr = array();
-                    } else {
-                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
-                            $stk = array(SERVICES_JSON_IN_OBJ);
-                            $obj = array();
-                        } else {
-                            $stk = array(SERVICES_JSON_IN_OBJ);
-                            $obj = new stdClass();
-                        }
-                    }
-
-                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
-                                           'where' => 0,
-                                           'delim' => false));
-
-                    $chrs = substr($str, 1, -1);
-                    $chrs = $this->reduce_string($chrs);
-
-                    if ($chrs == '') {
-                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
-                            return $arr;
-
-                        } else {
-                            return $obj;
-
-                        }
-                    }
-
-                    //print("\nparsing {$chrs}\n");
-
-                    $strlen_chrs = strlen($chrs);
-
-                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
-
-                        $top = end($stk);
-                        $substr_chrs_c_2 = substr($chrs, $c, 2);
-
-                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
-                            // found a comma that is not inside a string, array, etc.,
-                            // OR we've reached the end of the character list
-                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
-                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
-                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
-                                // we are in an array, so just push an element onto the stack
-                                array_push($arr, $this->decode($slice));
-
-                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
-                                // we are in an object, so figure
-                                // out the property name and set an
-                                // element in an associative array,
-                                // for now
-                                $parts = array();
-                                
-                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
-                                    // "name":value pair
-                                    $key = $this->decode($parts[1]);
-                                    $val = $this->decode($parts[2]);
-
-                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
-                                        $obj[$key] = $val;
-                                    } else {
-                                        $obj->$key = $val;
-                                    }
-                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
-                                    // name:value pair, where name is unquoted
-                                    $key = $parts[1];
-                                    $val = $this->decode($parts[2]);
-
-                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
-                                        $obj[$key] = $val;
-                                    } else {
-                                        $obj->$key = $val;
-                                    }
-                                }
-
-                            }
-
-                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
-                            // found a quote, and we are not inside a string
-                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
-                            //print("Found start of string at {$c}\n");
-
-                        } elseif (($chrs{$c} == $top['delim']) &&
-                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
-                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
-                            // found a quote, we're in a string, and it's not escaped
-                            // we know that it's not escaped becase there is _not_ an
-                            // odd number of backslashes at the end of the string so far
-                            array_pop($stk);
-                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
-
-                        } elseif (($chrs{$c} == '[') &&
-                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
-                            // found a left-bracket, and we are in an array, object, or slice
-                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
-                            //print("Found start of array at {$c}\n");
-
-                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
-                            // found a right-bracket, and we're in an array
-                            array_pop($stk);
-                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                        } elseif (($chrs{$c} == '{') &&
-                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
-                            // found a left-brace, and we are in an array, object, or slice
-                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
-                            //print("Found start of object at {$c}\n");
-
-                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
-                            // found a right-brace, and we're in an object
-                            array_pop($stk);
-                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                        } elseif (($substr_chrs_c_2 == '/*') &&
-                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
-                            // found a comment start, and we are in an array, object, or slice
-                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
-                            $c++;
-                            //print("Found start of comment at {$c}\n");
-
-                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
-                            // found a comment end, and we're in one now
-                            array_pop($stk);
-                            $c++;
-
-                            for ($i = $top['where']; $i <= $c; ++$i)
-                                $chrs = substr_replace($chrs, ' ', $i, 1);
-
-                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                        }
-
-                    }
-
-                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
-                        return $arr;
-
-                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
-                        return $obj;
-
-                    }
-
-                }
-        }
-    }
-
-    /**
-     * @todo Ultimately, this should just call PEAR::isError()
-     */
-    function isError($data, $code = null)
-    {
-        if (class_exists('pear')) {
-            return PEAR::isError($data, $code);
-        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
-                                 is_subclass_of($data, 'services_json_error'))) {
-            return true;
-        }
-
-        return false;
-    }
-}
-
-if (class_exists('PEAR_Error')) {
-
-    class Services_JSON_Error extends PEAR_Error
-    {
-        function Services_JSON_Error($message = 'unknown error', $code = null,
-                                     $mode = null, $options = null, $userinfo = null)
-        {
-            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
-        }
-    }
-
-} else {
-
-    /**
-     * @todo Ultimately, this class shall be descended from PEAR_Error
-     */
-    class Services_JSON_Error
-    {
-        function Services_JSON_Error($message = 'unknown error', $code = null,
-                                     $mode = null, $options = null, $userinfo = null)
-        {
-
-        }
-    }
-
-}
-    
-?>
diff --git a/extlib/facebook/jsonwrapper/JSON/LICENSE b/extlib/facebook/jsonwrapper/JSON/LICENSE
deleted file mode 100644 (file)
index 4ae6bef..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-Redistributions of source code must retain the above copyright notice,
-this list of conditions and the following disclaimer.
-
-Redistributions in binary form must reproduce the above copyright
-notice, this list of conditions and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
-WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
-NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/extlib/facebook/jsonwrapper/jsonwrapper.php b/extlib/facebook/jsonwrapper/jsonwrapper.php
deleted file mode 100644 (file)
index 29509de..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-<?php
-# In PHP 5.2 or higher we don't need to bring this in
-if (!function_exists('json_encode')) {
-       require_once 'jsonwrapper_inner.php';
-} 
-?>
diff --git a/extlib/facebook/jsonwrapper/jsonwrapper_inner.php b/extlib/facebook/jsonwrapper/jsonwrapper_inner.php
deleted file mode 100644 (file)
index 36a3f28..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-require_once 'JSON/JSON.php';
-
-function json_encode($arg)
-{
-       global $services_json;
-       if (!isset($services_json)) {
-               $services_json = new Services_JSON();
-       }
-       return $services_json->encode($arg);
-}
-
-function json_decode($arg)
-{
-       global $services_json;
-       if (!isset($services_json)) {
-               $services_json = new Services_JSON();
-       }
-       return $services_json->decode($arg);
-}
-
-?>
diff --git a/plugins/FBConnect/FBCLoginGroupNav.php b/plugins/FBConnect/FBCLoginGroupNav.php
deleted file mode 100644 (file)
index 81b2520..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Menu for login group of actions
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Menu
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR . '/lib/widget.php';
-
-/**
- * Menu for login group of actions
- *
- * @category Output
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @author   Zach Copley <zach@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- *
- * @see      Widget
- */
-
-class FBCLoginGroupNav extends Widget
-{
-    var $action = null;
-
-    /**
-     * Construction
-     *
-     * @param Action $action current action, used for output
-     */
-
-    function __construct($action=null)
-    {
-        parent::__construct($action);
-        $this->action = $action;
-    }
-
-    /**
-     * Show the menu
-     *
-     * @return void
-     */
-
-    function show()
-    {
-        $this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
-        $this->action->element('dt', null, _('Local views'));
-        $this->action->elementStart('dd');
-
-        // action => array('prompt', 'title')
-        $menu = array();
-
-        if (!common_config('site','openidonly')) {
-            $menu['login'] = array(_('Login'),
-                             _('Login with a username and password'));
-
-            if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
-                $menu['register'] = array(_('Register'),
-                                    _('Sign up for a new account'));
-            }
-        }
-
-        if (common_config('openid', 'enabled')) {
-            $menu['openidlogin'] = array(_('OpenID'),
-                                   _('Login or register with OpenID'));
-        }
-
-        $menu['FBConnectLogin'] = array(_('Facebook'),
-                               _('Login or register using Facebook'));
-
-        $action_name = $this->action->trimmed('action');
-        $this->action->elementStart('ul', array('class' => 'nav'));
-
-        foreach ($menu as $menuaction => $menudesc) {
-            $this->action->menuItem(common_local_url($menuaction),
-                                    $menudesc[0],
-                                    $menudesc[1],
-                                    $action_name === $menuaction);
-        }
-
-        $this->action->elementEnd('ul');
-
-        $this->action->elementEnd('dd');
-        $this->action->elementEnd('dl');
-    }
-}
diff --git a/plugins/FBConnect/FBCSettingsNav.php b/plugins/FBConnect/FBCSettingsNav.php
deleted file mode 100644 (file)
index ed02371..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Menu for login group of actions
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Menu
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR . '/lib/widget.php';
-
-/**
- * A widget for showing the connect group local nav menu
- *
- * @category Output
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @author   Zach Copley <zach@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- *
- * @see      Widget
- */
-
-class FBCSettingsNav extends Widget
-{
-    var $action = null;
-
-    /**
-     * Construction
-     *
-     * @param Action $action current action, used for output
-     */
-
-    function __construct($action=null)
-    {
-        parent::__construct($action);
-        $this->action = $action;
-    }
-
-    /**
-     * Show the menu
-     *
-     * @return void
-     */
-
-    function show()
-    {
-
-        $this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
-        $this->action->element('dt', null, _('Local views'));
-        $this->action->elementStart('dd');
-
-        # action => array('prompt', 'title')
-        $menu = array();
-        if (common_config('xmpp', 'enabled')) {
-            $menu['imsettings'] =
-              array(_('IM'),
-                    _('Updates by instant messenger (IM)'));
-        }
-        if (common_config('sms', 'enabled')) {
-            $menu['smssettings'] =
-              array(_('SMS'),
-                    _('Updates by SMS'));
-        }
-        if (common_config('twitter', 'enabled')) {
-            $menu['twittersettings'] =
-              array(_('Twitter'),
-                    _('Twitter integration options'));
-        }
-        $menu['FBConnectSettings'] =
-          array(_('Facebook'),
-                _('Facebook Connect settings'));
-
-        $action_name = $this->action->trimmed('action');
-        $this->action->elementStart('ul', array('class' => 'nav'));
-
-        foreach ($menu as $menuaction => $menudesc) {
-            $this->action->menuItem(common_local_url($menuaction),
-                                    $menudesc[0],
-                                    $menudesc[1],
-                                    $action_name === $menuaction);
-        }
-
-        $this->action->elementEnd('ul');
-
-        $this->action->elementEnd('dd');
-        $this->action->elementEnd('dl');
-    }
-}
diff --git a/plugins/FBConnect/FBC_XDReceiver.php b/plugins/FBConnect/FBC_XDReceiver.php
deleted file mode 100644 (file)
index 2bc790d..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-/*
- * Generates the cross domain communication channel file
- * (xd_receiver.html). By generating it we can add some caching
- * instructions.
- *
- * See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
- */
-class FBC_XDReceiverAction extends Action
-{
-
-    /**
-     * Do we need to write to the database?
-     *
-     * @return boolean true
-     */
-
-    function isReadonly()
-    {
-        return true;
-    }
-
-    /**
-     * Handle a request
-     *
-     * @param array $args Arguments from $_REQUEST
-     *
-     * @return void
-     */
-
-    function handle($args)
-    {
-        // Parent handling, including cache check
-        parent::handle($args);
-        $this->showPage();
-    }
-
-    function showPage()
-    {
-        // cache the xd_receiver
-        header('Cache-Control: max-age=225065900');
-        header('Expires:');
-        header('Pragma:');
-
-        $this->startXML('html');
-
-        $language = $this->getLanguage();
-
-        $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
-                                          'xml:lang' => $language,
-                                          'lang' => $language));
-        $this->elementStart('head');
-        $this->element('title', null, 'cross domain receiver page');
-        $this->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js');
-        $this->elementEnd('head');
-        $this->elementStart('body');
-        $this->elementEnd('body');
-
-        $this->elementEnd('html');
-    }
-
-}
-
diff --git a/plugins/FBConnect/FBConnectAuth.php b/plugins/FBConnect/FBConnectAuth.php
deleted file mode 100644 (file)
index 647d5de..0000000
+++ /dev/null
@@ -1,461 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Plugin to enable Facebook Connect
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Plugin
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
-
-class FBConnectauthAction extends Action
-{
-    var $fbuid      = null;
-    var $fb_fields  = null;
-
-    function prepare($args) {
-        parent::prepare($args);
-
-        $this->fbuid = getFacebook()->get_loggedin_user();
-
-        if ($this->fbuid > 0) {
-            $this->fb_fields = $this->getFacebookFields($this->fbuid,
-                                                        array('first_name', 'last_name', 'name'));
-        } else {
-            list($proxy, $ip) = common_client_ip();
-
-            common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
-                       "Failed auth attempt, proxy = $proxy, ip = $ip.");
-
-            $this->clientError(_('You must be logged into Facebook to ' .
-                                 'use Facebook Connect.'));
-        }
-
-        return true;
-    }
-
-    function handle($args)
-    {
-        parent::handle($args);
-
-        if (common_is_real_login()) {
-
-            // User is already logged in.  Does she already have a linked Facebook acct?
-            $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
-
-            if (!empty($flink)) {
-
-                // User already has a linked Facebook account and shouldn't be here
-                common_debug('Facebook Connect Plugin - ' .
-                             'There is already a local user (' . $flink->user_id .
-                             ') linked with this Facebook (' . $this->fbuid . ').');
-
-                // We don't want these cookies
-                getFacebook()->clear_cookie_state();
-
-                $this->clientError(_('There is already a local user linked with this Facebook.'));
-
-            } else {
-
-                // User came from the Facebook connect settings tab, and
-                // probably just wants to link/relink their Facebook account
-                $this->connectUser();
-            }
-
-        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
-
-            $token = $this->trimmed('token');
-            if (!$token || $token != common_session_token()) {
-                $this->showForm(_('There was a problem with your session token. Try again, please.'));
-                return;
-            }
-            if ($this->arg('create')) {
-                if (!$this->boolean('license')) {
-                    $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
-                                    $this->trimmed('newname'));
-                    return;
-                }
-                $this->createNewUser();
-            } else if ($this->arg('connect')) {
-                $this->connectNewUser();
-            } else {
-                common_debug('Facebook Connect Plugin - ' .
-                             print_r($this->args, true));
-                $this->showForm(_('Something weird happened.'),
-                                $this->trimmed('newname'));
-            }
-        } else {
-            $this->tryLogin();
-        }
-    }
-
-    function showPageNotice()
-    {
-        if ($this->error) {
-            $this->element('div', array('class' => 'error'), $this->error);
-        } else {
-            $this->element('div', 'instructions',
-                           sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
-        }
-    }
-
-    function title()
-    {
-        return _('Facebook Account Setup');
-    }
-
-    function showForm($error=null, $username=null)
-    {
-        $this->error = $error;
-        $this->username = $username;
-
-        $this->showPage();
-    }
-
-    function showPage()
-    {
-        parent::showPage();
-    }
-
-    function showContent()
-    {
-        if (!empty($this->message_text)) {
-            $this->element('p', null, $this->message);
-            return;
-        }
-
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'form_settings_facebook_connect',
-                                          'class' => 'form_settings',
-                                          'action' => common_local_url('FBConnectAuth')));
-        $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
-        $this->element('legend', null, _('Connection options'));
-        $this->elementStart('ul', 'form_data');
-        $this->elementStart('li');
-        $this->element('input', array('type' => 'checkbox',
-                                      'id' => 'license',
-                                      'class' => 'checkbox',
-                                      'name' => 'license',
-                                      'value' => 'true'));
-        $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
-        $this->text(_('My text and files are available under '));
-        $this->element('a', array('href' => common_config('license', 'url')),
-                       common_config('license', 'title'));
-        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
-        $this->elementEnd('label');
-        $this->elementEnd('li');
-        $this->elementEnd('ul');
-
-        $this->elementStart('fieldset');
-        $this->hidden('token', common_session_token());
-        $this->element('legend', null,
-                       _('Create new account'));
-        $this->element('p', null,
-                       _('Create a new user with this nickname.'));
-        $this->elementStart('ul', 'form_data');
-        $this->elementStart('li');
-        $this->input('newname', _('New nickname'),
-                     ($this->username) ? $this->username : '',
-                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
-        $this->elementEnd('li');
-        $this->elementEnd('ul');
-        $this->submit('create', _('Create'));
-        $this->elementEnd('fieldset');
-
-        $this->elementStart('fieldset');
-        $this->element('legend', null,
-                       _('Connect existing account'));
-        $this->element('p', null,
-                       _('If you already have an account, login with your username and password to connect it to your Facebook.'));
-        $this->elementStart('ul', 'form_data');
-        $this->elementStart('li');
-        $this->input('nickname', _('Existing nickname'));
-        $this->elementEnd('li');
-        $this->elementStart('li');
-        $this->password('password', _('Password'));
-        $this->elementEnd('li');
-        $this->elementEnd('ul');
-        $this->submit('connect', _('Connect'));
-        $this->elementEnd('fieldset');
-
-        $this->elementEnd('fieldset');
-        $this->elementEnd('form');
-    }
-
-    function message($msg)
-    {
-        $this->message_text = $msg;
-        $this->showPage();
-    }
-
-    function createNewUser()
-    {
-        if (common_config('site', 'closed')) {
-            $this->clientError(_('Registration not allowed.'));
-            return;
-        }
-
-        $invite = null;
-
-        if (common_config('site', 'inviteonly')) {
-            $code = $_SESSION['invitecode'];
-            if (empty($code)) {
-                $this->clientError(_('Registration not allowed.'));
-                return;
-            }
-
-            $invite = Invitation::staticGet($code);
-
-            if (empty($invite)) {
-                $this->clientError(_('Not a valid invitation code.'));
-                return;
-            }
-        }
-
-        $nickname = $this->trimmed('newname');
-
-        if (!Validate::string($nickname, array('min_length' => 1,
-                                               'max_length' => 64,
-                                               'format' => NICKNAME_FMT))) {
-            $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
-            return;
-        }
-
-        if (!User::allowed_nickname($nickname)) {
-            $this->showForm(_('Nickname not allowed.'));
-            return;
-        }
-
-        if (User::staticGet('nickname', $nickname)) {
-            $this->showForm(_('Nickname already in use. Try another one.'));
-            return;
-        }
-
-        $fullname = trim($this->fb_fields['firstname'] .
-            ' ' . $this->fb_fields['lastname']);
-
-        $args = array('nickname' => $nickname, 'fullname' => $fullname);
-
-        if (!empty($invite)) {
-            $args['code'] = $invite->code;
-        }
-
-        $user = User::register($args);
-
-        $result = $this->flinkUser($user->id, $this->fbuid);
-
-        if (!$result) {
-            $this->serverError(_('Error connecting user to Facebook.'));
-            return;
-        }
-
-        common_set_user($user);
-        common_real_login(true);
-
-        common_debug('Facebook Connect Plugin - ' .
-                     "Registered new user $user->id from Facebook user $this->fbuid");
-
-        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
-                        303);
-    }
-
-    function connectNewUser()
-    {
-        $nickname = $this->trimmed('nickname');
-        $password = $this->trimmed('password');
-
-        if (!common_check_user($nickname, $password)) {
-            $this->showForm(_('Invalid username or password.'));
-            return;
-        }
-
-        $user = User::staticGet('nickname', $nickname);
-
-        if (!empty($user)) {
-            common_debug('Facebook Connect Plugin - ' .
-                         "Legit user to connect to Facebook: $nickname");
-        }
-
-        $result = $this->flinkUser($user->id, $this->fbuid);
-
-        if (!$result) {
-            $this->serverError(_('Error connecting user to Facebook.'));
-            return;
-        }
-
-        common_debug('Facebook Connnect Plugin - ' .
-                     "Connected Facebook user $this->fbuid to local user $user->id");
-
-        common_set_user($user);
-        common_real_login(true);
-
-        $this->goHome($user->nickname);
-    }
-
-    function connectUser()
-    {
-        $user = common_current_user();
-
-        $result = $this->flinkUser($user->id, $this->fbuid);
-
-        if (empty($result)) {
-            $this->serverError(_('Error connecting user to Facebook.'));
-            return;
-        }
-
-        common_debug('Facebook Connect Plugin - ' .
-                     "Connected Facebook user $this->fbuid to local user $user->id");
-
-        // Return to Facebook connection settings tab
-        common_redirect(common_local_url('FBConnectSettings'), 303);
-    }
-
-    function tryLogin()
-    {
-        common_debug('Facebook Connect Plugin - ' .
-                     "Trying login for Facebook user $this->fbuid.");
-
-        $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
-
-        if (!empty($flink)) {
-            $user = $flink->getUser();
-
-            if (!empty($user)) {
-
-                common_debug('Facebook Connect Plugin - ' .
-                             "Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
-
-                common_set_user($user);
-                common_real_login(true);
-                $this->goHome($user->nickname);
-            }
-
-        } else {
-
-            common_debug('Facebook Connect Plugin - ' .
-                         "No flink found for fbuid: $this->fbuid - new user");
-
-            $this->showForm(null, $this->bestNewNickname());
-        }
-    }
-
-    function goHome($nickname)
-    {
-        $url = common_get_returnto();
-        if ($url) {
-            // We don't have to return to it again
-            common_set_returnto(null);
-        } else {
-            $url = common_local_url('all',
-                                    array('nickname' =>
-                                          $nickname));
-        }
-
-        common_redirect($url, 303);
-    }
-
-    function flinkUser($user_id, $fbuid)
-    {
-        $flink = new Foreign_link();
-        $flink->user_id = $user_id;
-        $flink->foreign_id = $fbuid;
-        $flink->service = FACEBOOK_CONNECT_SERVICE;
-        $flink->created = common_sql_now();
-
-        $flink_id = $flink->insert();
-
-        return $flink_id;
-    }
-
-    function bestNewNickname()
-    {
-        if (!empty($this->fb_fields['name'])) {
-            $nickname = $this->nicknamize($this->fb_fields['name']);
-            if ($this->isNewNickname($nickname)) {
-                return $nickname;
-            }
-        }
-
-        // Try the full name
-
-        $fullname = trim($this->fb_fields['firstname'] .
-            ' ' . $this->fb_fields['lastname']);
-
-        if (!empty($fullname)) {
-            $fullname = $this->nicknamize($fullname);
-            if ($this->isNewNickname($fullname)) {
-                return $fullname;
-            }
-        }
-
-        return null;
-    }
-
-     // Given a string, try to make it work as a nickname
-
-     function nicknamize($str)
-     {
-         $str = preg_replace('/\W/', '', $str);
-         return strtolower($str);
-     }
-
-    function isNewNickname($str)
-    {
-        if (!Validate::string($str, array('min_length' => 1,
-                                          'max_length' => 64,
-                                          'format' => NICKNAME_FMT))) {
-            return false;
-        }
-        if (!User::allowed_nickname($str)) {
-            return false;
-        }
-        if (User::staticGet('nickname', $str)) {
-            return false;
-        }
-        return true;
-    }
-
-    // XXX: Consider moving this to lib/facebookutil.php
-    function getFacebookFields($fb_uid, $fields) {
-        try {
-
-            $facebook = getFacebook();
-
-            $infos = $facebook->api_client->users_getInfo($fb_uid, $fields);
-
-            if (empty($infos)) {
-                return null;
-            }
-            return reset($infos);
-
-        } catch (Exception $e) {
-            common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
-                       "Facebook client failure when requesting " .
-                join(",", $fields) . " on uid " . $fb_uid .
-                    " : ". $e->getMessage());
-            return null;
-        }
-    }
-
-}
diff --git a/plugins/FBConnect/FBConnectLogin.php b/plugins/FBConnect/FBConnectLogin.php
deleted file mode 100644 (file)
index 5696d88..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-/*
- * StatusNet - a distributed open-source microblogging tool
- * Copyright (C) 2008, Controlez-Vous, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
-
-class FBConnectLoginAction extends Action
-{
-    function handle($args)
-    {
-        parent::handle($args);
-
-        if (common_is_real_login()) {
-            $this->clientError(_('Already logged in.'));
-        }
-
-        $this->showPage();
-    }
-
-    function getInstructions()
-    {
-        return _('Login with your Facebook Account');
-    }
-
-    function showPageNotice()
-    {
-        $instr = $this->getInstructions();
-        $output = common_markup_to_html($instr);
-        $this->elementStart('div', 'instructions');
-        $this->raw($output);
-        $this->elementEnd('div');
-    }
-
-    function title()
-    {
-        return _('Facebook Login');
-    }
-
-    function showContent() {
-
-        $this->elementStart('fieldset');
-        $this->element('fb:login-button', array('onlogin' => 'goto_login()',
-            'length' => 'long'));
-
-        $this->elementEnd('fieldset');
-    }
-
-}
diff --git a/plugins/FBConnect/FBConnectPlugin.css b/plugins/FBConnect/FBConnectPlugin.css
deleted file mode 100644 (file)
index 49217bf..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-/** Styles for Facebook logo and Facebook user profile avatar.
- *
- * @package   StatusNet
- * @author Sarven Capadisli <csarven@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-#site_nav_global_primary #nav_fb {
-position:relative;
-margin-left:18px;
-}
-
-#nav_fb #fbc_profile-pic {
-position:absolute;
-top:-3px;
-left:-18px;
-display:inline;
-border:1px solid #3B5998;
-padding:1px;
-}
-
-#nav_fb #fb_favicon {
-position:absolute;
-top:-13px;
-left:-25px;
-display:inline;
-}
-
-#settings_facebook_connect_options legend {
-display:none;
-}
-#form_settings_facebook_connect fieldset fieldset legend {
-display:block;
-}
diff --git a/plugins/FBConnect/FBConnectPlugin.php b/plugins/FBConnect/FBConnectPlugin.php
deleted file mode 100644 (file)
index 0dacf90..0000000
+++ /dev/null
@@ -1,367 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Plugin to enable Facebook Connect
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Plugin
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-define("FACEBOOK_CONNECT_SERVICE", 3);
-
-require_once INSTALLDIR . '/lib/facebookutil.php';
-require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php';
-require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
-require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
-require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
-require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
-require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
-
-/**
- * Plugin to enable Facebook Connect
- *
- * @category Plugin
- * @package  StatusNet
- * @author   Zach Copley <zach@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- */
-
-class FBConnectPlugin extends Plugin
-{
-    function __construct()
-    {
-        parent::__construct();
-    }
-
-    // Hook in new actions
-    function onRouterInitialized(&$m) {
-        $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
-        $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
-        $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
-        $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
-     }
-
-    // Add in xmlns:fb
-    function onStartShowHTML($action)
-    {
-
-        if ($this->reqFbScripts($action)) {
-
-            // XXX: Horrible hack to make Safari, FF2, and Chrome work with
-            // Facebook Connect. These browser cannot use Facebook's
-            // DOM parsing routines unless the mime type of the page is
-            // text/html even though Facebook Connect uses XHTML.  This is
-            // A bug in Facebook Connect, and this is a temporary solution
-            // until they fix their JavaScript libs.
-            header('Content-Type: text/html');
-
-            $action->extraHeaders();
-
-            $action->startXML('html');
-
-            $language = $action->getLanguage();
-
-            $action->elementStart('html',
-                array('xmlns'  => 'http://www.w3.org/1999/xhtml',
-                      'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
-                      'xml:lang' => $language,
-                      'lang'     => $language));
-
-            return false;
-
-        } else {
-
-            return true;
-        }
-    }
-
-    // Note: this script needs to appear in the <body>
-
-    function onEndShowScripts($action)
-    {
-        if ($this->reqFbScripts($action)) {
-
-            $apikey = common_config('facebook', 'apikey');
-            $plugin_path = common_path('plugins/FBConnect');
-
-            $login_url = common_local_url('FBConnectAuth');
-            $logout_url = common_local_url('logout');
-
-            // XXX: Facebook says we don't need this FB_RequireFeatures(),
-            // but we actually do, for IE and Safari. Gar.
-
-            $js =  '<script type="text/javascript">';
-            $js .= '    $(document).ready(function () {';
-            $js .= '         FB_RequireFeatures(';
-            $js .= '             ["XFBML"], function() {';
-            $js .= '                 FB.init("%1$s", "../xd_receiver.html");';
-            $js .= '             }';
-            $js .= '         );';
-            $js .= '    });';
-
-            $js .= '    function goto_login() {';
-            $js .= '        window.location = "%2$s";';
-            $js .= '    }';
-
-            // The below function alters the logout link so that it logs the user out
-            // of Facebook Connect as well as the site.  However, for some pages
-            // (FB Connect Settings) we need to output the FB Connect scripts (to
-            // show an existing FB connection even if the user isn't authenticated
-            // with Facebook connect) but NOT alter the logout link. And the only
-            // way to reliably do that is with the FB Connect .js libs.  Crazy.
-
-            $js .= '    FB.ensureInit(function() {';
-            $js .= '        FB.Connect.ifUserConnected(';
-            $js .= '            function() { ';
-            $js .= '                $(\'#nav_logout a\').attr(\'href\', \'#\');';
-            $js .= '                $(\'#nav_logout a\').click(function() {';
-            $js .= '                   FB.Connect.logoutAndRedirect(\'%3$s\');';
-            $js .= '                   return false;';
-            $js .= '                })';
-            $js .= '            },';
-            $js .= '            function() {';
-            $js .= '                return false;';
-            $js .= '            }';
-            $js .= '        );';
-            $js .= '     });';
-            $js .= '</script>';
-
-            $js = sprintf($js, $apikey, $login_url, $logout_url);
-
-            // Compress the bugger down a bit
-            $js = str_replace('  ', '', $js);
-
-            $action->raw("  $js");  // leading two spaces to make it line up
-        }
-
-    }
-
-    // Note: this script needs to appear as close as possible to </body>
-
-    function onEndShowFooter($action)
-    {
-        if ($this->reqFbScripts($action)) {
-            $action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php');
-        }
-    }
-
-    function onEndShowStatusNetStyles($action)
-    {
-        if ($this->reqFbScripts($action)) {
-            $action->cssLink('plugins/FBConnect/FBConnectPlugin.css');
-        }
-    }
-
-    /**
-     * Does the Action we're plugged into require the FB Scripts?  We only
-     * want to output FB namespace, scripts, CSS, etc. on the pages that
-     * really need them.
-     *
-     * @param Action the action in question
-     *
-     * @return boolean true
-     */
-
-    function reqFbScripts($action) {
-
-        // If you're logged in w/FB Connect, you always need the FB stuff
-
-        $fbuid = $this->loggedIn();
-
-        if (!empty($fbuid)) {
-            return true;
-        }
-
-        // List of actions that require FB stuff
-
-        $needy = array('FBConnectLoginAction',
-                       'FBConnectauthAction',
-                       'FBConnectSettingsAction');
-
-        if (in_array(get_class($action), $needy)) {
-            return true;
-        }
-
-        return false;
-
-    }
-
-    /**
-     * Is the user currently logged in with FB Connect?
-     *
-     * @return mixed $fbuid the Facebook ID of the logged in user, or null
-     */
-
-    function loggedIn()
-    {
-        $user = common_current_user();
-
-        if (!empty($user)) {
-
-            $flink = Foreign_link::getByUserId($user->id,
-                FACEBOOK_CONNECT_SERVICE);
-            $fbuid = 0;
-
-            if (!empty($flink)) {
-
-                try {
-
-                    $facebook = getFacebook();
-                    $fbuid    = $facebook->get_loggedin_user();
-
-                } catch (Exception $e) {
-                    common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
-                        'Problem getting Facebook user: ' .
-                            $e->getMessage());
-                }
-
-                if ($fbuid > 0) {
-                    return $fbuid;
-                }
-            }
-        }
-
-        return null;
-    }
-
-    function onStartPrimaryNav($action)
-    {
-
-        $user = common_current_user();
-        $connect = 'FBConnectSettings';
-        if (common_config('xmpp', 'enabled')) {
-            $connect = 'imsettings';
-        } else if (common_config('sms', 'enabled')) {
-            $connect = 'smssettings';
-        } else if (common_config('twitter', 'enabled')) {
-            $connect = 'twittersettings';
-        }
-
-        if (!empty($user)) {
-
-            $fbuid = $this->loggedIn();
-
-            if (!empty($fbuid)) {
-
-                /* Default FB silhouette pic for FB users who haven't
-                   uploaded a profile pic yet. */
-
-                $silhouetteUrl =
-                    'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
-
-                $url = $this->getProfilePicURL($fbuid);
-
-                $action->elementStart('li', array('id' => 'nav_fb'));
-
-                $action->element('img', array('id' => 'fbc_profile-pic',
-                    'src' => (!empty($url)) ? $url : $silhouetteUrl,
-                    'alt' => 'Facebook Connect User',
-                    'width' => '16'), '');
-
-                $iconurl =  common_path('plugins/FBConnect/fbfavicon.ico');
-                $action->element('img', array('id' => 'fb_favicon',
-                    'src' => $iconurl));
-
-                $action->elementEnd('li');
-
-            }
-        }
-
-        return true;
-    }
-
-    function onStartShowLocalNavBlock($action)
-    {
-        $action_name   = get_class($action);
-
-        $login_actions = array('LoginAction', 'RegisterAction',
-            'OpenidloginAction', 'FBConnectLoginAction');
-
-        if (in_array($action_name, $login_actions)) {
-            $nav = new FBCLoginGroupNav($action);
-            $nav->show();
-            return false;
-        }
-
-        $connect_actions = array('SmssettingsAction', 'ImsettingsAction',
-            'TwittersettingsAction', 'FBConnectSettingsAction');
-
-        if (in_array($action_name, $connect_actions)) {
-            $nav = new FBCSettingsNav($action);
-            $nav->show();
-            return false;
-        }
-
-        return true;
-    }
-
-    function onStartLogout($action)
-    {
-        $action->logout();
-        $fbuid = $this->loggedIn();
-
-        if (!empty($fbuid)) {
-            try {
-                $facebook = getFacebook();
-                $facebook->expire_session();
-            } catch (Exception $e) {
-                common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
-                           'Could\'t logout of Facebook: ' .
-                           $e->getMessage());
-            }
-        }
-
-        return true;
-    }
-
-    function getProfilePicURL($fbuid)
-    {
-
-        $facebook = getFacebook();
-        $url      = null;
-
-        try {
-
-            $fqry = 'SELECT pic_square FROM user WHERE uid = %s';
-
-            $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
-
-            if (!empty($result)) {
-                $url = $result[0]['pic_square'];
-            }
-
-        } catch (Exception $e) {
-            common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
-                       "Facebook client failure requesting profile pic!");
-        }
-
-       return $url;
-
-    }
-
-}
diff --git a/plugins/FBConnect/FBConnectSettings.php b/plugins/FBConnect/FBConnectSettings.php
deleted file mode 100644 (file)
index 911c567..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Facebook Connect settings
- *
- * PHP version 5
- *
- * LICENCE: This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- * @category  Settings
- * @package   StatusNet
- * @author    Zach Copley <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/connectsettingsaction.php';
-
-/**
- * Facebook Connect settings action
- *
- * @category Settings
- * @package  StatusNet
- * @author   Zach Copley <zach@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- */
-
-class FBConnectSettingsAction extends ConnectSettingsAction
-{
-    /**
-     * Title of the page
-     *
-     * @return string Title of the page
-     */
-
-    function title()
-    {
-        return _('Facebook Connect Settings');
-    }
-
-    /**
-     * Instructions for use
-     *
-     * @return instructions for use
-     */
-
-    function getInstructions()
-    {
-        return _('Manage how your account connects to Facebook');
-    }
-
-    /**
-     * Content area of the page
-     *
-     * Shows a form for uploading an avatar.
-     *
-     * @return void
-     */
-
-    function showContent()
-    {
-        $user = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
-
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'form_settings_facebook',
-                                          'class' => 'form_settings',
-                                          'action' =>
-                                          common_local_url('FBConnectSettings')));
-
-        if (!$flink) {
-
-            $this->element('p', 'instructions',
-                _('There is no Facebook user connected to this account.'));
-
-            $this->element('fb:login-button', array('onlogin' => 'goto_login()',
-                'length' => 'long'));
-
-        } else {
-
-            $this->element('p', 'form_note',
-                           _('Connected Facebook user'));
-
-            $this->elementStart('p', array('class' => 'facebook-user-display'));
-            $this->elementStart('fb:profile-pic',
-                array('uid' => $flink->foreign_id,
-                      'size' => 'small',
-                      'linked' => 'true',
-                      'facebook-logo' => 'true'));
-            $this->elementEnd('fb:profile-pic');
-
-            $this->elementStart('fb:name', array('uid' => $flink->foreign_id,
-                                                 'useyou' => 'false'));
-            $this->elementEnd('fb:name');
-            $this->elementEnd('p');
-
-            $this->hidden('token', common_session_token());
-
-            $this->elementStart('fieldset');
-
-            $this->element('legend', null, _('Disconnect my account from Facebook'));
-
-            if (!$user->password) {
-
-                $this->elementStart('p', array('class' => 'form_guide'));
-                $this->text(_('Disconnecting your Faceboook ' .
-                              'would make it impossible to log in! Please '));
-                $this->element('a',
-                    array('href' => common_local_url('passwordsettings')),
-                        _('set a password'));
-
-                $this->text(_(' first.'));
-                $this->elementEnd('p');
-            } else {
-
-                $note = 'Keep your %s account but disconnect from Facebook. ' .
-                    'You\'ll use your %s password to log in.';
-
-                $site = common_config('site', 'name');
-
-                $this->element('p', 'instructions',
-                    sprintf($note, $site, $site));
-
-                $this->submit('disconnect', _('Disconnect'));
-            }
-
-            $this->elementEnd('fieldset');
-        }
-
-        $this->elementEnd('form');
-    }
-
-    /**
-     * Handle post
-     *
-     * Disconnects the current Facebook user from the current user's account
-     *
-     * @return void
-     */
-
-    function handlePost()
-    {
-        // CSRF protection
-        $token = $this->trimmed('token');
-        if (!$token || $token != common_session_token()) {
-            $this->showForm(_('There was a problem with your session token. '.
-                              'Try again, please.'));
-            return;
-        }
-
-        if ($this->arg('disconnect')) {
-
-            $user = common_current_user();
-
-            $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
-            $result = $flink->delete();
-
-            if ($result === false) {
-                common_log_db_error($user, 'DELETE', __FILE__);
-                $this->serverError(_('Couldn\'t delete link to Facebook.'));
-                return;
-            }
-
-            try {
-
-                // Clear FB Connect cookies out
-                $facebook = getFacebook();
-                $facebook->clear_cookie_state();
-
-            } catch (Exception $e) {
-                common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
-                           'Couldn\'t clear Facebook cookies: ' .
-                           $e->getMessage());
-            }
-
-            $this->showForm(_('You have disconnected from Facebook.'), true);
-
-        } else {
-            $this->showForm(_('Not sure what you\'re trying to do.'));
-            return;
-        }
-
-    }
-
-}
diff --git a/plugins/FBConnect/README b/plugins/FBConnect/README
deleted file mode 100644 (file)
index 77d57ef..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-This plugin allows you to utilize Facebook Connect with StatusNet.
-Supported Facebook Connect features:
-
-- Authenticate (register/login/logout -- works similar to OpenID)
-- Associate an existing StatusNet account with a Facebook account
-- Disconnect a Facebook account from a StatusNet account
-
-Future planned functionality:
-
-- Invite Facebook friends to use your StatusNet installation
-- Auto-subscribe Facebook friends already using StatusNet
-- Share StatusNet favorite notices to your Facebook stream
-
-To use the plugin you will need to configure a Facebook application
-to point to your StatusNet installation (see the Installation section
-below).
-
-Installation
-============
-
-If you don't already have the built-in Facebook application configured,
-you'll need to log into Facebook and create/configure a new application.
-Please follow the instructions in the section titled, "Setting Up Your
-Application and Getting an API Key," on the following page of the
-Facebook developer wiki:
-
-    http://wiki.developers.facebook.com/index.php/Connect/Setting_Up_Your_Site
-
-If you already are using the build-in StatusNet Facebook application,
-you can modify your existing application's configuration using the
-Facebook Developer Application on Facebook.  Use it to edit your
-application settings, and under the 'Connect' tab, change the 'Connect
-URL' to be the main URL for your StatusNet site.  E.g.:
-
-    http://SITE/PATH_TO_STATUSNET/
-
-After you application is created and configured, you'll need to add its
-API key and secret to your StatusNet config.php file:
-
-    $config['facebook']['apikey'] = 'APIKEY';
-    $config['facebook']['secret'] = 'SECRET';
-
-Finally, to enable the plugin, add the following stanza to your
-config.php:
-
-    addPlugin('FBConnect');
-
-To try out the plugin, fire up your browser and connect to:
-
-    http://SITE/PATH_TO_STATUSNET/main/facebooklogin
-
-or, if you do not have fancy URLs turned on:
-
-    http://SITE/PATH_TO_STATUSNET/index.php/main/facebooklogin
-
-You should see a page with a blue button that says: "Connect with
-Facebook".
-
-Connect/Disconnect existing account
-===================================
-
-If the Facebook Connect plugin is enabled, there will be a new Facebook
-Connect Settings tab under each user's Connect menu. Users can connect
-and disconnect to their Facebook accounts from it.  Note:  Before a user
-can disconnect from Facebook, she must set a normal StatusNet password.
-Otherwise, she might not be able to login in to her account in the
-future.  This is usually only required for users who have used Facebook
-Connect to register their StatusNet account, and therefore haven't
-already set a local password.
-
-Helpful links
-=============
-
-Facebook Connect Homepage:
-http://developers.facebook.com/connect.php
-
diff --git a/plugins/FBConnect/fbfavicon.ico b/plugins/FBConnect/fbfavicon.ico
deleted file mode 100644 (file)
index c57c034..0000000
Binary files a/plugins/FBConnect/fbfavicon.ico and /dev/null differ
diff --git a/plugins/Facebook/FBCLoginGroupNav.php b/plugins/Facebook/FBCLoginGroupNav.php
new file mode 100644 (file)
index 0000000..81b2520
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Menu for login group of actions
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Menu
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/widget.php';
+
+/**
+ * Menu for login group of actions
+ *
+ * @category Output
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @see      Widget
+ */
+
+class FBCLoginGroupNav extends Widget
+{
+    var $action = null;
+
+    /**
+     * Construction
+     *
+     * @param Action $action current action, used for output
+     */
+
+    function __construct($action=null)
+    {
+        parent::__construct($action);
+        $this->action = $action;
+    }
+
+    /**
+     * Show the menu
+     *
+     * @return void
+     */
+
+    function show()
+    {
+        $this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
+        $this->action->element('dt', null, _('Local views'));
+        $this->action->elementStart('dd');
+
+        // action => array('prompt', 'title')
+        $menu = array();
+
+        if (!common_config('site','openidonly')) {
+            $menu['login'] = array(_('Login'),
+                             _('Login with a username and password'));
+
+            if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
+                $menu['register'] = array(_('Register'),
+                                    _('Sign up for a new account'));
+            }
+        }
+
+        if (common_config('openid', 'enabled')) {
+            $menu['openidlogin'] = array(_('OpenID'),
+                                   _('Login or register with OpenID'));
+        }
+
+        $menu['FBConnectLogin'] = array(_('Facebook'),
+                               _('Login or register using Facebook'));
+
+        $action_name = $this->action->trimmed('action');
+        $this->action->elementStart('ul', array('class' => 'nav'));
+
+        foreach ($menu as $menuaction => $menudesc) {
+            $this->action->menuItem(common_local_url($menuaction),
+                                    $menudesc[0],
+                                    $menudesc[1],
+                                    $action_name === $menuaction);
+        }
+
+        $this->action->elementEnd('ul');
+
+        $this->action->elementEnd('dd');
+        $this->action->elementEnd('dl');
+    }
+}
diff --git a/plugins/Facebook/FBCSettingsNav.php b/plugins/Facebook/FBCSettingsNav.php
new file mode 100644 (file)
index 0000000..ed02371
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Menu for login group of actions
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Menu
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/widget.php';
+
+/**
+ * A widget for showing the connect group local nav menu
+ *
+ * @category Output
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @see      Widget
+ */
+
+class FBCSettingsNav extends Widget
+{
+    var $action = null;
+
+    /**
+     * Construction
+     *
+     * @param Action $action current action, used for output
+     */
+
+    function __construct($action=null)
+    {
+        parent::__construct($action);
+        $this->action = $action;
+    }
+
+    /**
+     * Show the menu
+     *
+     * @return void
+     */
+
+    function show()
+    {
+
+        $this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
+        $this->action->element('dt', null, _('Local views'));
+        $this->action->elementStart('dd');
+
+        # action => array('prompt', 'title')
+        $menu = array();
+        if (common_config('xmpp', 'enabled')) {
+            $menu['imsettings'] =
+              array(_('IM'),
+                    _('Updates by instant messenger (IM)'));
+        }
+        if (common_config('sms', 'enabled')) {
+            $menu['smssettings'] =
+              array(_('SMS'),
+                    _('Updates by SMS'));
+        }
+        if (common_config('twitter', 'enabled')) {
+            $menu['twittersettings'] =
+              array(_('Twitter'),
+                    _('Twitter integration options'));
+        }
+        $menu['FBConnectSettings'] =
+          array(_('Facebook'),
+                _('Facebook Connect settings'));
+
+        $action_name = $this->action->trimmed('action');
+        $this->action->elementStart('ul', array('class' => 'nav'));
+
+        foreach ($menu as $menuaction => $menudesc) {
+            $this->action->menuItem(common_local_url($menuaction),
+                                    $menudesc[0],
+                                    $menudesc[1],
+                                    $action_name === $menuaction);
+        }
+
+        $this->action->elementEnd('ul');
+
+        $this->action->elementEnd('dd');
+        $this->action->elementEnd('dl');
+    }
+}
diff --git a/plugins/Facebook/FBC_XDReceiver.php b/plugins/Facebook/FBC_XDReceiver.php
new file mode 100644 (file)
index 0000000..2bc790d
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+/*
+ * Generates the cross domain communication channel file
+ * (xd_receiver.html). By generating it we can add some caching
+ * instructions.
+ *
+ * See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
+ */
+class FBC_XDReceiverAction extends Action
+{
+
+    /**
+     * Do we need to write to the database?
+     *
+     * @return boolean true
+     */
+
+    function isReadonly()
+    {
+        return true;
+    }
+
+    /**
+     * Handle a request
+     *
+     * @param array $args Arguments from $_REQUEST
+     *
+     * @return void
+     */
+
+    function handle($args)
+    {
+        // Parent handling, including cache check
+        parent::handle($args);
+        $this->showPage();
+    }
+
+    function showPage()
+    {
+        // cache the xd_receiver
+        header('Cache-Control: max-age=225065900');
+        header('Expires:');
+        header('Pragma:');
+
+        $this->startXML('html');
+
+        $language = $this->getLanguage();
+
+        $this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
+                                          'xml:lang' => $language,
+                                          'lang' => $language));
+        $this->elementStart('head');
+        $this->element('title', null, 'cross domain receiver page');
+        $this->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js');
+        $this->elementEnd('head');
+        $this->elementStart('body');
+        $this->elementEnd('body');
+
+        $this->elementEnd('html');
+    }
+
+}
+
diff --git a/plugins/Facebook/FBConnectAuth.php b/plugins/Facebook/FBConnectAuth.php
new file mode 100644 (file)
index 0000000..647d5de
--- /dev/null
@@ -0,0 +1,461 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to enable Facebook Connect
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
+
+class FBConnectauthAction extends Action
+{
+    var $fbuid      = null;
+    var $fb_fields  = null;
+
+    function prepare($args) {
+        parent::prepare($args);
+
+        $this->fbuid = getFacebook()->get_loggedin_user();
+
+        if ($this->fbuid > 0) {
+            $this->fb_fields = $this->getFacebookFields($this->fbuid,
+                                                        array('first_name', 'last_name', 'name'));
+        } else {
+            list($proxy, $ip) = common_client_ip();
+
+            common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
+                       "Failed auth attempt, proxy = $proxy, ip = $ip.");
+
+            $this->clientError(_('You must be logged into Facebook to ' .
+                                 'use Facebook Connect.'));
+        }
+
+        return true;
+    }
+
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (common_is_real_login()) {
+
+            // User is already logged in.  Does she already have a linked Facebook acct?
+            $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
+
+            if (!empty($flink)) {
+
+                // User already has a linked Facebook account and shouldn't be here
+                common_debug('Facebook Connect Plugin - ' .
+                             'There is already a local user (' . $flink->user_id .
+                             ') linked with this Facebook (' . $this->fbuid . ').');
+
+                // We don't want these cookies
+                getFacebook()->clear_cookie_state();
+
+                $this->clientError(_('There is already a local user linked with this Facebook.'));
+
+            } else {
+
+                // User came from the Facebook connect settings tab, and
+                // probably just wants to link/relink their Facebook account
+                $this->connectUser();
+            }
+
+        } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+
+            $token = $this->trimmed('token');
+            if (!$token || $token != common_session_token()) {
+                $this->showForm(_('There was a problem with your session token. Try again, please.'));
+                return;
+            }
+            if ($this->arg('create')) {
+                if (!$this->boolean('license')) {
+                    $this->showForm(_('You can\'t register if you don\'t agree to the license.'),
+                                    $this->trimmed('newname'));
+                    return;
+                }
+                $this->createNewUser();
+            } else if ($this->arg('connect')) {
+                $this->connectNewUser();
+            } else {
+                common_debug('Facebook Connect Plugin - ' .
+                             print_r($this->args, true));
+                $this->showForm(_('Something weird happened.'),
+                                $this->trimmed('newname'));
+            }
+        } else {
+            $this->tryLogin();
+        }
+    }
+
+    function showPageNotice()
+    {
+        if ($this->error) {
+            $this->element('div', array('class' => 'error'), $this->error);
+        } else {
+            $this->element('div', 'instructions',
+                           sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
+        }
+    }
+
+    function title()
+    {
+        return _('Facebook Account Setup');
+    }
+
+    function showForm($error=null, $username=null)
+    {
+        $this->error = $error;
+        $this->username = $username;
+
+        $this->showPage();
+    }
+
+    function showPage()
+    {
+        parent::showPage();
+    }
+
+    function showContent()
+    {
+        if (!empty($this->message_text)) {
+            $this->element('p', null, $this->message);
+            return;
+        }
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_facebook_connect',
+                                          'class' => 'form_settings',
+                                          'action' => common_local_url('FBConnectAuth')));
+        $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
+        $this->element('legend', null, _('Connection options'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->element('input', array('type' => 'checkbox',
+                                      'id' => 'license',
+                                      'class' => 'checkbox',
+                                      'name' => 'license',
+                                      'value' => 'true'));
+        $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
+        $this->text(_('My text and files are available under '));
+        $this->element('a', array('href' => common_config('license', 'url')),
+                       common_config('license', 'title'));
+        $this->text(_(' except this private data: password, email address, IM address, phone number.'));
+        $this->elementEnd('label');
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+
+        $this->elementStart('fieldset');
+        $this->hidden('token', common_session_token());
+        $this->element('legend', null,
+                       _('Create new account'));
+        $this->element('p', null,
+                       _('Create a new user with this nickname.'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('newname', _('New nickname'),
+                     ($this->username) ? $this->username : '',
+                     _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('create', _('Create'));
+        $this->elementEnd('fieldset');
+
+        $this->elementStart('fieldset');
+        $this->element('legend', null,
+                       _('Connect existing account'));
+        $this->element('p', null,
+                       _('If you already have an account, login with your username and password to connect it to your Facebook.'));
+        $this->elementStart('ul', 'form_data');
+        $this->elementStart('li');
+        $this->input('nickname', _('Existing nickname'));
+        $this->elementEnd('li');
+        $this->elementStart('li');
+        $this->password('password', _('Password'));
+        $this->elementEnd('li');
+        $this->elementEnd('ul');
+        $this->submit('connect', _('Connect'));
+        $this->elementEnd('fieldset');
+
+        $this->elementEnd('fieldset');
+        $this->elementEnd('form');
+    }
+
+    function message($msg)
+    {
+        $this->message_text = $msg;
+        $this->showPage();
+    }
+
+    function createNewUser()
+    {
+        if (common_config('site', 'closed')) {
+            $this->clientError(_('Registration not allowed.'));
+            return;
+        }
+
+        $invite = null;
+
+        if (common_config('site', 'inviteonly')) {
+            $code = $_SESSION['invitecode'];
+            if (empty($code)) {
+                $this->clientError(_('Registration not allowed.'));
+                return;
+            }
+
+            $invite = Invitation::staticGet($code);
+
+            if (empty($invite)) {
+                $this->clientError(_('Not a valid invitation code.'));
+                return;
+            }
+        }
+
+        $nickname = $this->trimmed('newname');
+
+        if (!Validate::string($nickname, array('min_length' => 1,
+                                               'max_length' => 64,
+                                               'format' => NICKNAME_FMT))) {
+            $this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
+            return;
+        }
+
+        if (!User::allowed_nickname($nickname)) {
+            $this->showForm(_('Nickname not allowed.'));
+            return;
+        }
+
+        if (User::staticGet('nickname', $nickname)) {
+            $this->showForm(_('Nickname already in use. Try another one.'));
+            return;
+        }
+
+        $fullname = trim($this->fb_fields['firstname'] .
+            ' ' . $this->fb_fields['lastname']);
+
+        $args = array('nickname' => $nickname, 'fullname' => $fullname);
+
+        if (!empty($invite)) {
+            $args['code'] = $invite->code;
+        }
+
+        $user = User::register($args);
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_set_user($user);
+        common_real_login(true);
+
+        common_debug('Facebook Connect Plugin - ' .
+                     "Registered new user $user->id from Facebook user $this->fbuid");
+
+        common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
+                        303);
+    }
+
+    function connectNewUser()
+    {
+        $nickname = $this->trimmed('nickname');
+        $password = $this->trimmed('password');
+
+        if (!common_check_user($nickname, $password)) {
+            $this->showForm(_('Invalid username or password.'));
+            return;
+        }
+
+        $user = User::staticGet('nickname', $nickname);
+
+        if (!empty($user)) {
+            common_debug('Facebook Connect Plugin - ' .
+                         "Legit user to connect to Facebook: $nickname");
+        }
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (!$result) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_debug('Facebook Connnect Plugin - ' .
+                     "Connected Facebook user $this->fbuid to local user $user->id");
+
+        common_set_user($user);
+        common_real_login(true);
+
+        $this->goHome($user->nickname);
+    }
+
+    function connectUser()
+    {
+        $user = common_current_user();
+
+        $result = $this->flinkUser($user->id, $this->fbuid);
+
+        if (empty($result)) {
+            $this->serverError(_('Error connecting user to Facebook.'));
+            return;
+        }
+
+        common_debug('Facebook Connect Plugin - ' .
+                     "Connected Facebook user $this->fbuid to local user $user->id");
+
+        // Return to Facebook connection settings tab
+        common_redirect(common_local_url('FBConnectSettings'), 303);
+    }
+
+    function tryLogin()
+    {
+        common_debug('Facebook Connect Plugin - ' .
+                     "Trying login for Facebook user $this->fbuid.");
+
+        $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
+
+        if (!empty($flink)) {
+            $user = $flink->getUser();
+
+            if (!empty($user)) {
+
+                common_debug('Facebook Connect Plugin - ' .
+                             "Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
+
+                common_set_user($user);
+                common_real_login(true);
+                $this->goHome($user->nickname);
+            }
+
+        } else {
+
+            common_debug('Facebook Connect Plugin - ' .
+                         "No flink found for fbuid: $this->fbuid - new user");
+
+            $this->showForm(null, $this->bestNewNickname());
+        }
+    }
+
+    function goHome($nickname)
+    {
+        $url = common_get_returnto();
+        if ($url) {
+            // We don't have to return to it again
+            common_set_returnto(null);
+        } else {
+            $url = common_local_url('all',
+                                    array('nickname' =>
+                                          $nickname));
+        }
+
+        common_redirect($url, 303);
+    }
+
+    function flinkUser($user_id, $fbuid)
+    {
+        $flink = new Foreign_link();
+        $flink->user_id = $user_id;
+        $flink->foreign_id = $fbuid;
+        $flink->service = FACEBOOK_CONNECT_SERVICE;
+        $flink->created = common_sql_now();
+
+        $flink_id = $flink->insert();
+
+        return $flink_id;
+    }
+
+    function bestNewNickname()
+    {
+        if (!empty($this->fb_fields['name'])) {
+            $nickname = $this->nicknamize($this->fb_fields['name']);
+            if ($this->isNewNickname($nickname)) {
+                return $nickname;
+            }
+        }
+
+        // Try the full name
+
+        $fullname = trim($this->fb_fields['firstname'] .
+            ' ' . $this->fb_fields['lastname']);
+
+        if (!empty($fullname)) {
+            $fullname = $this->nicknamize($fullname);
+            if ($this->isNewNickname($fullname)) {
+                return $fullname;
+            }
+        }
+
+        return null;
+    }
+
+     // Given a string, try to make it work as a nickname
+
+     function nicknamize($str)
+     {
+         $str = preg_replace('/\W/', '', $str);
+         return strtolower($str);
+     }
+
+    function isNewNickname($str)
+    {
+        if (!Validate::string($str, array('min_length' => 1,
+                                          'max_length' => 64,
+                                          'format' => NICKNAME_FMT))) {
+            return false;
+        }
+        if (!User::allowed_nickname($str)) {
+            return false;
+        }
+        if (User::staticGet('nickname', $str)) {
+            return false;
+        }
+        return true;
+    }
+
+    // XXX: Consider moving this to lib/facebookutil.php
+    function getFacebookFields($fb_uid, $fields) {
+        try {
+
+            $facebook = getFacebook();
+
+            $infos = $facebook->api_client->users_getInfo($fb_uid, $fields);
+
+            if (empty($infos)) {
+                return null;
+            }
+            return reset($infos);
+
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
+                       "Facebook client failure when requesting " .
+                join(",", $fields) . " on uid " . $fb_uid .
+                    " : ". $e->getMessage());
+            return null;
+        }
+    }
+
+}
diff --git a/plugins/Facebook/FBConnectLogin.php b/plugins/Facebook/FBConnectLogin.php
new file mode 100644 (file)
index 0000000..5696d88
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/*
+ * StatusNet - a distributed open-source microblogging tool
+ * Copyright (C) 2008, Controlez-Vous, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
+
+class FBConnectLoginAction extends Action
+{
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (common_is_real_login()) {
+            $this->clientError(_('Already logged in.'));
+        }
+
+        $this->showPage();
+    }
+
+    function getInstructions()
+    {
+        return _('Login with your Facebook Account');
+    }
+
+    function showPageNotice()
+    {
+        $instr = $this->getInstructions();
+        $output = common_markup_to_html($instr);
+        $this->elementStart('div', 'instructions');
+        $this->raw($output);
+        $this->elementEnd('div');
+    }
+
+    function title()
+    {
+        return _('Facebook Login');
+    }
+
+    function showContent() {
+
+        $this->elementStart('fieldset');
+        $this->element('fb:login-button', array('onlogin' => 'goto_login()',
+            'length' => 'long'));
+
+        $this->elementEnd('fieldset');
+    }
+
+}
diff --git a/plugins/Facebook/FBConnectPlugin.css b/plugins/Facebook/FBConnectPlugin.css
new file mode 100644 (file)
index 0000000..49217bf
--- /dev/null
@@ -0,0 +1,36 @@
+/** Styles for Facebook logo and Facebook user profile avatar.
+ *
+ * @package   StatusNet
+ * @author Sarven Capadisli <csarven@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+#site_nav_global_primary #nav_fb {
+position:relative;
+margin-left:18px;
+}
+
+#nav_fb #fbc_profile-pic {
+position:absolute;
+top:-3px;
+left:-18px;
+display:inline;
+border:1px solid #3B5998;
+padding:1px;
+}
+
+#nav_fb #fb_favicon {
+position:absolute;
+top:-13px;
+left:-25px;
+display:inline;
+}
+
+#settings_facebook_connect_options legend {
+display:none;
+}
+#form_settings_facebook_connect fieldset fieldset legend {
+display:block;
+}
diff --git a/plugins/Facebook/FBConnectPlugin.php b/plugins/Facebook/FBConnectPlugin.php
new file mode 100644 (file)
index 0000000..0dacf90
--- /dev/null
@@ -0,0 +1,367 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Plugin to enable Facebook Connect
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Plugin
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+define("FACEBOOK_CONNECT_SERVICE", 3);
+
+require_once INSTALLDIR . '/lib/facebookutil.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
+require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
+
+/**
+ * Plugin to enable Facebook Connect
+ *
+ * @category Plugin
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class FBConnectPlugin extends Plugin
+{
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    // Hook in new actions
+    function onRouterInitialized(&$m) {
+        $m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
+        $m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
+        $m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
+        $m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
+     }
+
+    // Add in xmlns:fb
+    function onStartShowHTML($action)
+    {
+
+        if ($this->reqFbScripts($action)) {
+
+            // XXX: Horrible hack to make Safari, FF2, and Chrome work with
+            // Facebook Connect. These browser cannot use Facebook's
+            // DOM parsing routines unless the mime type of the page is
+            // text/html even though Facebook Connect uses XHTML.  This is
+            // A bug in Facebook Connect, and this is a temporary solution
+            // until they fix their JavaScript libs.
+            header('Content-Type: text/html');
+
+            $action->extraHeaders();
+
+            $action->startXML('html');
+
+            $language = $action->getLanguage();
+
+            $action->elementStart('html',
+                array('xmlns'  => 'http://www.w3.org/1999/xhtml',
+                      'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
+                      'xml:lang' => $language,
+                      'lang'     => $language));
+
+            return false;
+
+        } else {
+
+            return true;
+        }
+    }
+
+    // Note: this script needs to appear in the <body>
+
+    function onEndShowScripts($action)
+    {
+        if ($this->reqFbScripts($action)) {
+
+            $apikey = common_config('facebook', 'apikey');
+            $plugin_path = common_path('plugins/FBConnect');
+
+            $login_url = common_local_url('FBConnectAuth');
+            $logout_url = common_local_url('logout');
+
+            // XXX: Facebook says we don't need this FB_RequireFeatures(),
+            // but we actually do, for IE and Safari. Gar.
+
+            $js =  '<script type="text/javascript">';
+            $js .= '    $(document).ready(function () {';
+            $js .= '         FB_RequireFeatures(';
+            $js .= '             ["XFBML"], function() {';
+            $js .= '                 FB.init("%1$s", "../xd_receiver.html");';
+            $js .= '             }';
+            $js .= '         );';
+            $js .= '    });';
+
+            $js .= '    function goto_login() {';
+            $js .= '        window.location = "%2$s";';
+            $js .= '    }';
+
+            // The below function alters the logout link so that it logs the user out
+            // of Facebook Connect as well as the site.  However, for some pages
+            // (FB Connect Settings) we need to output the FB Connect scripts (to
+            // show an existing FB connection even if the user isn't authenticated
+            // with Facebook connect) but NOT alter the logout link. And the only
+            // way to reliably do that is with the FB Connect .js libs.  Crazy.
+
+            $js .= '    FB.ensureInit(function() {';
+            $js .= '        FB.Connect.ifUserConnected(';
+            $js .= '            function() { ';
+            $js .= '                $(\'#nav_logout a\').attr(\'href\', \'#\');';
+            $js .= '                $(\'#nav_logout a\').click(function() {';
+            $js .= '                   FB.Connect.logoutAndRedirect(\'%3$s\');';
+            $js .= '                   return false;';
+            $js .= '                })';
+            $js .= '            },';
+            $js .= '            function() {';
+            $js .= '                return false;';
+            $js .= '            }';
+            $js .= '        );';
+            $js .= '     });';
+            $js .= '</script>';
+
+            $js = sprintf($js, $apikey, $login_url, $logout_url);
+
+            // Compress the bugger down a bit
+            $js = str_replace('  ', '', $js);
+
+            $action->raw("  $js");  // leading two spaces to make it line up
+        }
+
+    }
+
+    // Note: this script needs to appear as close as possible to </body>
+
+    function onEndShowFooter($action)
+    {
+        if ($this->reqFbScripts($action)) {
+            $action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php');
+        }
+    }
+
+    function onEndShowStatusNetStyles($action)
+    {
+        if ($this->reqFbScripts($action)) {
+            $action->cssLink('plugins/FBConnect/FBConnectPlugin.css');
+        }
+    }
+
+    /**
+     * Does the Action we're plugged into require the FB Scripts?  We only
+     * want to output FB namespace, scripts, CSS, etc. on the pages that
+     * really need them.
+     *
+     * @param Action the action in question
+     *
+     * @return boolean true
+     */
+
+    function reqFbScripts($action) {
+
+        // If you're logged in w/FB Connect, you always need the FB stuff
+
+        $fbuid = $this->loggedIn();
+
+        if (!empty($fbuid)) {
+            return true;
+        }
+
+        // List of actions that require FB stuff
+
+        $needy = array('FBConnectLoginAction',
+                       'FBConnectauthAction',
+                       'FBConnectSettingsAction');
+
+        if (in_array(get_class($action), $needy)) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Is the user currently logged in with FB Connect?
+     *
+     * @return mixed $fbuid the Facebook ID of the logged in user, or null
+     */
+
+    function loggedIn()
+    {
+        $user = common_current_user();
+
+        if (!empty($user)) {
+
+            $flink = Foreign_link::getByUserId($user->id,
+                FACEBOOK_CONNECT_SERVICE);
+            $fbuid = 0;
+
+            if (!empty($flink)) {
+
+                try {
+
+                    $facebook = getFacebook();
+                    $fbuid    = $facebook->get_loggedin_user();
+
+                } catch (Exception $e) {
+                    common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
+                        'Problem getting Facebook user: ' .
+                            $e->getMessage());
+                }
+
+                if ($fbuid > 0) {
+                    return $fbuid;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    function onStartPrimaryNav($action)
+    {
+
+        $user = common_current_user();
+        $connect = 'FBConnectSettings';
+        if (common_config('xmpp', 'enabled')) {
+            $connect = 'imsettings';
+        } else if (common_config('sms', 'enabled')) {
+            $connect = 'smssettings';
+        } else if (common_config('twitter', 'enabled')) {
+            $connect = 'twittersettings';
+        }
+
+        if (!empty($user)) {
+
+            $fbuid = $this->loggedIn();
+
+            if (!empty($fbuid)) {
+
+                /* Default FB silhouette pic for FB users who haven't
+                   uploaded a profile pic yet. */
+
+                $silhouetteUrl =
+                    'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
+
+                $url = $this->getProfilePicURL($fbuid);
+
+                $action->elementStart('li', array('id' => 'nav_fb'));
+
+                $action->element('img', array('id' => 'fbc_profile-pic',
+                    'src' => (!empty($url)) ? $url : $silhouetteUrl,
+                    'alt' => 'Facebook Connect User',
+                    'width' => '16'), '');
+
+                $iconurl =  common_path('plugins/FBConnect/fbfavicon.ico');
+                $action->element('img', array('id' => 'fb_favicon',
+                    'src' => $iconurl));
+
+                $action->elementEnd('li');
+
+            }
+        }
+
+        return true;
+    }
+
+    function onStartShowLocalNavBlock($action)
+    {
+        $action_name   = get_class($action);
+
+        $login_actions = array('LoginAction', 'RegisterAction',
+            'OpenidloginAction', 'FBConnectLoginAction');
+
+        if (in_array($action_name, $login_actions)) {
+            $nav = new FBCLoginGroupNav($action);
+            $nav->show();
+            return false;
+        }
+
+        $connect_actions = array('SmssettingsAction', 'ImsettingsAction',
+            'TwittersettingsAction', 'FBConnectSettingsAction');
+
+        if (in_array($action_name, $connect_actions)) {
+            $nav = new FBCSettingsNav($action);
+            $nav->show();
+            return false;
+        }
+
+        return true;
+    }
+
+    function onStartLogout($action)
+    {
+        $action->logout();
+        $fbuid = $this->loggedIn();
+
+        if (!empty($fbuid)) {
+            try {
+                $facebook = getFacebook();
+                $facebook->expire_session();
+            } catch (Exception $e) {
+                common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
+                           'Could\'t logout of Facebook: ' .
+                           $e->getMessage());
+            }
+        }
+
+        return true;
+    }
+
+    function getProfilePicURL($fbuid)
+    {
+
+        $facebook = getFacebook();
+        $url      = null;
+
+        try {
+
+            $fqry = 'SELECT pic_square FROM user WHERE uid = %s';
+
+            $result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
+
+            if (!empty($result)) {
+                $url = $result[0]['pic_square'];
+            }
+
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
+                       "Facebook client failure requesting profile pic!");
+        }
+
+       return $url;
+
+    }
+
+}
diff --git a/plugins/Facebook/FBConnectSettings.php b/plugins/Facebook/FBConnectSettings.php
new file mode 100644 (file)
index 0000000..911c567
--- /dev/null
@@ -0,0 +1,203 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Facebook Connect settings
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category  Settings
+ * @package   StatusNet
+ * @author    Zach Copley <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR.'/lib/connectsettingsaction.php';
+
+/**
+ * Facebook Connect settings action
+ *
+ * @category Settings
+ * @package  StatusNet
+ * @author   Zach Copley <zach@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ */
+
+class FBConnectSettingsAction extends ConnectSettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Facebook Connect Settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Manage how your account connects to Facebook');
+    }
+
+    /**
+     * Content area of the page
+     *
+     * Shows a form for uploading an avatar.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_facebook',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('FBConnectSettings')));
+
+        if (!$flink) {
+
+            $this->element('p', 'instructions',
+                _('There is no Facebook user connected to this account.'));
+
+            $this->element('fb:login-button', array('onlogin' => 'goto_login()',
+                'length' => 'long'));
+
+        } else {
+
+            $this->element('p', 'form_note',
+                           _('Connected Facebook user'));
+
+            $this->elementStart('p', array('class' => 'facebook-user-display'));
+            $this->elementStart('fb:profile-pic',
+                array('uid' => $flink->foreign_id,
+                      'size' => 'small',
+                      'linked' => 'true',
+                      'facebook-logo' => 'true'));
+            $this->elementEnd('fb:profile-pic');
+
+            $this->elementStart('fb:name', array('uid' => $flink->foreign_id,
+                                                 'useyou' => 'false'));
+            $this->elementEnd('fb:name');
+            $this->elementEnd('p');
+
+            $this->hidden('token', common_session_token());
+
+            $this->elementStart('fieldset');
+
+            $this->element('legend', null, _('Disconnect my account from Facebook'));
+
+            if (!$user->password) {
+
+                $this->elementStart('p', array('class' => 'form_guide'));
+                $this->text(_('Disconnecting your Faceboook ' .
+                              'would make it impossible to log in! Please '));
+                $this->element('a',
+                    array('href' => common_local_url('passwordsettings')),
+                        _('set a password'));
+
+                $this->text(_(' first.'));
+                $this->elementEnd('p');
+            } else {
+
+                $note = 'Keep your %s account but disconnect from Facebook. ' .
+                    'You\'ll use your %s password to log in.';
+
+                $site = common_config('site', 'name');
+
+                $this->element('p', 'instructions',
+                    sprintf($note, $site, $site));
+
+                $this->submit('disconnect', _('Disconnect'));
+            }
+
+            $this->elementEnd('fieldset');
+        }
+
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Handle post
+     *
+     * Disconnects the current Facebook user from the current user's account
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('disconnect')) {
+
+            $user = common_current_user();
+
+            $flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
+            $result = $flink->delete();
+
+            if ($result === false) {
+                common_log_db_error($user, 'DELETE', __FILE__);
+                $this->serverError(_('Couldn\'t delete link to Facebook.'));
+                return;
+            }
+
+            try {
+
+                // Clear FB Connect cookies out
+                $facebook = getFacebook();
+                $facebook->clear_cookie_state();
+
+            } catch (Exception $e) {
+                common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
+                           'Couldn\'t clear Facebook cookies: ' .
+                           $e->getMessage());
+            }
+
+            $this->showForm(_('You have disconnected from Facebook.'), true);
+
+        } else {
+            $this->showForm(_('Not sure what you\'re trying to do.'));
+            return;
+        }
+
+    }
+
+}
index a350c5b5b7d96d6b08f256ba0f9efe2d5b77281b..a8aaa106649ab5099ee2471c3dd9bfd454601c7e 100644 (file)
@@ -1,4 +1,8 @@
 
+// Facebook plugin
+require_once(INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php');                                                      
+$fb = new FacebookPlugin();
+
 
 TODO:
 
diff --git a/plugins/Facebook/facebook/facebook.php b/plugins/Facebook/facebook/facebook.php
new file mode 100644 (file)
index 0000000..016e8e8
--- /dev/null
@@ -0,0 +1,598 @@
+<?php
+// Copyright 2004-2009 Facebook. All Rights Reserved.
+//
+// +---------------------------------------------------------------------------+
+// | Facebook Platform PHP5 client                                             |
+// +---------------------------------------------------------------------------+
+// | Copyright (c) 2007 Facebook, Inc.                                         |
+// | All rights reserved.                                                      |
+// |                                                                           |
+// | Redistribution and use in source and binary forms, with or without        |
+// | modification, are permitted provided that the following conditions        |
+// | are met:                                                                  |
+// |                                                                           |
+// | 1. Redistributions of source code must retain the above copyright         |
+// |    notice, this list of conditions and the following disclaimer.          |
+// | 2. Redistributions in binary form must reproduce the above copyright      |
+// |    notice, this list of conditions and the following disclaimer in the    |
+// |    documentation and/or other materials provided with the distribution.   |
+// |                                                                           |
+// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
+// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
+// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
+// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
+// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
+// +---------------------------------------------------------------------------+
+// | For help with this library, contact developers-help@facebook.com          |
+// +---------------------------------------------------------------------------+
+
+include_once 'facebookapi_php5_restlib.php';
+
+define('FACEBOOK_API_VALIDATION_ERROR', 1);
+class Facebook {
+  public $api_client;
+  public $api_key;
+  public $secret;
+  public $generate_session_secret;
+  public $session_expires;
+
+  public $fb_params;
+  public $user;
+  public $profile_user;
+  public $canvas_user;
+  protected $base_domain;
+  /*
+   * Create a Facebook client like this:
+   *
+   * $fb = new Facebook(API_KEY, SECRET);
+   *
+   * This will automatically pull in any parameters, validate them against the
+   * session signature, and chuck them in the public $fb_params member variable.
+   *
+   * @param api_key                  your Developer API key
+   * @param secret                   your Developer API secret
+   * @param generate_session_secret  whether to automatically generate a session
+   *                                 if the user doesn't have one, but
+   *                                 there is an auth token present in the url,
+   */
+  public function __construct($api_key, $secret, $generate_session_secret=false) {
+    $this->api_key                 = $api_key;
+    $this->secret                  = $secret;
+    $this->generate_session_secret = $generate_session_secret;
+    $this->api_client = new FacebookRestClient($api_key, $secret, null);
+    $this->validate_fb_params();
+
+    // Set the default user id for methods that allow the caller to
+    // pass an explicit uid instead of using a session key.
+    $defaultUser = null;
+    if ($this->user) {
+      $defaultUser = $this->user;
+    } else if ($this->profile_user) {
+      $defaultUser = $this->profile_user;
+    } else if ($this->canvas_user) {
+      $defaultUser = $this->canvas_user;
+    }
+
+    $this->api_client->set_user($defaultUser);
+
+
+    if (isset($this->fb_params['friends'])) {
+      $this->api_client->friends_list = explode(',', $this->fb_params['friends']);
+    }
+    if (isset($this->fb_params['added'])) {
+      $this->api_client->added = $this->fb_params['added'];
+    }
+    if (isset($this->fb_params['canvas_user'])) {
+      $this->api_client->canvas_user = $this->fb_params['canvas_user'];
+    }
+  }
+
+  /*
+   * Validates that the parameters passed in were sent from Facebook. It does so
+   * by validating that the signature matches one that could only be generated
+   * by using your application's secret key.
+   *
+   * Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE,
+   * in that order. $_POST and $_GET are always more up-to-date than cookies,
+   * so we prefer those if they are available.
+   *
+   * For nitty-gritty details of when each of these is used, check out
+   * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
+   *
+   * @param bool  resolve_auth_token  convert an auth token into a session
+   */
+  public function validate_fb_params($resolve_auth_token=true) {
+    $this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
+
+    // note that with preload FQL, it's possible to receive POST params in
+    // addition to GET, so use a different prefix to differentiate them
+    if (!$this->fb_params) {
+      $fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
+      $fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
+      $this->fb_params = array_merge($fb_params, $fb_post_params);
+    }
+
+    // Okay, something came in via POST or GET
+    if ($this->fb_params) {
+      $user               = isset($this->fb_params['user']) ?
+                            $this->fb_params['user'] : null;
+      $this->profile_user = isset($this->fb_params['profile_user']) ?
+                            $this->fb_params['profile_user'] : null;
+      $this->canvas_user  = isset($this->fb_params['canvas_user']) ?
+                            $this->fb_params['canvas_user'] : null;
+      $this->base_domain  = isset($this->fb_params['base_domain']) ?
+                            $this->fb_params['base_domain'] : null;
+
+      if (isset($this->fb_params['session_key'])) {
+        $session_key =  $this->fb_params['session_key'];
+      } else if (isset($this->fb_params['profile_session_key'])) {
+        $session_key =  $this->fb_params['profile_session_key'];
+      } else {
+        $session_key = null;
+      }
+      $expires     = isset($this->fb_params['expires']) ?
+                     $this->fb_params['expires'] : null;
+      $this->set_user($user,
+                      $session_key,
+                      $expires);
+    }
+    // if no Facebook parameters were found in the GET or POST variables,
+    // then fall back to cookies, which may have cached user information
+    // Cookies are also used to receive session data via the Javascript API
+    else if ($cookies =
+             $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
+
+      $base_domain_cookie = 'base_domain_' . $this->api_key;
+      if (isset($_COOKIE[$base_domain_cookie])) {
+        $this->base_domain = $_COOKIE[$base_domain_cookie];
+      }
+
+      // use $api_key . '_' as a prefix for the cookies in case there are
+      // multiple facebook clients on the same domain.
+      $expires = isset($cookies['expires']) ? $cookies['expires'] : null;
+      $this->set_user($cookies['user'],
+                      $cookies['session_key'],
+                      $expires);
+    }
+    // finally, if we received no parameters, but the 'auth_token' GET var
+    // is present, then we are in the middle of auth handshake,
+    // so go ahead and create the session
+    else if ($resolve_auth_token && isset($_GET['auth_token']) &&
+             $session = $this->do_get_session($_GET['auth_token'])) {
+      if ($this->generate_session_secret &&
+          !empty($session['secret'])) {
+        $session_secret = $session['secret'];
+      }
+
+      if (isset($session['base_domain'])) {
+        $this->base_domain = $session['base_domain'];
+      }
+
+      $this->set_user($session['uid'],
+                      $session['session_key'],
+                      $session['expires'],
+                      isset($session_secret) ? $session_secret : null);
+    }
+
+    return !empty($this->fb_params);
+  }
+
+  // Store a temporary session secret for the current session
+  // for use with the JS client library
+  public function promote_session() {
+    try {
+      $session_secret = $this->api_client->auth_promoteSession();
+      if (!$this->in_fb_canvas()) {
+        $this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret);
+      }
+      return $session_secret;
+    } catch (FacebookRestClientException $e) {
+      // API_EC_PARAM means we don't have a logged in user, otherwise who
+      // knows what it means, so just throw it.
+      if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
+        throw $e;
+      }
+    }
+  }
+
+  public function do_get_session($auth_token) {
+    try {
+      return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret);
+    } catch (FacebookRestClientException $e) {
+      // API_EC_PARAM means we don't have a logged in user, otherwise who
+      // knows what it means, so just throw it.
+      if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
+        throw $e;
+      }
+    }
+  }
+
+  // Invalidate the session currently being used, and clear any state associated
+  // with it. Note that the user will still remain logged into Facebook.
+  public function expire_session() {
+    if ($this->api_client->auth_expireSession()) {
+      $this->clear_cookie_state();
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /** Logs the user out of all temporary application sessions as well as their
+   * Facebook session.  Note this will only work if the user has a valid current
+   * session with the application.
+   *
+   * @param string  $next  URL to redirect to upon logging out
+   *
+   */
+   public function logout($next) {
+    $logout_url = $this->get_logout_url($next);
+
+    // Clear any stored state
+    $this->clear_cookie_state();
+
+    $this->redirect($logout_url);
+  }
+
+  /**
+   *  Clears any persistent state stored about the user, including
+   *  cookies and information related to the current session in the
+   *  client.
+   *
+   */
+  public function clear_cookie_state() {
+    if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
+       $cookies = array('user', 'session_key', 'expires', 'ss');
+       foreach ($cookies as $name) {
+         setcookie($this->api_key . '_' . $name, false, time() - 3600);
+         unset($_COOKIE[$this->api_key . '_' . $name]);
+       }
+       setcookie($this->api_key, false, time() - 3600);
+       unset($_COOKIE[$this->api_key]);
+     }
+
+     // now, clear the rest of the stored state
+     $this->user = 0;
+     $this->api_client->session_key = 0;
+  }
+
+  public function redirect($url) {
+    if ($this->in_fb_canvas()) {
+      echo '<fb:redirect url="' . $url . '"/>';
+    } else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) {
+      // make sure facebook.com url's load in the full frame so that we don't
+      // get a frame within a frame.
+      echo "<script type=\"text/javascript\">\ntop.location.href = \"$url\";\n</script>";
+    } else {
+      header('Location: ' . $url);
+    }
+    exit;
+  }
+
+  public function in_frame() {
+    return isset($this->fb_params['in_canvas'])
+        || isset($this->fb_params['in_iframe']);
+  }
+  public function in_fb_canvas() {
+    return isset($this->fb_params['in_canvas']);
+  }
+
+  public function get_loggedin_user() {
+    return $this->user;
+  }
+
+  public function get_canvas_user() {
+    return $this->canvas_user;
+  }
+
+  public function get_profile_user() {
+    return $this->profile_user;
+  }
+
+  public static function current_url() {
+    return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+  }
+
+  // require_add and require_install have been removed.
+  // see http://developer.facebook.com/news.php?blog=1&story=116 for more details
+  public function require_login() {
+    if ($user = $this->get_loggedin_user()) {
+      return $user;
+    }
+    $this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
+  }
+
+  public function require_frame() {
+    if (!$this->in_frame()) {
+      $this->redirect($this->get_login_url(self::current_url(), true));
+    }
+  }
+
+  public static function get_facebook_url($subdomain='www') {
+    return 'http://' . $subdomain . '.facebook.com';
+  }
+
+  public function get_install_url($next=null) {
+    // this was renamed, keeping for compatibility's sake
+    return $this->get_add_url($next);
+  }
+
+  public function get_add_url($next=null) {
+    $page = self::get_facebook_url().'/add.php';
+    $params = array('api_key' => $this->api_key);
+
+    if ($next) {
+      $params['next'] = $next;
+    }
+
+    return $page . '?' . http_build_query($params);
+  }
+
+  public function get_login_url($next, $canvas) {
+    $page = self::get_facebook_url().'/login.php';
+    $params = array('api_key' => $this->api_key,
+                    'v'       => '1.0');
+
+    if ($next) {
+      $params['next'] = $next;
+    }
+    if ($canvas) {
+      $params['canvas'] = '1';
+    }
+
+    return $page . '?' . http_build_query($params);
+  }
+
+  public function get_logout_url($next) {
+    $page = self::get_facebook_url().'/logout.php';
+    $params = array('app_key'     => $this->api_key,
+                    'session_key' => $this->api_client->session_key);
+
+    if ($next) {
+      $params['connect_next'] = 1;
+      $params['next'] = $next;
+    }
+
+    return $page . '?' . http_build_query($params);
+  }
+
+  public function set_user($user, $session_key, $expires=null, $session_secret=null) {
+    if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user'])
+                                   || $_COOKIE[$this->api_key . '_user'] != $user)) {
+      $this->set_cookies($user, $session_key, $expires, $session_secret);
+    }
+    $this->user = $user;
+    $this->api_client->session_key = $session_key;
+    $this->session_expires = $expires;
+  }
+
+  public function set_cookies($user, $session_key, $expires=null, $session_secret=null) {
+    $cookies = array();
+    $cookies['user'] = $user;
+    $cookies['session_key'] = $session_key;
+    if ($expires != null) {
+      $cookies['expires'] = $expires;
+    }
+    if ($session_secret != null) {
+      $cookies['ss'] = $session_secret;
+    }
+
+    foreach ($cookies as $name => $val) {
+      setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain);
+      $_COOKIE[$this->api_key . '_' . $name] = $val;
+    }
+    $sig = self::generate_sig($cookies, $this->secret);
+    setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain);
+    $_COOKIE[$this->api_key] = $sig;
+
+    if ($this->base_domain != null) {
+      $base_domain_cookie = 'base_domain_' . $this->api_key;
+      setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain);
+      $_COOKIE[$base_domain_cookie] = $this->base_domain;
+    }
+  }
+
+  /**
+   * Tries to undo the badness of magic quotes as best we can
+   * @param     string   $val   Should come directly from $_GET, $_POST, etc.
+   * @return    string   val without added slashes
+   */
+  public static function no_magic_quotes($val) {
+    if (get_magic_quotes_gpc()) {
+      return stripslashes($val);
+    } else {
+      return $val;
+    }
+  }
+
+  /*
+   * Get the signed parameters that were sent from Facebook. Validates the set
+   * of parameters against the included signature.
+   *
+   * Since Facebook sends data to your callback URL via unsecured means, the
+   * signature is the only way to make sure that the data actually came from
+   * Facebook. So if an app receives a request at the callback URL, it should
+   * always verify the signature that comes with against your own secret key.
+   * Otherwise, it's possible for someone to spoof a request by
+   * pretending to be someone else, i.e.:
+   *      www.your-callback-url.com/?fb_user=10101
+   *
+   * This is done automatically by verify_fb_params.
+   *
+   * @param  assoc  $params     a full array of external parameters.
+   *                            presumed $_GET, $_POST, or $_COOKIE
+   * @param  int    $timeout    number of seconds that the args are good for.
+   *                            Specifically good for forcing cookies to expire.
+   * @param  string $namespace  prefix string for the set of parameters we want
+   *                            to verify. i.e., fb_sig or fb_post_sig
+   *
+   * @return  assoc the subset of parameters containing the given prefix,
+   *                and also matching the signature associated with them.
+   *          OR    an empty array if the params do not validate
+   */
+  public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') {
+    $prefix = $namespace . '_';
+    $prefix_len = strlen($prefix);
+    $fb_params = array();
+    if (empty($params)) {
+      return array();
+    }
+
+    foreach ($params as $name => $val) {
+      // pull out only those parameters that match the prefix
+      // note that the signature itself ($params[$namespace]) is not in the list
+      if (strpos($name, $prefix) === 0) {
+        $fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val);
+      }
+    }
+
+    // validate that the request hasn't expired. this is most likely
+    // for params that come from $_COOKIE
+    if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) {
+      return array();
+    }
+
+    // validate that the params match the signature
+    $signature = isset($params[$namespace]) ? $params[$namespace] : null;
+    if (!$signature || (!$this->verify_signature($fb_params, $signature))) {
+      return array();
+    }
+    return $fb_params;
+  }
+
+  /**
+   *  Validates the account that a user was trying to set up an
+   *  independent account through Facebook Connect.
+   *
+   *  @param  user The user attempting to set up an independent account.
+   *  @param  hash The hash passed to the reclamation URL used.
+   *  @return bool True if the user is the one that selected the
+   *               reclamation link.
+   */
+  public function verify_account_reclamation($user, $hash) {
+    return $hash == md5($user . $this->secret);
+  }
+
+  /**
+   * Validates that a given set of parameters match their signature.
+   * Parameters all match a given input prefix, such as "fb_sig".
+   *
+   * @param $fb_params     an array of all Facebook-sent parameters,
+   *                       not including the signature itself
+   * @param $expected_sig  the expected result to check against
+   */
+  public function verify_signature($fb_params, $expected_sig) {
+    return self::generate_sig($fb_params, $this->secret) == $expected_sig;
+  }
+
+  /**
+   * Validate the given signed public session data structure with
+   * public key of the app that
+   * the session proof belongs to.
+   *
+   * @param $signed_data the session info that is passed by another app
+   * @param string $public_key Optional public key of the app. If this
+   *               is not passed, function will make an API call to get it.
+   * return true if the session proof passed verification.
+   */
+  public function verify_signed_public_session_data($signed_data,
+                                                    $public_key = null) {
+
+    // If public key is not already provided, we need to get it through API
+    if (!$public_key) {
+      $public_key = $this->api_client->auth_getAppPublicKey(
+        $signed_data['api_key']);
+    }
+
+    // Create data to verify
+    $data_to_serialize = $signed_data;
+    unset($data_to_serialize['sig']);
+    $serialized_data = implode('_', $data_to_serialize);
+
+    // Decode signature
+    $signature = base64_decode($signed_data['sig']);
+    $result = openssl_verify($serialized_data, $signature, $public_key,
+                             OPENSSL_ALGO_SHA1);
+    return $result == 1;
+  }
+
+  /*
+   * Generate a signature using the application secret key.
+   *
+   * The only two entities that know your secret key are you and Facebook,
+   * according to the Terms of Service. Since nobody else can generate
+   * the signature, you can rely on it to verify that the information
+   * came from Facebook.
+   *
+   * @param $params_array   an array of all Facebook-sent parameters,
+   *                        NOT INCLUDING the signature itself
+   * @param $secret         your app's secret key
+   *
+   * @return a hash to be checked against the signature provided by Facebook
+   */
+  public static function generate_sig($params_array, $secret) {
+    $str = '';
+
+    ksort($params_array);
+    // Note: make sure that the signature parameter is not already included in
+    //       $params_array.
+    foreach ($params_array as $k=>$v) {
+      $str .= "$k=$v";
+    }
+    $str .= $secret;
+
+    return md5($str);
+  }
+
+  public function encode_validationError($summary, $message) {
+    return json_encode(
+               array('errorCode'    => FACEBOOK_API_VALIDATION_ERROR,
+                     'errorTitle'   => $summary,
+                     'errorMessage' => $message));
+  }
+
+  public function encode_multiFeedStory($feed, $next) {
+    return json_encode(
+               array('method'   => 'multiFeedStory',
+                     'content'  =>
+                     array('next' => $next,
+                           'feed' => $feed)));
+  }
+
+  public function encode_feedStory($feed, $next) {
+    return json_encode(
+               array('method'   => 'feedStory',
+                     'content'  =>
+                     array('next' => $next,
+                           'feed' => $feed)));
+  }
+
+  public function create_templatizedFeedStory($title_template, $title_data=array(),
+                                    $body_template='', $body_data = array(), $body_general=null,
+                                    $image_1=null, $image_1_link=null,
+                                    $image_2=null, $image_2_link=null,
+                                    $image_3=null, $image_3_link=null,
+                                    $image_4=null, $image_4_link=null) {
+    return array('title_template'=> $title_template,
+                 'title_data'   => $title_data,
+                 'body_template'=> $body_template,
+                 'body_data'    => $body_data,
+                 'body_general' => $body_general,
+                 'image_1'      => $image_1,
+                 'image_1_link' => $image_1_link,
+                 'image_2'      => $image_2,
+                 'image_2_link' => $image_2_link,
+                 'image_3'      => $image_3,
+                 'image_3_link' => $image_3_link,
+                 'image_4'      => $image_4,
+                 'image_4_link' => $image_4_link);
+  }
+
+
+}
+
diff --git a/plugins/Facebook/facebook/facebook_desktop.php b/plugins/Facebook/facebook/facebook_desktop.php
new file mode 100644 (file)
index 0000000..e79a2ca
--- /dev/null
@@ -0,0 +1,104 @@
+<?php
+// Copyright 2004-2009 Facebook. All Rights Reserved.
+//
+// +---------------------------------------------------------------------------+
+// | Facebook Platform PHP5 client                                             |
+// +---------------------------------------------------------------------------+
+// | Copyright (c) 2007 Facebook, Inc.                                         |
+// | All rights reserved.                                                      |
+// |                                                                           |
+// | Redistribution and use in source and binary forms, with or without        |
+// | modification, are permitted provided that the following conditions        |
+// | are met:                                                                  |
+// |                                                                           |
+// | 1. Redistributions of source code must retain the above copyright         |
+// |    notice, this list of conditions and the following disclaimer.          |
+// | 2. Redistributions in binary form must reproduce the above copyright      |
+// |    notice, this list of conditions and the following disclaimer in the    |
+// |    documentation and/or other materials provided with the distribution.   |
+// |                                                                           |
+// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
+// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
+// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
+// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
+// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
+// +---------------------------------------------------------------------------+
+// | For help with this library, contact developers-help@facebook.com          |
+// +---------------------------------------------------------------------------+
+//
+
+/**
+ *  This class extends and modifies the "Facebook" class to better
+ *  suit desktop apps.
+ */
+class FacebookDesktop extends Facebook {
+  // the application secret, which differs from the session secret
+  public $app_secret;
+  public $verify_sig;
+
+  public function __construct($api_key, $secret) {
+    $this->app_secret = $secret;
+    $this->verify_sig = false;
+    parent::__construct($api_key, $secret);
+  }
+
+  public function do_get_session($auth_token) {
+    $this->api_client->secret = $this->app_secret;
+    $this->api_client->session_key = null;
+    $session_info = parent::do_get_session($auth_token);
+    if (!empty($session_info['secret'])) {
+      // store the session secret
+      $this->set_session_secret($session_info['secret']);
+    }
+    return $session_info;
+  }
+
+  public function set_session_secret($session_secret) {
+    $this->secret = $session_secret;
+    $this->api_client->secret = $session_secret;
+  }
+
+  public function require_login() {
+    if ($this->get_loggedin_user()) {
+      try {
+        // try a session-based API call to ensure that we have the correct
+        // session secret
+        $user = $this->api_client->users_getLoggedInUser();
+
+        // now that we have a valid session secret, verify the signature
+        $this->verify_sig = true;
+        if ($this->validate_fb_params(false)) {
+          return $user;
+        } else {
+          // validation failed
+          return null;
+        }
+      } catch (FacebookRestClientException $ex) {
+        if (isset($_GET['auth_token'])) {
+          // if we have an auth_token, use it to establish a session
+          $session_info = $this->do_get_session($_GET['auth_token']);
+          if ($session_info) {
+            return $session_info['uid'];
+          }
+        }
+      }
+    }
+    // if we get here, we need to redirect the user to log in
+    $this->redirect($this->get_login_url(self::current_url(), $this->in_fb_canvas()));
+  }
+
+  public function verify_signature($fb_params, $expected_sig) {
+    // we don't want to verify the signature until we have a valid
+    // session secret
+    if ($this->verify_sig) {
+      return parent::verify_signature($fb_params, $expected_sig);
+    } else {
+      return true;
+    }
+  }
+}
diff --git a/plugins/Facebook/facebook/facebookapi_php5_restlib.php b/plugins/Facebook/facebook/facebookapi_php5_restlib.php
new file mode 100755 (executable)
index 0000000..55cb7fb
--- /dev/null
@@ -0,0 +1,3618 @@
+<?php
+// Copyright 2004-2009 Facebook. All Rights Reserved.
+//
+// +---------------------------------------------------------------------------+
+// | Facebook Platform PHP5 client                                             |
+// +---------------------------------------------------------------------------+
+// | Copyright (c) 2007-2009 Facebook, Inc.                                    |
+// | All rights reserved.                                                      |
+// |                                                                           |
+// | Redistribution and use in source and binary forms, with or without        |
+// | modification, are permitted provided that the following conditions        |
+// | are met:                                                                  |
+// |                                                                           |
+// | 1. Redistributions of source code must retain the above copyright         |
+// |    notice, this list of conditions and the following disclaimer.          |
+// | 2. Redistributions in binary form must reproduce the above copyright      |
+// |    notice, this list of conditions and the following disclaimer in the    |
+// |    documentation and/or other materials provided with the distribution.   |
+// |                                                                           |
+// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR      |
+// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
+// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.   |
+// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,          |
+// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT  |
+// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
+// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY     |
+// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT       |
+// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF  |
+// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         |
+// +---------------------------------------------------------------------------+
+// | For help with this library, contact developers-help@facebook.com          |
+// +---------------------------------------------------------------------------+
+//
+
+include_once 'jsonwrapper/jsonwrapper.php';
+
+class FacebookRestClient {
+  public $secret;
+  public $session_key;
+  public $api_key;
+  // to save making the friends.get api call, this will get prepopulated on
+  // canvas pages
+  public $friends_list;
+  public $user;
+  // to save making the pages.isAppAdded api call, this will get prepopulated
+  // on canvas pages
+  public $added;
+  public $is_user;
+  // we don't pass friends list to iframes, but we want to make
+  // friends_get really simple in the canvas_user (non-logged in) case.
+  // So we use the canvas_user as default arg to friends_get
+  public $canvas_user;
+  public $batch_mode;
+  private $batch_queue;
+  private $pending_batch;
+  private $call_as_apikey;
+  private $use_curl_if_available;
+  private $format = null;
+
+  const BATCH_MODE_DEFAULT = 0;
+  const BATCH_MODE_SERVER_PARALLEL = 0;
+  const BATCH_MODE_SERIAL_ONLY = 2;
+
+  /**
+   * Create the client.
+   * @param string $session_key if you haven't gotten a session key yet, leave
+   *                            this as null and then set it later by just
+   *                            directly accessing the $session_key member
+   *                            variable.
+   */
+  public function __construct($api_key, $secret, $session_key=null) {
+    $this->secret       = $secret;
+    $this->session_key  = $session_key;
+    $this->api_key      = $api_key;
+    $this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT;
+    $this->last_call_id = 0;
+    $this->call_as_apikey = '';
+    $this->use_curl_if_available = true;
+    $this->server_addr  = Facebook::get_facebook_url('api') . '/restserver.php';
+
+    if (!empty($GLOBALS['facebook_config']['debug'])) {
+      $this->cur_id = 0;
+      ?>
+<script type="text/javascript">
+var types = ['params', 'xml', 'php', 'sxml'];
+function getStyle(elem, style) {
+  if (elem.getStyle) {
+    return elem.getStyle(style);
+  } else {
+    return elem.style[style];
+  }
+}
+function setStyle(elem, style, value) {
+  if (elem.setStyle) {
+    elem.setStyle(style, value);
+  } else {
+    elem.style[style] = value;
+  }
+}
+function toggleDisplay(id, type) {
+  for (var i = 0; i < types.length; i++) {
+    var t = types[i];
+    var pre = document.getElementById(t + id);
+    if (pre) {
+      if (t != type || getStyle(pre, 'display') == 'block') {
+        setStyle(pre, 'display', 'none');
+      } else {
+        setStyle(pre, 'display', 'block');
+      }
+    }
+  }
+  return false;
+}
+</script>
+<?php
+    }
+  }
+
+  /**
+   * Set the default user id for methods that allow the caller
+   * to pass an uid parameter to identify the target user
+   * instead of a session key. This currently applies to
+   * the user preferences methods.
+   *
+   * @param $uid int the user id
+   */
+  public function set_user($uid) {
+    $this->user = $uid;
+  }
+
+  /**
+   * Normally, if the cURL library/PHP extension is available, it is used for
+   * HTTP transactions.  This allows that behavior to be overridden, falling
+   * back to a vanilla-PHP implementation even if cURL is installed.
+   *
+   * @param $use_curl_if_available bool whether or not to use cURL if available
+   */
+  public function set_use_curl_if_available($use_curl_if_available) {
+    $this->use_curl_if_available = $use_curl_if_available;
+  }
+
+  /**
+   * Start a batch operation.
+   */
+  public function begin_batch() {
+    if ($this->pending_batch()) {
+      $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED;
+      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+      throw new FacebookRestClientException($description, $code);
+    }
+
+    $this->batch_queue = array();
+    $this->pending_batch = true;
+  }
+
+  /*
+   * End current batch operation
+   */
+  public function end_batch() {
+    if (!$this->pending_batch()) {
+      $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED;
+      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+      throw new FacebookRestClientException($description, $code);
+    }
+
+    $this->pending_batch = false;
+
+    $this->execute_server_side_batch();
+    $this->batch_queue = null;
+  }
+
+  /**
+   * are we currently queueing up calls for a batch?
+   */
+  public function pending_batch() {
+    return $this->pending_batch;
+  }
+
+  private function execute_server_side_batch() {
+    $item_count = count($this->batch_queue);
+    $method_feed = array();
+    foreach ($this->batch_queue as $batch_item) {
+      $method = $batch_item['m'];
+      $params = $batch_item['p'];
+      list($get, $post) = $this->finalize_params($method, $params);
+      $method_feed[] = $this->create_url_string(array_merge($post, $get));
+    }
+
+    $serial_only =
+      ($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
+
+    $params = array('method_feed' => json_encode($method_feed),
+                    'serial_only' => $serial_only,
+                    'format' => $this->format);
+    $result = $this->call_method('facebook.batch.run', $params);
+
+    if (is_array($result) && isset($result['error_code'])) {
+      throw new FacebookRestClientException($result['error_msg'],
+                                            $result['error_code']);
+    }
+
+    for ($i = 0; $i < $item_count; $i++) {
+      $batch_item = $this->batch_queue[$i];
+      $batch_item['p']['format'] = $this->format;
+      $batch_item_result = $this->convert_result($result[$i],
+                                                 $batch_item['m'],
+                                                 $batch_item['p']);
+
+      if (is_array($batch_item_result) &&
+          isset($batch_item_result['error_code'])) {
+        throw new FacebookRestClientException($batch_item_result['error_msg'],
+                                              $batch_item_result['error_code']);
+      }
+      $batch_item['r'] = $batch_item_result;
+    }
+  }
+
+  public function begin_permissions_mode($permissions_apikey) {
+    $this->call_as_apikey = $permissions_apikey;
+  }
+
+  public function end_permissions_mode() {
+    $this->call_as_apikey = '';
+  }
+
+
+  /*
+   * If a page is loaded via HTTPS, then all images and static
+   * resources need to be printed with HTTPS urls to avoid
+   * mixed content warnings. If your page loads with an HTTPS
+   * url, then call set_use_ssl_resources to retrieve the correct
+   * urls.
+   */
+  public function set_use_ssl_resources($is_ssl = true) {
+    $this->use_ssl_resources = $is_ssl;
+  }
+
+  /**
+   * Returns public information for an application (as shown in the application
+   * directory) by either application ID, API key, or canvas page name.
+   *
+   * @param int $application_id              (Optional) app id
+   * @param string $application_api_key      (Optional) api key
+   * @param string $application_canvas_name  (Optional) canvas name
+   *
+   * Exactly one argument must be specified, otherwise it is an error.
+   *
+   * @return array  An array of public information about the application.
+   */
+  public function application_getPublicInfo($application_id=null,
+                                            $application_api_key=null,
+                                            $application_canvas_name=null) {
+    return $this->call_method('facebook.application.getPublicInfo',
+        array('application_id' => $application_id,
+              'application_api_key' => $application_api_key,
+              'application_canvas_name' => $application_canvas_name));
+  }
+
+  /**
+   * Creates an authentication token to be used as part of the desktop login
+   * flow.  For more information, please see
+   * http://wiki.developers.facebook.com/index.php/Auth.createToken.
+   *
+   * @return string  An authentication token.
+   */
+  public function auth_createToken() {
+    return $this->call_method('facebook.auth.createToken');
+  }
+
+  /**
+   * Returns the session information available after current user logs in.
+   *
+   * @param string $auth_token             the token returned by
+   *                                       auth_createToken or passed back to
+   *                                       your callback_url.
+   * @param bool $generate_session_secret  whether the session returned should
+   *                                       include a session secret
+   *
+   * @return array  An assoc array containing session_key, uid
+   */
+  public function auth_getSession($auth_token, $generate_session_secret=false) {
+    if (!$this->pending_batch()) {
+      $result = $this->call_method('facebook.auth.getSession',
+          array('auth_token' => $auth_token,
+                'generate_session_secret' => $generate_session_secret));
+      $this->session_key = $result['session_key'];
+
+    if (!empty($result['secret']) && !$generate_session_secret) {
+      // desktop apps have a special secret
+      $this->secret = $result['secret'];
+    }
+      return $result;
+    }
+  }
+
+  /**
+   * Generates a session-specific secret. This is for integration with
+   * client-side API calls, such as the JS library.
+   *
+   * @return array  A session secret for the current promoted session
+   *
+   * @error API_EC_PARAM_SESSION_KEY
+   *        API_EC_PARAM_UNKNOWN
+   */
+  public function auth_promoteSession() {
+      return $this->call_method('facebook.auth.promoteSession');
+  }
+
+  /**
+   * Expires the session that is currently being used.  If this call is
+   * successful, no further calls to the API (which require a session) can be
+   * made until a valid session is created.
+   *
+   * @return bool  true if session expiration was successful, false otherwise
+   */
+  public function auth_expireSession() {
+      return $this->call_method('facebook.auth.expireSession');
+  }
+
+  /**
+   *  Revokes the given extended permission that the user granted at some
+   *  prior time (for instance, offline_access or email).  If no user is
+   *  provided, it will be revoked for the user of the current session.
+   *
+   *  @param  string  $perm  The permission to revoke
+   *  @param  int     $uid   The user for whom to revoke the permission.
+   */
+  public function auth_revokeExtendedPermission($perm, $uid=null) {
+    return $this->call_method('facebook.auth.revokeExtendedPermission',
+        array('perm' => $perm, 'uid' => $uid));
+  }
+
+  /**
+   * Revokes the user's agreement to the Facebook Terms of Service for your
+   * application.  If you call this method for one of your users, you will no
+   * longer be able to make API requests on their behalf until they again
+   * authorize your application.  Use with care.  Note that if this method is
+   * called without a user parameter, then it will revoke access for the
+   * current session's user.
+   *
+   * @param int $uid  (Optional) User to revoke
+   *
+   * @return bool  true if revocation succeeds, false otherwise
+   */
+  public function auth_revokeAuthorization($uid=null) {
+      return $this->call_method('facebook.auth.revokeAuthorization',
+          array('uid' => $uid));
+  }
+
+  /**
+   * Get public key that is needed to verify digital signature
+   * an app may pass to other apps. The public key is only used by
+   * other apps for verification purposes.
+   * @param  string  API key of an app
+   * @return string  The public key for the app.
+   */
+  public function auth_getAppPublicKey($target_app_key) {
+    return $this->call_method('facebook.auth.getAppPublicKey',
+          array('target_app_key' => $target_app_key));
+  }
+
+  /**
+   * Get a structure that can be passed to another app
+   * as proof of session. The other app can verify it using public
+   * key of this app.
+   *
+   * @return signed public session data structure.
+   */
+  public function auth_getSignedPublicSessionData() {
+    return $this->call_method('facebook.auth.getSignedPublicSessionData',
+                              array());
+  }
+
+  /**
+   * Returns the number of unconnected friends that exist in this application.
+   * This number is determined based on the accounts registered through
+   * connect.registerUsers() (see below).
+   */
+  public function connect_getUnconnectedFriendsCount() {
+    return $this->call_method('facebook.connect.getUnconnectedFriendsCount',
+        array());
+  }
+
+ /**
+  * This method is used to create an association between an external user
+  * account and a Facebook user account, as per Facebook Connect.
+  *
+  * This method takes an array of account data, including a required email_hash
+  * and optional account data. For each connected account, if the user exists,
+  * the information is added to the set of the user's connected accounts.
+  * If the user has already authorized the site, the connected account is added
+  * in the confirmed state. If the user has not yet authorized the site, the
+  * connected account is added in the pending state.
+  *
+  * This is designed to help Facebook Connect recognize when two Facebook
+  * friends are both members of a external site, but perhaps are not aware of
+  * it.  The Connect dialog (see fb:connect-form) is used when friends can be
+  * identified through these email hashes. See the following url for details:
+  *
+  *   http://wiki.developers.facebook.com/index.php/Connect.registerUsers
+  *
+  * @param mixed $accounts A (JSON-encoded) array of arrays, where each array
+  *                        has three properties:
+  *                        'email_hash'  (req) - public email hash of account
+  *                        'account_id'  (opt) - remote account id;
+  *                        'account_url' (opt) - url to remote account;
+  *
+  * @return array  The list of email hashes for the successfully registered
+  *                accounts.
+  */
+  public function connect_registerUsers($accounts) {
+    return $this->call_method('facebook.connect.registerUsers',
+        array('accounts' => $accounts));
+  }
+
+ /**
+  * Unregisters a set of accounts registered using connect.registerUsers.
+  *
+  * @param array $email_hashes  The (JSON-encoded) list of email hashes to be
+  *                             unregistered.
+  *
+  * @return array  The list of email hashes which have been successfully
+  *                unregistered.
+  */
+  public function connect_unregisterUsers($email_hashes) {
+    return $this->call_method('facebook.connect.unregisterUsers',
+        array('email_hashes' => $email_hashes));
+  }
+
+  /**
+   * Returns events according to the filters specified.
+   *
+   * @param int $uid            (Optional) User associated with events. A null
+   *                            parameter will default to the session user.
+   * @param array/string $eids  (Optional) Filter by these event
+   *                            ids. A null parameter will get all events for
+   *                            the user. (A csv list will work but is deprecated)
+   * @param int $start_time     (Optional) Filter with this unix time as lower
+   *                            bound.  A null or zero parameter indicates no
+   *                            lower bound.
+   * @param int $end_time       (Optional) Filter with this UTC as upper bound.
+   *                            A null or zero parameter indicates no upper
+   *                            bound.
+   * @param string $rsvp_status (Optional) Only show events where the given uid
+   *                            has this rsvp status.  This only works if you
+   *                            have specified a value for $uid.  Values are as
+   *                            in events.getMembers.  Null indicates to ignore
+   *                            rsvp status when filtering.
+   *
+   * @return array  The events matching the query.
+   */
+  public function &events_get($uid=null,
+                              $eids=null,
+                              $start_time=null,
+                              $end_time=null,
+                              $rsvp_status=null) {
+    return $this->call_method('facebook.events.get',
+        array('uid' => $uid,
+              'eids' => $eids,
+              'start_time' => $start_time,
+              'end_time' => $end_time,
+              'rsvp_status' => $rsvp_status));
+  }
+
+  /**
+   * Returns membership list data associated with an event.
+   *
+   * @param int $eid  event id
+   *
+   * @return array  An assoc array of four membership lists, with keys
+   *                'attending', 'unsure', 'declined', and 'not_replied'
+   */
+  public function &events_getMembers($eid) {
+    return $this->call_method('facebook.events.getMembers',
+      array('eid' => $eid));
+  }
+
+  /**
+   * RSVPs the current user to this event.
+   *
+   * @param int $eid             event id
+   * @param string $rsvp_status  'attending', 'unsure', or 'declined'
+   *
+   * @return bool  true if successful
+   */
+  public function &events_rsvp($eid, $rsvp_status) {
+    return $this->call_method('facebook.events.rsvp',
+        array(
+        'eid' => $eid,
+        'rsvp_status' => $rsvp_status));
+  }
+
+  /**
+   * Cancels an event. Only works for events where application is the admin.
+   *
+   * @param int $eid                event id
+   * @param string $cancel_message  (Optional) message to send to members of
+   *                                the event about why it is cancelled
+   *
+   * @return bool  true if successful
+   */
+  public function &events_cancel($eid, $cancel_message='') {
+    return $this->call_method('facebook.events.cancel',
+        array('eid' => $eid,
+              'cancel_message' => $cancel_message));
+  }
+
+  /**
+   * Creates an event on behalf of the user is there is a session, otherwise on
+   * behalf of app.  Successful creation guarantees app will be admin.
+   *
+   * @param assoc array $event_info  json encoded event information
+   * @param string $file             (Optional) filename of picture to set
+   *
+   * @return int  event id
+   */
+  public function events_create($event_info, $file = null) {
+    if ($file) {
+      return $this->call_upload_method('facebook.events.create',
+        array('event_info' => $event_info),
+        $file,
+        Facebook::get_facebook_url('api-photo') . '/restserver.php');
+    } else {
+      return $this->call_method('facebook.events.create',
+        array('event_info' => $event_info));
+    }
+  }
+
+  /**
+   * Edits an existing event. Only works for events where application is admin.
+   *
+   * @param int $eid                 event id
+   * @param assoc array $event_info  json encoded event information
+   * @param string $file             (Optional) filename of new picture to set
+   *
+   * @return bool  true if successful
+   */
+  public function events_edit($eid, $event_info, $file = null) {
+    if ($file) {
+      return $this->call_upload_method('facebook.events.edit',
+        array('eid' => $eid, 'event_info' => $event_info),
+        $file,
+        Facebook::get_facebook_url('api-photo') . '/restserver.php');
+    } else {
+      return $this->call_method('facebook.events.edit',
+        array('eid' => $eid,
+        'event_info' => $event_info));
+    }
+  }
+
+  /**
+   * Fetches and re-caches the image stored at the given URL, for use in images
+   * published to non-canvas pages via the API (for example, to user profiles
+   * via profile.setFBML, or to News Feed via feed.publishUserAction).
+   *
+   * @param string $url  The absolute URL from which to refresh the image.
+   *
+   * @return bool  true on success
+   */
+  public function &fbml_refreshImgSrc($url) {
+    return $this->call_method('facebook.fbml.refreshImgSrc',
+        array('url' => $url));
+  }
+
+  /**
+   * Fetches and re-caches the content stored at the given URL, for use in an
+   * fb:ref FBML tag.
+   *
+   * @param string $url  The absolute URL from which to fetch content. This URL
+   *                     should be used in a fb:ref FBML tag.
+   *
+   * @return bool  true on success
+   */
+  public function &fbml_refreshRefUrl($url) {
+    return $this->call_method('facebook.fbml.refreshRefUrl',
+        array('url' => $url));
+  }
+
+  /**
+   * Lets you insert text strings in their native language into the Facebook
+   * Translations database so they can be translated.
+   *
+   * @param array $native_strings  An array of maps, where each map has a 'text'
+   *                               field and a 'description' field.
+   *
+   * @return int  Number of strings uploaded.
+   */
+  public function &fbml_uploadNativeStrings($native_strings) {
+    return $this->call_method('facebook.fbml.uploadNativeStrings',
+        array('native_strings' => json_encode($native_strings)));
+  }
+
+  /**
+   * Associates a given "handle" with FBML markup so that the handle can be
+   * used within the fb:ref FBML tag. A handle is unique within an application
+   * and allows an application to publish identical FBML to many user profiles
+   * and do subsequent updates without having to republish FBML on behalf of
+   * each user.
+   *
+   * @param string $handle  The handle to associate with the given FBML.
+   * @param string $fbml    The FBML to associate with the given handle.
+   *
+   * @return bool  true on success
+   */
+  public function &fbml_setRefHandle($handle, $fbml) {
+    return $this->call_method('facebook.fbml.setRefHandle',
+        array('handle' => $handle, 'fbml' => $fbml));
+  }
+
+  /**
+   * Register custom tags for the application. Custom tags can be used
+   * to extend the set of tags available to applications in FBML
+   * markup.
+   *
+   * Before you call this function,
+   * make sure you read the full documentation at
+   *
+   * http://wiki.developers.facebook.com/index.php/Fbml.RegisterCustomTags
+   *
+   * IMPORTANT: This function overwrites the values of
+   * existing tags if the names match. Use this function with care because
+   * it may break the FBML of any application that is using the
+   * existing version of the tags.
+   *
+   * @param mixed $tags an array of tag objects (the full description is on the
+   *   wiki page)
+   *
+   * @return int  the number of tags that were registered
+   */
+  public function &fbml_registerCustomTags($tags) {
+    $tags = json_encode($tags);
+    return $this->call_method('facebook.fbml.registerCustomTags',
+                              array('tags' => $tags));
+  }
+
+  /**
+   * Get the custom tags for an application. If $app_id
+   * is not specified, the calling app's tags are returned.
+   * If $app_id is different from the id of the calling app,
+   * only the app's public tags are returned.
+   * The return value is an array of the same type as
+   * the $tags parameter of fbml_registerCustomTags().
+   *
+   * @param int $app_id the application's id (optional)
+   *
+   * @return mixed  an array containing the custom tag  objects
+   */
+  public function &fbml_getCustomTags($app_id = null) {
+    return $this->call_method('facebook.fbml.getCustomTags',
+                              array('app_id' => $app_id));
+  }
+
+
+  /**
+   * Delete custom tags the application has registered. If
+   * $tag_names is null, all the application's custom tags will be
+   * deleted.
+   *
+   * IMPORTANT: If your application has registered public tags
+   * that other applications may be using, don't delete those tags!
+   * Doing so can break the FBML ofapplications that are using them.
+   *
+   * @param array $tag_names the names of the tags to delete (optinal)
+   * @return bool true on success
+   */
+  public function &fbml_deleteCustomTags($tag_names = null) {
+    return $this->call_method('facebook.fbml.deleteCustomTags',
+                              array('tag_names' => json_encode($tag_names)));
+  }
+
+
+
+  /**
+   * This method is deprecated for calls made on behalf of users. This method
+   * works only for publishing stories on a Facebook Page that has installed
+   * your application. To publish stories to a user's profile, use
+   * feed.publishUserAction instead.
+   *
+   * For more details on this call, please visit the wiki page:
+   *
+   * http://wiki.developers.facebook.com/index.php/Feed.publishTemplatizedAction
+   */
+  public function &feed_publishTemplatizedAction($title_template,
+                                                 $title_data,
+                                                 $body_template,
+                                                 $body_data,
+                                                 $body_general,
+                                                 $image_1=null,
+                                                 $image_1_link=null,
+                                                 $image_2=null,
+                                                 $image_2_link=null,
+                                                 $image_3=null,
+                                                 $image_3_link=null,
+                                                 $image_4=null,
+                                                 $image_4_link=null,
+                                                 $target_ids='',
+                                                 $page_actor_id=null) {
+    return $this->call_method('facebook.feed.publishTemplatizedAction',
+      array('title_template' => $title_template,
+            'title_data' => $title_data,
+            'body_template' => $body_template,
+            'body_data' => $body_data,
+            'body_general' => $body_general,
+            'image_1' => $image_1,
+            'image_1_link' => $image_1_link,
+            'image_2' => $image_2,
+            'image_2_link' => $image_2_link,
+            'image_3' => $image_3,
+            'image_3_link' => $image_3_link,
+            'image_4' => $image_4,
+            'image_4_link' => $image_4_link,
+            'target_ids' => $target_ids,
+            'page_actor_id' => $page_actor_id));
+  }
+
+  /**
+   * Registers a template bundle.  Template bundles are somewhat involved, so
+   * it's recommended you check out the wiki for more details:
+   *
+   *  http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle
+   *
+   * @return string  A template bundle id
+   */
+  public function &feed_registerTemplateBundle($one_line_story_templates,
+                                               $short_story_templates = array(),
+                                               $full_story_template = null,
+                                               $action_links = array()) {
+
+    $one_line_story_templates = json_encode($one_line_story_templates);
+
+    if (!empty($short_story_templates)) {
+      $short_story_templates = json_encode($short_story_templates);
+    }
+
+    if (isset($full_story_template)) {
+      $full_story_template = json_encode($full_story_template);
+    }
+
+    if (isset($action_links)) {
+      $action_links = json_encode($action_links);
+    }
+
+    return $this->call_method('facebook.feed.registerTemplateBundle',
+        array('one_line_story_templates' => $one_line_story_templates,
+              'short_story_templates' => $short_story_templates,
+              'full_story_template' => $full_story_template,
+              'action_links' => $action_links));
+  }
+
+  /**
+   * Retrieves the full list of active template bundles registered by the
+   * requesting application.
+   *
+   * @return array  An array of template bundles
+   */
+  public function &feed_getRegisteredTemplateBundles() {
+    return $this->call_method('facebook.feed.getRegisteredTemplateBundles',
+        array());
+  }
+
+  /**
+   * Retrieves information about a specified template bundle previously
+   * registered by the requesting application.
+   *
+   * @param string $template_bundle_id  The template bundle id
+   *
+   * @return array  Template bundle
+   */
+  public function &feed_getRegisteredTemplateBundleByID($template_bundle_id) {
+    return $this->call_method('facebook.feed.getRegisteredTemplateBundleByID',
+        array('template_bundle_id' => $template_bundle_id));
+  }
+
+  /**
+   * Deactivates a previously registered template bundle.
+   *
+   * @param string $template_bundle_id  The template bundle id
+   *
+   * @return bool  true on success
+   */
+  public function &feed_deactivateTemplateBundleByID($template_bundle_id) {
+    return $this->call_method('facebook.feed.deactivateTemplateBundleByID',
+        array('template_bundle_id' => $template_bundle_id));
+  }
+
+  const STORY_SIZE_ONE_LINE = 1;
+  const STORY_SIZE_SHORT = 2;
+  const STORY_SIZE_FULL = 4;
+
+  /**
+   * Publishes a story on behalf of the user owning the session, using the
+   * specified template bundle. This method requires an active session key in
+   * order to be called.
+   *
+   * The parameters to this method ($templata_data in particular) are somewhat
+   * involved.  It's recommended you visit the wiki for details:
+   *
+   *  http://wiki.developers.facebook.com/index.php/Feed.publishUserAction
+   *
+   * @param int $template_bundle_id  A template bundle id previously registered
+   * @param array $template_data     See wiki article for syntax
+   * @param array $target_ids        (Optional) An array of friend uids of the
+   *                                 user who shared in this action.
+   * @param string $body_general     (Optional) Additional markup that extends
+   *                                 the body of a short story.
+   * @param int $story_size          (Optional) A story size (see above)
+   * @param string $user_message     (Optional) A user message for a short
+   *                                 story.
+   *
+   * @return bool  true on success
+   */
+  public function &feed_publishUserAction(
+      $template_bundle_id, $template_data, $target_ids='', $body_general='',
+      $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE,
+      $user_message='') {
+
+    if (is_array($template_data)) {
+      $template_data = json_encode($template_data);
+    } // allow client to either pass in JSON or an assoc that we JSON for them
+
+    if (is_array($target_ids)) {
+      $target_ids = json_encode($target_ids);
+      $target_ids = trim($target_ids, "[]"); // we don't want square brackets
+    }
+
+    return $this->call_method('facebook.feed.publishUserAction',
+        array('template_bundle_id' => $template_bundle_id,
+              'template_data' => $template_data,
+              'target_ids' => $target_ids,
+              'body_general' => $body_general,
+              'story_size' => $story_size,
+              'user_message' => $user_message));
+  }
+
+
+  /**
+   * Publish a post to the user's stream.
+   *
+   * @param $message        the user's message
+   * @param $attachment     the post's attachment (optional)
+   * @param $action links   the post's action links (optional)
+   * @param $target_id      the user on whose wall the post will be posted
+   *                        (optional)
+   * @param $uid            the actor (defaults to session user)
+   * @return string the post id
+   */
+  public function stream_publish(
+    $message, $attachment = null, $action_links = null, $target_id = null,
+    $uid = null) {
+
+    return $this->call_method(
+      'facebook.stream.publish',
+      array('message' => $message,
+            'attachment' => $attachment,
+            'action_links' => $action_links,
+            'target_id' => $target_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Remove a post from the user's stream.
+   * Currently, you may only remove stories you application created.
+   *
+   * @param $post_id  the post id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_remove($post_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.remove',
+      array('post_id' => $post_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Add a comment to a stream post
+   *
+   * @param $post_id  the post id
+   * @param $comment  the comment text
+   * @param $uid      the actor (defaults to session user)
+   * @return string the id of the created comment
+   */
+  public function stream_addComment($post_id, $comment, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.addComment',
+      array('post_id' => $post_id,
+            'comment' => $comment,
+            'uid' => $this->get_uid($uid)));
+  }
+
+
+  /**
+   * Remove a comment from a stream post
+   *
+   * @param $comment_id  the comment id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_removeComment($comment_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.removeComment',
+      array('comment_id' => $comment_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Add a like to a stream post
+   *
+   * @param $post_id  the post id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_addLike($post_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.addLike',
+      array('post_id' => $post_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Remove a like from a stream post
+   *
+   * @param $post_id  the post id
+   * @param $uid      the actor (defaults to session user)
+   * @return bool
+   */
+  public function stream_removeLike($post_id, $uid = null) {
+    return $this->call_method(
+      'facebook.stream.removeLike',
+      array('post_id' => $post_id,
+            'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * For the current user, retrieves stories generated by the user's friends
+   * while using this application.  This can be used to easily create a
+   * "News Feed" like experience.
+   *
+   * @return array  An array of feed story objects.
+   */
+  public function &feed_getAppFriendStories() {
+    return $this->call_method('facebook.feed.getAppFriendStories');
+  }
+
+  /**
+   * Makes an FQL query.  This is a generalized way of accessing all the data
+   * in the API, as an alternative to most of the other method calls.  More
+   * info at http://wiki.developers.facebook.com/index.php/FQL
+   *
+   * @param string $query  the query to evaluate
+   *
+   * @return array  generalized array representing the results
+   */
+  public function &fql_query($query) {
+    return $this->call_method('facebook.fql.query',
+      array('query' => $query));
+  }
+
+  /**
+   * Makes a set of FQL queries in parallel.  This method takes a dictionary
+   * of FQL queries where the keys are names for the queries.  Results from
+   * one query can be used within another query to fetch additional data.  More
+   * info about FQL queries at http://wiki.developers.facebook.com/index.php/FQL
+   *
+   * @param string $queries  JSON-encoded dictionary of queries to evaluate
+   *
+   * @return array  generalized array representing the results
+   */
+  public function &fql_multiquery($queries) {
+    return $this->call_method('facebook.fql.multiquery',
+      array('queries' => $queries));
+  }
+
+  /**
+   * Returns whether or not pairs of users are friends.
+   * Note that the Facebook friend relationship is symmetric.
+   *
+   * @param array/string $uids1  list of ids (id_1, id_2,...)
+   *                       of some length X (csv is deprecated)
+   * @param array/string $uids2  list of ids (id_A, id_B,...)
+   *                       of SAME length X (csv is deprecated)
+   *
+   * @return array  An array with uid1, uid2, and bool if friends, e.g.:
+   *   array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1),
+   *         1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0)
+   *         ...)
+   * @error
+   *    API_EC_PARAM_USER_ID_LIST
+   */
+  public function &friends_areFriends($uids1, $uids2) {
+    return $this->call_method('facebook.friends.areFriends',
+                 array('uids1' => $uids1,
+                       'uids2' => $uids2));
+  }
+
+  /**
+   * Returns the friends of the current session user.
+   *
+   * @param int $flid  (Optional) Only return friends on this friend list.
+   * @param int $uid   (Optional) Return friends for this user.
+   *
+   * @return array  An array of friends
+   */
+  public function &friends_get($flid=null, $uid = null) {
+    if (isset($this->friends_list)) {
+      return $this->friends_list;
+    }
+    $params = array();
+    if (!$uid && isset($this->canvas_user)) {
+      $uid = $this->canvas_user;
+    }
+    if ($uid) {
+      $params['uid'] = $uid;
+    }
+    if ($flid) {
+      $params['flid'] = $flid;
+    }
+    return $this->call_method('facebook.friends.get', $params);
+
+  }
+
+  /**
+   * Returns the mutual friends between the target uid and a source uid or
+   * the current session user.
+   *
+   * @param int $target_uid Target uid for which mutual friends will be found.
+   * @param int $source_uid (optional) Source uid for which mutual friends will
+   *                                   be found. If no source_uid is specified,
+   *                                   source_id will default to the session
+   *                                   user.
+   * @return array  An array of friend uids
+   */
+  public function &friends_getMutualFriends($target_uid, $source_uid = null) {
+    return $this->call_method('facebook.friends.getMutualFriends',
+                              array("target_uid" => $target_uid,
+                                    "source_uid" => $source_uid));
+  }
+
+  /**
+   * Returns the set of friend lists for the current session user.
+   *
+   * @return array  An array of friend list objects
+   */
+  public function &friends_getLists() {
+    return $this->call_method('facebook.friends.getLists');
+  }
+
+  /**
+   * Returns the friends of the session user, who are also users
+   * of the calling application.
+   *
+   * @return array  An array of friends also using the app
+   */
+  public function &friends_getAppUsers() {
+    return $this->call_method('facebook.friends.getAppUsers');
+  }
+
+  /**
+   * Returns groups according to the filters specified.
+   *
+   * @param int $uid     (Optional) User associated with groups.  A null
+   *                     parameter will default to the session user.
+   * @param array/string $gids (Optional) Array of group ids to query. A null
+   *                     parameter will get all groups for the user.
+   *                     (csv is deprecated)
+   *
+   * @return array  An array of group objects
+   */
+  public function &groups_get($uid, $gids) {
+    return $this->call_method('facebook.groups.get',
+        array('uid' => $uid,
+              'gids' => $gids));
+  }
+
+  /**
+   * Returns the membership list of a group.
+   *
+   * @param int $gid  Group id
+   *
+   * @return array  An array with four membership lists, with keys 'members',
+   *                'admins', 'officers', and 'not_replied'
+   */
+  public function &groups_getMembers($gid) {
+    return $this->call_method('facebook.groups.getMembers',
+      array('gid' => $gid));
+  }
+
+  /**
+   * Returns cookies according to the filters specified.
+   *
+   * @param int $uid     User for which the cookies are needed.
+   * @param string $name (Optional) A null parameter will get all cookies
+   *                     for the user.
+   *
+   * @return array  Cookies!  Nom nom nom nom nom.
+   */
+  public function data_getCookies($uid, $name) {
+    return $this->call_method('facebook.data.getCookies',
+        array('uid' => $uid,
+              'name' => $name));
+  }
+
+  /**
+   * Sets cookies according to the params specified.
+   *
+   * @param int $uid       User for which the cookies are needed.
+   * @param string $name   Name of the cookie
+   * @param string $value  (Optional) if expires specified and is in the past
+   * @param int $expires   (Optional) Expiry time
+   * @param string $path   (Optional) Url path to associate with (default is /)
+   *
+   * @return bool  true on success
+   */
+  public function data_setCookie($uid, $name, $value, $expires, $path) {
+    return $this->call_method('facebook.data.setCookie',
+        array('uid' => $uid,
+              'name' => $name,
+              'value' => $value,
+              'expires' => $expires,
+              'path' => $path));
+  }
+
+  /**
+   * Retrieves links posted by the given user.
+   *
+   * @param int    $uid      The user whose links you wish to retrieve
+   * @param int    $limit    The maximimum number of links to retrieve
+   * @param array $link_ids (Optional) Array of specific link
+   *                          IDs to retrieve by this user
+   *
+   * @return array  An array of links.
+   */
+  public function &links_get($uid, $limit, $link_ids = null) {
+    return $this->call_method('links.get',
+        array('uid' => $uid,
+              'limit' => $limit,
+              'link_ids' => $link_ids));
+  }
+
+  /**
+   * Posts a link on Facebook.
+   *
+   * @param string $url     URL/link you wish to post
+   * @param string $comment (Optional) A comment about this link
+   * @param int    $uid     (Optional) User ID that is posting this link;
+   *                        defaults to current session user
+   *
+   * @return bool
+   */
+  public function &links_post($url, $comment='', $uid = null) {
+    return $this->call_method('links.post',
+        array('uid' => $uid,
+              'url' => $url,
+              'comment' => $comment));
+  }
+
+  /**
+   * Permissions API
+   */
+
+  /**
+   * Checks API-access granted by self to the specified application.
+   *
+   * @param string $permissions_apikey  Other application key
+   *
+   * @return array  API methods/namespaces which are allowed access
+   */
+  public function permissions_checkGrantedApiAccess($permissions_apikey) {
+    return $this->call_method('facebook.permissions.checkGrantedApiAccess',
+        array('permissions_apikey' => $permissions_apikey));
+  }
+
+  /**
+   * Checks API-access granted to self by the specified application.
+   *
+   * @param string $permissions_apikey  Other application key
+   *
+   * @return array  API methods/namespaces which are allowed access
+   */
+  public function permissions_checkAvailableApiAccess($permissions_apikey) {
+    return $this->call_method('facebook.permissions.checkAvailableApiAccess',
+        array('permissions_apikey' => $permissions_apikey));
+  }
+
+  /**
+   * Grant API-access to the specified methods/namespaces to the specified
+   * application.
+   *
+   * @param string $permissions_apikey  Other application key
+   * @param array(string) $method_arr   (Optional) API methods/namespaces
+   *                                    allowed
+   *
+   * @return array  API methods/namespaces which are allowed access
+   */
+  public function permissions_grantApiAccess($permissions_apikey, $method_arr) {
+    return $this->call_method('facebook.permissions.grantApiAccess',
+        array('permissions_apikey' => $permissions_apikey,
+              'method_arr' => $method_arr));
+  }
+
+  /**
+   * Revoke API-access granted to the specified application.
+   *
+   * @param string $permissions_apikey  Other application key
+   *
+   * @return bool  true on success
+   */
+  public function permissions_revokeApiAccess($permissions_apikey) {
+    return $this->call_method('facebook.permissions.revokeApiAccess',
+        array('permissions_apikey' => $permissions_apikey));
+  }
+
+  /**
+   * Payments Order API
+   */
+
+  /**
+   * Set Payments properties for an app.
+   *
+   * @param  properties  a map from property names to  values
+   * @return             true on success
+   */
+  public function payments_setProperties($properties) {
+    return $this->call_method ('facebook.payments.setProperties',
+        array('properties' => json_encode($properties)));
+  }
+
+  public function payments_getOrderDetails($order_id) {
+    return json_decode($this->call_method(
+        'facebook.payments.getOrderDetails',
+        array('order_id' => $order_id)), true);
+  }
+
+  public function payments_updateOrder($order_id, $status,
+                                         $params) {
+    return $this->call_method('facebook.payments.updateOrder',
+        array('order_id' => $order_id,
+              'status' => $status,
+              'params' => json_encode($params)));
+  }
+
+  public function payments_getOrders($status, $start_time,
+                                       $end_time, $test_mode=false) {
+    return json_decode($this->call_method('facebook.payments.getOrders',
+        array('status' => $status,
+              'start_time' => $start_time,
+              'end_time' => $end_time,
+              'test_mode' => $test_mode)), true);
+  }
+
+  /**
+   * Creates a note with the specified title and content.
+   *
+   * @param string $title   Title of the note.
+   * @param string $content Content of the note.
+   * @param int    $uid     (Optional) The user for whom you are creating a
+   *                        note; defaults to current session user
+   *
+   * @return int   The ID of the note that was just created.
+   */
+  public function &notes_create($title, $content, $uid = null) {
+    return $this->call_method('notes.create',
+        array('uid' => $uid,
+              'title' => $title,
+              'content' => $content));
+  }
+
+  /**
+   * Deletes the specified note.
+   *
+   * @param int $note_id  ID of the note you wish to delete
+   * @param int $uid      (Optional) Owner of the note you wish to delete;
+   *                      defaults to current session user
+   *
+   * @return bool
+   */
+  public function &notes_delete($note_id, $uid = null) {
+    return $this->call_method('notes.delete',
+        array('uid' => $uid,
+              'note_id' => $note_id));
+  }
+
+  /**
+   * Edits a note, replacing its title and contents with the title
+   * and contents specified.
+   *
+   * @param int    $note_id  ID of the note you wish to edit
+   * @param string $title    Replacement title for the note
+   * @param string $content  Replacement content for the note
+   * @param int    $uid      (Optional) Owner of the note you wish to edit;
+   *                         defaults to current session user
+   *
+   * @return bool
+   */
+  public function &notes_edit($note_id, $title, $content, $uid = null) {
+    return $this->call_method('notes.edit',
+        array('uid' => $uid,
+              'note_id' => $note_id,
+              'title' => $title,
+              'content' => $content));
+  }
+
+  /**
+   * Retrieves all notes by a user. If note_ids are specified,
+   * retrieves only those specific notes by that user.
+   *
+   * @param int    $uid      User whose notes you wish to retrieve
+   * @param array  $note_ids (Optional) List of specific note
+   *                         IDs by this user to retrieve
+   *
+   * @return array A list of all of the given user's notes, or an empty list
+   *               if the viewer lacks permissions or if there are no visible
+   *               notes.
+   */
+  public function &notes_get($uid, $note_ids = null) {
+    return $this->call_method('notes.get',
+        array('uid' => $uid,
+              'note_ids' => $note_ids));
+  }
+
+
+  /**
+   * Returns the outstanding notifications for the session user.
+   *
+   * @return array An assoc array of notification count objects for
+   *               'messages', 'pokes' and 'shares', a uid list of
+   *               'friend_requests', a gid list of 'group_invites',
+   *               and an eid list of 'event_invites'
+   */
+  public function &notifications_get() {
+    return $this->call_method('facebook.notifications.get');
+  }
+
+  /**
+   * Sends a notification to the specified users.
+   *
+   * @return A comma separated list of successful recipients
+   * @error
+   *    API_EC_PARAM_USER_ID_LIST
+   */
+  public function &notifications_send($to_ids, $notification, $type) {
+    return $this->call_method('facebook.notifications.send',
+        array('to_ids' => $to_ids,
+              'notification' => $notification,
+              'type' => $type));
+  }
+
+  /**
+   * Sends an email to the specified user of the application.
+   *
+   * @param array/string $recipients array of ids of the recipients (csv is deprecated)
+   * @param string $subject    subject of the email
+   * @param string $text       (plain text) body of the email
+   * @param string $fbml       fbml markup for an html version of the email
+   *
+   * @return string  A comma separated list of successful recipients
+   * @error
+   *    API_EC_PARAM_USER_ID_LIST
+   */
+  public function &notifications_sendEmail($recipients,
+                                           $subject,
+                                           $text,
+                                           $fbml) {
+    return $this->call_method('facebook.notifications.sendEmail',
+        array('recipients' => $recipients,
+              'subject' => $subject,
+              'text' => $text,
+              'fbml' => $fbml));
+  }
+
+  /**
+   * Returns the requested info fields for the requested set of pages.
+   *
+   * @param array/string $page_ids  an array of page ids (csv is deprecated)
+   * @param array/string  $fields    an array of strings describing the
+   *                           info fields desired (csv is deprecated)
+   * @param int    $uid       (Optional) limit results to pages of which this
+   *                          user is a fan.
+   * @param string type       limits results to a particular type of page.
+   *
+   * @return array  An array of pages
+   */
+  public function &pages_getInfo($page_ids, $fields, $uid, $type) {
+    return $this->call_method('facebook.pages.getInfo',
+        array('page_ids' => $page_ids,
+              'fields' => $fields,
+              'uid' => $uid,
+              'type' => $type));
+  }
+
+  /**
+   * Returns true if the given user is an admin for the passed page.
+   *
+   * @param int $page_id  target page id
+   * @param int $uid      (Optional) user id (defaults to the logged-in user)
+   *
+   * @return bool  true on success
+   */
+  public function &pages_isAdmin($page_id, $uid = null) {
+    return $this->call_method('facebook.pages.isAdmin',
+        array('page_id' => $page_id,
+              'uid' => $uid));
+  }
+
+  /**
+   * Returns whether or not the given page has added the application.
+   *
+   * @param int $page_id  target page id
+   *
+   * @return bool  true on success
+   */
+  public function &pages_isAppAdded($page_id) {
+    return $this->call_method('facebook.pages.isAppAdded',
+        array('page_id' => $page_id));
+  }
+
+  /**
+   * Returns true if logged in user is a fan for the passed page.
+   *
+   * @param int $page_id target page id
+   * @param int $uid user to compare.  If empty, the logged in user.
+   *
+   * @return bool  true on success
+   */
+  public function &pages_isFan($page_id, $uid = null) {
+    return $this->call_method('facebook.pages.isFan',
+        array('page_id' => $page_id,
+              'uid' => $uid));
+  }
+
+  /**
+   * Adds a tag with the given information to a photo. See the wiki for details:
+   *
+   *  http://wiki.developers.facebook.com/index.php/Photos.addTag
+   *
+   * @param int $pid          The ID of the photo to be tagged
+   * @param int $tag_uid      The ID of the user being tagged. You must specify
+   *                          either the $tag_uid or the $tag_text parameter
+   *                          (unless $tags is specified).
+   * @param string $tag_text  Some text identifying the person being tagged.
+   *                          You must specify either the $tag_uid or $tag_text
+   *                          parameter (unless $tags is specified).
+   * @param float $x          The horizontal position of the tag, as a
+   *                          percentage from 0 to 100, from the left of the
+   *                          photo.
+   * @param float $y          The vertical position of the tag, as a percentage
+   *                          from 0 to 100, from the top of the photo.
+   * @param array $tags       (Optional) An array of maps, where each map
+   *                          can contain the tag_uid, tag_text, x, and y
+   *                          parameters defined above.  If specified, the
+   *                          individual arguments are ignored.
+   * @param int $owner_uid    (Optional)  The user ID of the user whose photo
+   *                          you are tagging. If this parameter is not
+   *                          specified, then it defaults to the session user.
+   *
+   * @return bool  true on success
+   */
+  public function &photos_addTag($pid,
+                                 $tag_uid,
+                                 $tag_text,
+                                 $x,
+                                 $y,
+                                 $tags,
+                                 $owner_uid=0) {
+    return $this->call_method('facebook.photos.addTag',
+        array('pid' => $pid,
+              'tag_uid' => $tag_uid,
+              'tag_text' => $tag_text,
+              'x' => $x,
+              'y' => $y,
+              'tags' => (is_array($tags)) ? json_encode($tags) : null,
+              'owner_uid' => $this->get_uid($owner_uid)));
+  }
+
+  /**
+   * Creates and returns a new album owned by the specified user or the current
+   * session user.
+   *
+   * @param string $name         The name of the album.
+   * @param string $description  (Optional) A description of the album.
+   * @param string $location     (Optional) A description of the location.
+   * @param string $visible      (Optional) A privacy setting for the album.
+   *                             One of 'friends', 'friends-of-friends',
+   *                             'networks', or 'everyone'.  Default 'everyone'.
+   * @param int $uid             (Optional) User id for creating the album; if
+   *                             not specified, the session user is used.
+   *
+   * @return array  An album object
+   */
+  public function &photos_createAlbum($name,
+                                      $description='',
+                                      $location='',
+                                      $visible='',
+                                      $uid=0) {
+    return $this->call_method('facebook.photos.createAlbum',
+        array('name' => $name,
+              'description' => $description,
+              'location' => $location,
+              'visible' => $visible,
+              'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Returns photos according to the filters specified.
+   *
+   * @param int $subj_id  (Optional) Filter by uid of user tagged in the photos.
+   * @param int $aid      (Optional) Filter by an album, as returned by
+   *                      photos_getAlbums.
+   * @param array/string $pids   (Optional) Restrict to an array of pids
+   *                             (csv is deprecated)
+   *
+   * Note that at least one of these parameters needs to be specified, or an
+   * error is returned.
+   *
+   * @return array  An array of photo objects.
+   */
+  public function &photos_get($subj_id, $aid, $pids) {
+    return $this->call_method('facebook.photos.get',
+      array('subj_id' => $subj_id, 'aid' => $aid, 'pids' => $pids));
+  }
+
+  /**
+   * Returns the albums created by the given user.
+   *
+   * @param int $uid      (Optional) The uid of the user whose albums you want.
+   *                       A null will return the albums of the session user.
+   * @param string $aids  (Optional) An array of aids to restrict
+   *                       the query. (csv is deprecated)
+   *
+   * Note that at least one of the (uid, aids) parameters must be specified.
+   *
+   * @returns an array of album objects.
+   */
+  public function &photos_getAlbums($uid, $aids) {
+    return $this->call_method('facebook.photos.getAlbums',
+      array('uid' => $uid,
+            'aids' => $aids));
+  }
+
+  /**
+   * Returns the tags on all photos specified.
+   *
+   * @param string $pids  A list of pids to query
+   *
+   * @return array  An array of photo tag objects, which include pid,
+   *                subject uid, and two floating-point numbers (xcoord, ycoord)
+   *                for tag pixel location.
+   */
+  public function &photos_getTags($pids) {
+    return $this->call_method('facebook.photos.getTags',
+      array('pids' => $pids));
+  }
+
+  /**
+   * Uploads a photo.
+   *
+   * @param string $file     The location of the photo on the local filesystem.
+   * @param int $aid         (Optional) The album into which to upload the
+   *                         photo.
+   * @param string $caption  (Optional) A caption for the photo.
+   * @param int uid          (Optional) The user ID of the user whose photo you
+   *                         are uploading
+   *
+   * @return array  An array of user objects
+   */
+  public function photos_upload($file, $aid=null, $caption=null, $uid=null) {
+    return $this->call_upload_method('facebook.photos.upload',
+                                     array('aid' => $aid,
+                                           'caption' => $caption,
+                                           'uid' => $uid),
+                                     $file);
+  }
+
+
+  /**
+   * Uploads a video.
+   *
+   * @param  string $file        The location of the video on the local filesystem.
+   * @param  string $title       (Optional) A title for the video. Titles over 65 characters in length will be truncated.
+   * @param  string $description (Optional) A description for the video.
+   *
+   * @return array  An array with the video's ID, title, description, and a link to view it on Facebook.
+   */
+  public function video_upload($file, $title=null, $description=null) {
+    return $this->call_upload_method('facebook.video.upload',
+                                     array('title' => $title,
+                                           'description' => $description),
+                                     $file,
+                                     Facebook::get_facebook_url('api-video') . '/restserver.php');
+  }
+
+  /**
+   * Returns an array with the video limitations imposed on the current session's
+   * associated user. Maximum length is measured in seconds; maximum size is
+   * measured in bytes.
+   *
+   * @return array  Array with "length" and "size" keys
+   */
+  public function &video_getUploadLimits() {
+    return $this->call_method('facebook.video.getUploadLimits');
+  }
+
+  /**
+   * Returns the requested info fields for the requested set of users.
+   *
+   * @param array/string $uids    An array of user ids (csv is deprecated)
+   * @param array/string $fields  An array of info field names desired (csv is deprecated)
+   *
+   * @return array  An array of user objects
+   */
+  public function &users_getInfo($uids, $fields) {
+    return $this->call_method('facebook.users.getInfo',
+                  array('uids' => $uids,
+                        'fields' => $fields));
+  }
+
+  /**
+   * Returns the requested info fields for the requested set of users. A
+   * session key must not be specified. Only data about users that have
+   * authorized your application will be returned.
+   *
+   * Check the wiki for fields that can be queried through this API call.
+   * Data returned from here should not be used for rendering to application
+   * users, use users.getInfo instead, so that proper privacy rules will be
+   * applied.
+   *
+   * @param array/string $uids    An array of user ids (csv is deprecated)
+   * @param array/string $fields  An array of info field names desired (csv is deprecated)
+   *
+   * @return array  An array of user objects
+   */
+  public function &users_getStandardInfo($uids, $fields) {
+    return $this->call_method('facebook.users.getStandardInfo',
+                              array('uids' => $uids,
+                                    'fields' => $fields));
+  }
+
+  /**
+   * Returns the user corresponding to the current session object.
+   *
+   * @return integer  User id
+   */
+  public function &users_getLoggedInUser() {
+    return $this->call_method('facebook.users.getLoggedInUser');
+  }
+
+  /**
+   * Returns 1 if the user has the specified permission, 0 otherwise.
+   * http://wiki.developers.facebook.com/index.php/Users.hasAppPermission
+   *
+   * @return integer  1 or 0
+   */
+  public function &users_hasAppPermission($ext_perm, $uid=null) {
+    return $this->call_method('facebook.users.hasAppPermission',
+        array('ext_perm' => $ext_perm, 'uid' => $uid));
+  }
+
+  /**
+   * Returns whether or not the user corresponding to the current
+   * session object has the give the app basic authorization.
+   *
+   * @return boolean  true if the user has authorized the app
+   */
+  public function &users_isAppUser($uid=null) {
+    if ($uid === null && isset($this->is_user)) {
+      return $this->is_user;
+    }
+
+    return $this->call_method('facebook.users.isAppUser', array('uid' => $uid));
+  }
+
+  /**
+   * Returns whether or not the user corresponding to the current
+   * session object is verified by Facebook. See the documentation
+   * for Users.isVerified for details.
+   *
+   * @return boolean  true if the user is verified
+   */
+  public function &users_isVerified() {
+    return $this->call_method('facebook.users.isVerified');
+  }
+
+  /**
+   * Sets the users' current status message. Message does NOT contain the
+   * word "is" , so make sure to include a verb.
+   *
+   * Example: setStatus("is loving the API!")
+   * will produce the status "Luke is loving the API!"
+   *
+   * @param string $status                text-only message to set
+   * @param int    $uid                   user to set for (defaults to the
+   *                                      logged-in user)
+   * @param bool   $clear                 whether or not to clear the status,
+   *                                      instead of setting it
+   * @param bool   $status_includes_verb  if true, the word "is" will *not* be
+   *                                      prepended to the status message
+   *
+   * @return boolean
+   */
+  public function &users_setStatus($status,
+                                   $uid = null,
+                                   $clear = false,
+                                   $status_includes_verb = true) {
+    $args = array(
+      'status' => $status,
+      'uid' => $uid,
+      'clear' => $clear,
+      'status_includes_verb' => $status_includes_verb,
+    );
+    return $this->call_method('facebook.users.setStatus', $args);
+  }
+
+  /**
+   * Gets the comments for a particular xid. This is essentially a wrapper
+   * around the comment FQL table.
+   *
+   * @param string $xid external id associated with the comments
+   *
+   * @return array of comment objects
+   */
+  public function &comments_get($xid) {
+    $args = array('xid' => $xid);
+    return $this->call_method('facebook.comments.get', $args);
+  }
+
+  /**
+   * Add a comment to a particular xid on behalf of a user. If called
+   * without an app_secret (with session secret), this will only work
+   * for the session user.
+   *
+   * @param string $xid   external id associated with the comments
+   * @param string $text  text of the comment
+   * @param int    $uid   user adding the comment (def: session user)
+   * @param string $title optional title for the stream story
+   * @param string $url   optional url for the stream story
+   * @param bool   $publish_to_stream publish a feed story about this comment?
+   *                      a link will be generated to title/url in the story
+   *
+   * @return string comment_id associated with the comment
+   */
+  public function &comments_add($xid, $text, $uid=0, $title='', $url='',
+                                $publish_to_stream=false) {
+    $args = array(
+      'xid'               => $xid,
+      'uid'               => $this->get_uid($uid),
+      'text'              => $text,
+      'title'             => $title,
+      'url'               => $url,
+      'publish_to_stream' => $publish_to_stream);
+
+    return $this->call_method('facebook.comments.add', $args);
+  }
+
+  /**
+   * Remove a particular comment.
+   *
+   * @param string $xid        the external id associated with the comments
+   * @param string $comment_id id of the comment to remove (returned by
+   *                           comments.add and comments.get)
+   *
+   * @return boolean
+   */
+  public function &comments_remove($xid, $comment_id) {
+    $args = array(
+      'xid'        => $xid,
+      'comment_id' => $comment_id);
+    return $this->call_method('facebook.comments.remove', $args);
+  }
+
+  /**
+   * Gets the stream on behalf of a user using a set of users. This
+   * call will return the latest $limit queries between $start_time
+   * and $end_time.
+   *
+   * @param int    $viewer_id  user making the call (def: session)
+   * @param array  $source_ids users/pages to look at (def: all connections)
+   * @param int    $start_time start time to look for stories (def: 1 day ago)
+   * @param int    $end_time   end time to look for stories (def: now)
+   * @param int    $limit      number of stories to attempt to fetch (def: 30)
+   * @param string $filter_key key returned by stream.getFilters to fetch
+   * @param array  $metadata   metadata to include with the return, allows
+   *                           requested metadata to be returned, such as
+   *                           profiles, albums, photo_tags
+   *
+   * @return array(
+   *           'posts'      => array of posts,
+   *           // if requested, the following data may be returned
+   *           'profiles'   => array of profile metadata of users/pages in posts
+   *           'albums'     => array of album metadata in posts
+   *           'photo_tags' => array of photo_tags for photos in posts
+   *         )
+   */
+  public function &stream_get($viewer_id = null,
+                              $source_ids = null,
+                              $start_time = 0,
+                              $end_time = 0,
+                              $limit = 30,
+                              $filter_key = '') {
+    $args = array(
+      'viewer_id'  => $viewer_id,
+      'source_ids' => $source_ids,
+      'start_time' => $start_time,
+      'end_time'   => $end_time,
+      'limit'      => $limit,
+      'filter_key' => $filter_key);
+    return $this->call_method('facebook.stream.get', $args);
+  }
+
+  /**
+   * Gets the filters (with relevant filter keys for stream.get) for a
+   * particular user. These filters are typical things like news feed,
+   * friend lists, networks. They can be used to filter the stream
+   * without complex queries to determine which ids belong in which groups.
+   *
+   * @param int $uid user to get filters for
+   *
+   * @return array of stream filter objects
+   */
+  public function &stream_getFilters($uid = null) {
+    $args = array('uid' => $uid);
+    return $this->call_method('facebook.stream.getFilters', $args);
+  }
+
+  /**
+   * Gets the full comments given a post_id from stream.get or the
+   * stream FQL table. Initially, only a set of preview comments are
+   * returned because some posts can have many comments.
+   *
+   * @param string $post_id id of the post to get comments for
+   *
+   * @return array of comment objects
+   */
+  public function &stream_getComments($post_id) {
+    $args = array('post_id' => $post_id);
+    return $this->call_method('facebook.stream.getComments', $args);
+  }
+
+  /**
+   * Sets the FBML for the profile of the user attached to this session.
+   *
+   * @param   string   $markup           The FBML that describes the profile
+   *                                     presence of this app for the user
+   * @param   int      $uid              The user
+   * @param   string   $profile          Profile FBML
+   * @param   string   $profile_action   Profile action FBML (deprecated)
+   * @param   string   $mobile_profile   Mobile profile FBML
+   * @param   string   $profile_main     Main Tab profile FBML
+   *
+   * @return  array  A list of strings describing any compile errors for the
+   *                 submitted FBML
+   */
+  function profile_setFBML($markup,
+                           $uid=null,
+                           $profile='',
+                           $profile_action='',
+                           $mobile_profile='',
+                           $profile_main='') {
+    return $this->call_method('facebook.profile.setFBML',
+        array('markup' => $markup,
+              'uid' => $uid,
+              'profile' => $profile,
+              'profile_action' => $profile_action,
+              'mobile_profile' => $mobile_profile,
+              'profile_main' => $profile_main));
+  }
+
+  /**
+   * Gets the FBML for the profile box that is currently set for a user's
+   * profile (your application set the FBML previously by calling the
+   * profile.setFBML method).
+   *
+   * @param int $uid   (Optional) User id to lookup; defaults to session.
+   * @param int $type  (Optional) 1 for original style, 2 for profile_main boxes
+   *
+   * @return string  The FBML
+   */
+  public function &profile_getFBML($uid=null, $type=null) {
+    return $this->call_method('facebook.profile.getFBML',
+        array('uid' => $uid,
+              'type' => $type));
+  }
+
+  /**
+   * Returns the specified user's application info section for the calling
+   * application. These info sections have either been set via a previous
+   * profile.setInfo call or by the user editing them directly.
+   *
+   * @param int $uid  (Optional) User id to lookup; defaults to session.
+   *
+   * @return array  Info fields for the current user.  See wiki for structure:
+   *
+   *  http://wiki.developers.facebook.com/index.php/Profile.getInfo
+   *
+   */
+  public function &profile_getInfo($uid=null) {
+    return $this->call_method('facebook.profile.getInfo',
+        array('uid' => $uid));
+  }
+
+  /**
+   * Returns the options associated with the specified info field for an
+   * application info section.
+   *
+   * @param string $field  The title of the field
+   *
+   * @return array  An array of info options.
+   */
+  public function &profile_getInfoOptions($field) {
+    return $this->call_method('facebook.profile.getInfoOptions',
+        array('field' => $field));
+  }
+
+  /**
+   * Configures an application info section that the specified user can install
+   * on the Info tab of her profile.  For details on the structure of an info
+   * field, please see:
+   *
+   *  http://wiki.developers.facebook.com/index.php/Profile.setInfo
+   *
+   * @param string $title       Title / header of the info section
+   * @param int $type           1 for text-only, 5 for thumbnail views
+   * @param array $info_fields  An array of info fields. See wiki for details.
+   * @param int $uid            (Optional)
+   *
+   * @return bool  true on success
+   */
+  public function &profile_setInfo($title, $type, $info_fields, $uid=null) {
+    return $this->call_method('facebook.profile.setInfo',
+        array('uid' => $uid,
+              'type' => $type,
+              'title'   => $title,
+              'info_fields' => json_encode($info_fields)));
+  }
+
+  /**
+   * Specifies the objects for a field for an application info section. These
+   * options populate the typeahead for a thumbnail.
+   *
+   * @param string $field   The title of the field
+   * @param array $options  An array of items for a thumbnail, including
+   *                        'label', 'link', and optionally 'image',
+   *                        'description' and 'sublabel'
+   *
+   * @return bool  true on success
+   */
+  public function profile_setInfoOptions($field, $options) {
+    return $this->call_method('facebook.profile.setInfoOptions',
+        array('field'   => $field,
+              'options' => json_encode($options)));
+  }
+
+  /**
+   * Get all the marketplace categories.
+   *
+   * @return array  A list of category names
+   */
+  function marketplace_getCategories() {
+    return $this->call_method('facebook.marketplace.getCategories',
+        array());
+  }
+
+  /**
+   * Get all the marketplace subcategories for a particular category.
+   *
+   * @param  category  The category for which we are pulling subcategories
+   *
+   * @return array A list of subcategory names
+   */
+  function marketplace_getSubCategories($category) {
+    return $this->call_method('facebook.marketplace.getSubCategories',
+        array('category' => $category));
+  }
+
+  /**
+   * Get listings by either listing_id or user.
+   *
+   * @param listing_ids   An array of listing_ids (optional)
+   * @param uids          An array of user ids (optional)
+   *
+   * @return array  The data for matched listings
+   */
+  function marketplace_getListings($listing_ids, $uids) {
+    return $this->call_method('facebook.marketplace.getListings',
+        array('listing_ids' => $listing_ids, 'uids' => $uids));
+  }
+
+  /**
+   * Search for Marketplace listings.  All arguments are optional, though at
+   * least one must be filled out to retrieve results.
+   *
+   * @param category     The category in which to search (optional)
+   * @param subcategory  The subcategory in which to search (optional)
+   * @param query        A query string (optional)
+   *
+   * @return array  The data for matched listings
+   */
+  function marketplace_search($category, $subcategory, $query) {
+    return $this->call_method('facebook.marketplace.search',
+        array('category' => $category,
+              'subcategory' => $subcategory,
+              'query' => $query));
+  }
+
+  /**
+   * Remove a listing from Marketplace.
+   *
+   * @param listing_id  The id of the listing to be removed
+   * @param status      'SUCCESS', 'NOT_SUCCESS', or 'DEFAULT'
+   *
+   * @return bool  True on success
+   */
+  function marketplace_removeListing($listing_id,
+                                     $status='DEFAULT',
+                                     $uid=null) {
+    return $this->call_method('facebook.marketplace.removeListing',
+        array('listing_id' => $listing_id,
+              'status' => $status,
+              'uid' => $uid));
+  }
+
+  /**
+   * Create/modify a Marketplace listing for the loggedinuser.
+   *
+   * @param int              listing_id  The id of a listing to be modified, 0
+   *                                     for a new listing.
+   * @param show_on_profile  bool        Should we show this listing on the
+   *                                     user's profile
+   * @param listing_attrs    array       An array of the listing data
+   *
+   * @return int  The listing_id (unchanged if modifying an existing listing).
+   */
+  function marketplace_createListing($listing_id,
+                                     $show_on_profile,
+                                     $attrs,
+                                     $uid=null) {
+    return $this->call_method('facebook.marketplace.createListing',
+        array('listing_id' => $listing_id,
+              'show_on_profile' => $show_on_profile,
+              'listing_attrs' => json_encode($attrs),
+              'uid' => $uid));
+  }
+
+  /////////////////////////////////////////////////////////////////////////////
+  // Data Store API
+
+  /**
+   * Set a user preference.
+   *
+   * @param  pref_id    preference identifier (0-200)
+   * @param  value      preferece's value
+   * @param  uid        the user id (defaults to current session user)
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   *    API_EC_PERMISSION_OTHER_USER
+   */
+  public function &data_setUserPreference($pref_id, $value, $uid = null) {
+    return $this->call_method('facebook.data.setUserPreference',
+       array('pref_id' => $pref_id,
+             'value' => $value,
+             'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Set a user's all preferences for this application.
+   *
+   * @param  values     preferece values in an associative arrays
+   * @param  replace    whether to replace all existing preferences or
+   *                    merge into them.
+   * @param  uid        the user id (defaults to current session user)
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   *    API_EC_PERMISSION_OTHER_USER
+   */
+  public function &data_setUserPreferences($values,
+                                           $replace = false,
+                                           $uid = null) {
+    return $this->call_method('facebook.data.setUserPreferences',
+       array('values' => json_encode($values),
+             'replace' => $replace,
+             'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Get a user preference.
+   *
+   * @param  pref_id    preference identifier (0-200)
+   * @param  uid        the user id (defaults to current session user)
+   * @return            preference's value
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   *    API_EC_PERMISSION_OTHER_USER
+   */
+  public function &data_getUserPreference($pref_id, $uid = null) {
+    return $this->call_method('facebook.data.getUserPreference',
+       array('pref_id' => $pref_id,
+             'uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Get a user preference.
+   *
+   * @param  uid        the user id (defaults to current session user)
+   * @return            preference values
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   *    API_EC_PERMISSION_OTHER_USER
+   */
+  public function &data_getUserPreferences($uid = null) {
+    return $this->call_method('facebook.data.getUserPreferences',
+       array('uid' => $this->get_uid($uid)));
+  }
+
+  /**
+   * Create a new object type.
+   *
+   * @param  name       object type's name
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_createObjectType($name) {
+    return $this->call_method('facebook.data.createObjectType',
+       array('name' => $name));
+  }
+
+  /**
+   * Delete an object type.
+   *
+   * @param  obj_type       object type's name
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_dropObjectType($obj_type) {
+    return $this->call_method('facebook.data.dropObjectType',
+       array('obj_type' => $obj_type));
+  }
+
+  /**
+   * Rename an object type.
+   *
+   * @param  obj_type       object type's name
+   * @param  new_name       new object type's name
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_renameObjectType($obj_type, $new_name) {
+    return $this->call_method('facebook.data.renameObjectType',
+       array('obj_type' => $obj_type,
+             'new_name' => $new_name));
+  }
+
+  /**
+   * Add a new property to an object type.
+   *
+   * @param  obj_type       object type's name
+   * @param  prop_name      name of the property to add
+   * @param  prop_type      1: integer; 2: string; 3: text blob
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_defineObjectProperty($obj_type,
+                                             $prop_name,
+                                             $prop_type) {
+    return $this->call_method('facebook.data.defineObjectProperty',
+       array('obj_type' => $obj_type,
+             'prop_name' => $prop_name,
+             'prop_type' => $prop_type));
+  }
+
+  /**
+   * Remove a previously defined property from an object type.
+   *
+   * @param  obj_type      object type's name
+   * @param  prop_name     name of the property to remove
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_undefineObjectProperty($obj_type, $prop_name) {
+    return $this->call_method('facebook.data.undefineObjectProperty',
+       array('obj_type' => $obj_type,
+             'prop_name' => $prop_name));
+  }
+
+  /**
+   * Rename a previously defined property of an object type.
+   *
+   * @param  obj_type      object type's name
+   * @param  prop_name     name of the property to rename
+   * @param  new_name      new name to use
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_renameObjectProperty($obj_type, $prop_name,
+                                            $new_name) {
+    return $this->call_method('facebook.data.renameObjectProperty',
+       array('obj_type' => $obj_type,
+             'prop_name' => $prop_name,
+             'new_name' => $new_name));
+  }
+
+  /**
+   * Retrieve a list of all object types that have defined for the application.
+   *
+   * @return               a list of object type names
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getObjectTypes() {
+    return $this->call_method('facebook.data.getObjectTypes');
+  }
+
+  /**
+   * Get definitions of all properties of an object type.
+   *
+   * @param obj_type       object type's name
+   * @return               pairs of property name and property types
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getObjectType($obj_type) {
+    return $this->call_method('facebook.data.getObjectType',
+       array('obj_type' => $obj_type));
+  }
+
+  /**
+   * Create a new object.
+   *
+   * @param  obj_type      object type's name
+   * @param  properties    (optional) properties to set initially
+   * @return               newly created object's id
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_createObject($obj_type, $properties = null) {
+    return $this->call_method('facebook.data.createObject',
+       array('obj_type' => $obj_type,
+             'properties' => json_encode($properties)));
+  }
+
+  /**
+   * Update an existing object.
+   *
+   * @param  obj_id        object's id
+   * @param  properties    new properties
+   * @param  replace       true for replacing existing properties;
+   *                       false for merging
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_updateObject($obj_id, $properties, $replace = false) {
+    return $this->call_method('facebook.data.updateObject',
+       array('obj_id' => $obj_id,
+             'properties' => json_encode($properties),
+             'replace' => $replace));
+  }
+
+  /**
+   * Delete an existing object.
+   *
+   * @param  obj_id        object's id
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_deleteObject($obj_id) {
+    return $this->call_method('facebook.data.deleteObject',
+       array('obj_id' => $obj_id));
+  }
+
+  /**
+   * Delete a list of objects.
+   *
+   * @param  obj_ids       objects to delete
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_deleteObjects($obj_ids) {
+    return $this->call_method('facebook.data.deleteObjects',
+       array('obj_ids' => json_encode($obj_ids)));
+  }
+
+  /**
+   * Get a single property value of an object.
+   *
+   * @param  obj_id        object's id
+   * @param  prop_name     individual property's name
+   * @return               individual property's value
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getObjectProperty($obj_id, $prop_name) {
+    return $this->call_method('facebook.data.getObjectProperty',
+       array('obj_id' => $obj_id,
+             'prop_name' => $prop_name));
+  }
+
+  /**
+   * Get properties of an object.
+   *
+   * @param  obj_id      object's id
+   * @param  prop_names  (optional) properties to return; null for all.
+   * @return             specified properties of an object
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getObject($obj_id, $prop_names = null) {
+    return $this->call_method('facebook.data.getObject',
+       array('obj_id' => $obj_id,
+             'prop_names' => json_encode($prop_names)));
+  }
+
+  /**
+   * Get properties of a list of objects.
+   *
+   * @param  obj_ids     object ids
+   * @param  prop_names  (optional) properties to return; null for all.
+   * @return             specified properties of an object
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getObjects($obj_ids, $prop_names = null) {
+    return $this->call_method('facebook.data.getObjects',
+       array('obj_ids' => json_encode($obj_ids),
+             'prop_names' => json_encode($prop_names)));
+  }
+
+  /**
+   * Set a single property value of an object.
+   *
+   * @param  obj_id        object's id
+   * @param  prop_name     individual property's name
+   * @param  prop_value    new value to set
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_setObjectProperty($obj_id, $prop_name,
+                                         $prop_value) {
+    return $this->call_method('facebook.data.setObjectProperty',
+       array('obj_id' => $obj_id,
+             'prop_name' => $prop_name,
+             'prop_value' => $prop_value));
+  }
+
+  /**
+   * Read hash value by key.
+   *
+   * @param  obj_type      object type's name
+   * @param  key           hash key
+   * @param  prop_name     (optional) individual property's name
+   * @return               hash value
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getHashValue($obj_type, $key, $prop_name = null) {
+    return $this->call_method('facebook.data.getHashValue',
+       array('obj_type' => $obj_type,
+             'key' => $key,
+             'prop_name' => $prop_name));
+  }
+
+  /**
+   * Write hash value by key.
+   *
+   * @param  obj_type      object type's name
+   * @param  key           hash key
+   * @param  value         hash value
+   * @param  prop_name     (optional) individual property's name
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_setHashValue($obj_type,
+                                     $key,
+                                     $value,
+                                     $prop_name = null) {
+    return $this->call_method('facebook.data.setHashValue',
+       array('obj_type' => $obj_type,
+             'key' => $key,
+             'value' => $value,
+             'prop_name' => $prop_name));
+  }
+
+  /**
+   * Increase a hash value by specified increment atomically.
+   *
+   * @param  obj_type      object type's name
+   * @param  key           hash key
+   * @param  prop_name     individual property's name
+   * @param  increment     (optional) default is 1
+   * @return               incremented hash value
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_incHashValue($obj_type,
+                                     $key,
+                                     $prop_name,
+                                     $increment = 1) {
+    return $this->call_method('facebook.data.incHashValue',
+       array('obj_type' => $obj_type,
+             'key' => $key,
+             'prop_name' => $prop_name,
+             'increment' => $increment));
+  }
+
+  /**
+   * Remove a hash key and its values.
+   *
+   * @param  obj_type    object type's name
+   * @param  key         hash key
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_removeHashKey($obj_type, $key) {
+    return $this->call_method('facebook.data.removeHashKey',
+       array('obj_type' => $obj_type,
+             'key' => $key));
+  }
+
+  /**
+   * Remove hash keys and their values.
+   *
+   * @param  obj_type    object type's name
+   * @param  keys        hash keys
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_removeHashKeys($obj_type, $keys) {
+    return $this->call_method('facebook.data.removeHashKeys',
+       array('obj_type' => $obj_type,
+             'keys' => json_encode($keys)));
+  }
+
+  /**
+   * Define an object association.
+   *
+   * @param  name        name of this association
+   * @param  assoc_type  1: one-way 2: two-way symmetric 3: two-way asymmetric
+   * @param  assoc_info1 needed info about first object type
+   * @param  assoc_info2 needed info about second object type
+   * @param  inverse     (optional) name of reverse association
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_defineAssociation($name, $assoc_type, $assoc_info1,
+                                         $assoc_info2, $inverse = null) {
+    return $this->call_method('facebook.data.defineAssociation',
+       array('name' => $name,
+             'assoc_type' => $assoc_type,
+             'assoc_info1' => json_encode($assoc_info1),
+             'assoc_info2' => json_encode($assoc_info2),
+             'inverse' => $inverse));
+  }
+
+  /**
+   * Undefine an object association.
+   *
+   * @param  name        name of this association
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_undefineAssociation($name) {
+    return $this->call_method('facebook.data.undefineAssociation',
+       array('name' => $name));
+  }
+
+  /**
+   * Rename an object association or aliases.
+   *
+   * @param  name        name of this association
+   * @param  new_name    (optional) new name of this association
+   * @param  new_alias1  (optional) new alias for object type 1
+   * @param  new_alias2  (optional) new alias for object type 2
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_ALREADY_EXISTS
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_renameAssociation($name, $new_name, $new_alias1 = null,
+                                         $new_alias2 = null) {
+    return $this->call_method('facebook.data.renameAssociation',
+       array('name' => $name,
+             'new_name' => $new_name,
+             'new_alias1' => $new_alias1,
+             'new_alias2' => $new_alias2));
+  }
+
+  /**
+   * Get definition of an object association.
+   *
+   * @param  name        name of this association
+   * @return             specified association
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getAssociationDefinition($name) {
+    return $this->call_method('facebook.data.getAssociationDefinition',
+       array('name' => $name));
+  }
+
+  /**
+   * Get definition of all associations.
+   *
+   * @return             all defined associations
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getAssociationDefinitions() {
+    return $this->call_method('facebook.data.getAssociationDefinitions',
+       array());
+  }
+
+  /**
+   * Create or modify an association between two objects.
+   *
+   * @param  name        name of association
+   * @param  obj_id1     id of first object
+   * @param  obj_id2     id of second object
+   * @param  data        (optional) extra string data to store
+   * @param  assoc_time  (optional) extra time data; default to creation time
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_setAssociation($name, $obj_id1, $obj_id2, $data = null,
+                                      $assoc_time = null) {
+    return $this->call_method('facebook.data.setAssociation',
+       array('name' => $name,
+             'obj_id1' => $obj_id1,
+             'obj_id2' => $obj_id2,
+             'data' => $data,
+             'assoc_time' => $assoc_time));
+  }
+
+  /**
+   * Create or modify associations between objects.
+   *
+   * @param  assocs      associations to set
+   * @param  name        (optional) name of association
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_setAssociations($assocs, $name = null) {
+    return $this->call_method('facebook.data.setAssociations',
+       array('assocs' => json_encode($assocs),
+             'name' => $name));
+  }
+
+  /**
+   * Remove an association between two objects.
+   *
+   * @param  name        name of association
+   * @param  obj_id1     id of first object
+   * @param  obj_id2     id of second object
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_removeAssociation($name, $obj_id1, $obj_id2) {
+    return $this->call_method('facebook.data.removeAssociation',
+       array('name' => $name,
+             'obj_id1' => $obj_id1,
+             'obj_id2' => $obj_id2));
+  }
+
+  /**
+   * Remove associations between objects by specifying pairs of object ids.
+   *
+   * @param  assocs      associations to remove
+   * @param  name        (optional) name of association
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_removeAssociations($assocs, $name = null) {
+    return $this->call_method('facebook.data.removeAssociations',
+       array('assocs' => json_encode($assocs),
+             'name' => $name));
+  }
+
+  /**
+   * Remove associations between objects by specifying one object id.
+   *
+   * @param  name        name of association
+   * @param  obj_id      who's association to remove
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_removeAssociatedObjects($name, $obj_id) {
+    return $this->call_method('facebook.data.removeAssociatedObjects',
+       array('name' => $name,
+             'obj_id' => $obj_id));
+  }
+
+  /**
+   * Retrieve a list of associated objects.
+   *
+   * @param  name        name of association
+   * @param  obj_id      who's association to retrieve
+   * @param  no_data     only return object ids
+   * @return             associated objects
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getAssociatedObjects($name, $obj_id, $no_data = true) {
+    return $this->call_method('facebook.data.getAssociatedObjects',
+       array('name' => $name,
+             'obj_id' => $obj_id,
+             'no_data' => $no_data));
+  }
+
+  /**
+   * Count associated objects.
+   *
+   * @param  name        name of association
+   * @param  obj_id      who's association to retrieve
+   * @return             associated object's count
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getAssociatedObjectCount($name, $obj_id) {
+    return $this->call_method('facebook.data.getAssociatedObjectCount',
+       array('name' => $name,
+             'obj_id' => $obj_id));
+  }
+
+  /**
+   * Get a list of associated object counts.
+   *
+   * @param  name        name of association
+   * @param  obj_ids     whose association to retrieve
+   * @return             associated object counts
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_DATA_OBJECT_NOT_FOUND
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_INVALID_OPERATION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getAssociatedObjectCounts($name, $obj_ids) {
+    return $this->call_method('facebook.data.getAssociatedObjectCounts',
+       array('name' => $name,
+             'obj_ids' => json_encode($obj_ids)));
+  }
+
+  /**
+   * Find all associations between two objects.
+   *
+   * @param  obj_id1     id of first object
+   * @param  obj_id2     id of second object
+   * @param  no_data     only return association names without data
+   * @return             all associations between objects
+   * @error
+   *    API_EC_DATA_DATABASE_ERROR
+   *    API_EC_PARAM
+   *    API_EC_PERMISSION
+   *    API_EC_DATA_QUOTA_EXCEEDED
+   *    API_EC_DATA_UNKNOWN_ERROR
+   */
+  public function &data_getAssociations($obj_id1, $obj_id2, $no_data = true) {
+    return $this->call_method('facebook.data.getAssociations',
+       array('obj_id1' => $obj_id1,
+             'obj_id2' => $obj_id2,
+             'no_data' => $no_data));
+  }
+
+  /**
+   * Get the properties that you have set for an app.
+   *
+   * @param properties  List of properties names to fetch
+   *
+   * @return array  A map from property name to value
+   */
+  public function admin_getAppProperties($properties) {
+    return json_decode(
+        $this->call_method('facebook.admin.getAppProperties',
+            array('properties' => json_encode($properties))), true);
+  }
+
+  /**
+   * Set properties for an app.
+   *
+   * @param properties  A map from property names to values
+   *
+   * @return bool  true on success
+   */
+  public function admin_setAppProperties($properties) {
+    return $this->call_method('facebook.admin.setAppProperties',
+       array('properties' => json_encode($properties)));
+  }
+
+  /**
+   * Returns the allocation limit value for a specified integration point name
+   * Integration point names are defined in lib/api/karma/constants.php in the
+   * limit_map.
+   *
+   * @param string $integration_point_name  Name of an integration point
+   *                                        (see developer wiki for list).
+   * @param int    $uid                     Specific user to check the limit.
+   *
+   * @return int  Integration point allocation value
+   */
+  public function &admin_getAllocation($integration_point_name, $uid=null) {
+    return $this->call_method('facebook.admin.getAllocation',
+        array('integration_point_name' => $integration_point_name,
+              'uid' => $uid));
+  }
+
+  /**
+   * Returns values for the specified metrics for the current application, in
+   * the given time range.  The metrics are collected for fixed-length periods,
+   * and the times represent midnight at the end of each period.
+   *
+   * @param start_time  unix time for the start of the range
+   * @param end_time    unix time for the end of the range
+   * @param period      number of seconds in the desired period
+   * @param metrics     list of metrics to look up
+   *
+   * @return array  A map of the names and values for those metrics
+   */
+  public function &admin_getMetrics($start_time, $end_time, $period, $metrics) {
+    return $this->call_method('admin.getMetrics',
+        array('start_time' => $start_time,
+              'end_time' => $end_time,
+              'period' => $period,
+              'metrics' => json_encode($metrics)));
+  }
+
+  /**
+   * Sets application restriction info.
+   *
+   * Applications can restrict themselves to only a limited user demographic
+   * based on users' age and/or location or based on static predefined types
+   * specified by facebook for specifying diff age restriction for diff
+   * locations.
+   *
+   * @param array $restriction_info  The age restriction settings to set.
+   *
+   * @return bool  true on success
+   */
+  public function admin_setRestrictionInfo($restriction_info = null) {
+    $restriction_str = null;
+    if (!empty($restriction_info)) {
+      $restriction_str = json_encode($restriction_info);
+    }
+    return $this->call_method('admin.setRestrictionInfo',
+        array('restriction_str' => $restriction_str));
+  }
+
+  /**
+   * Gets application restriction info.
+   *
+   * Applications can restrict themselves to only a limited user demographic
+   * based on users' age and/or location or based on static predefined types
+   * specified by facebook for specifying diff age restriction for diff
+   * locations.
+   *
+   * @return array  The age restriction settings for this application.
+   */
+  public function admin_getRestrictionInfo() {
+    return json_decode(
+        $this->call_method('admin.getRestrictionInfo'),
+        true);
+  }
+
+
+  /**
+   * Bans a list of users from the app. Banned users can't
+   * access the app's canvas page and forums.
+   *
+   * @param array $uids an array of user ids
+   * @return bool true on success
+   */
+  public function admin_banUsers($uids) {
+    return $this->call_method(
+      'admin.banUsers', array('uids' => json_encode($uids)));
+  }
+
+  /**
+   * Unban users that have been previously banned with
+   * admin_banUsers().
+   *
+   * @param array $uids an array of user ids
+   * @return bool true on success
+   */
+  public function admin_unbanUsers($uids) {
+    return $this->call_method(
+      'admin.unbanUsers', array('uids' => json_encode($uids)));
+  }
+
+  /**
+   * Gets the list of users that have been banned from the application.
+   * $uids is an optional parameter that filters the result with the list
+   * of provided user ids. If $uids is provided,
+   * only banned user ids that are contained in $uids are returned.
+   *
+   * @param array $uids an array of user ids to filter by
+   * @return bool true on success
+   */
+
+  public function admin_getBannedUsers($uids = null) {
+    return $this->call_method(
+      'admin.getBannedUsers',
+      array('uids' => $uids ? json_encode($uids) : null));
+  }
+
+
+  /* UTILITY FUNCTIONS */
+
+  /**
+   * Calls the specified normal POST method with the specified parameters.
+   *
+   * @param string $method  Name of the Facebook method to invoke
+   * @param array $params   A map of param names => param values
+   *
+   * @return mixed  Result of method call; this returns a reference to support
+   *                'delayed returns' when in a batch context.
+   *     See: http://wiki.developers.facebook.com/index.php/Using_batching_API
+   */
+  public function &call_method($method, $params = array()) {
+    if ($this->format) {
+      $params['format'] = $this->format;
+    }
+    if (!$this->pending_batch()) {
+      if ($this->call_as_apikey) {
+        $params['call_as_apikey'] = $this->call_as_apikey;
+      }
+      $data = $this->post_request($method, $params);
+      $result = $this->convert_result($data, $method, $params);
+      if (is_array($result) && isset($result['error_code'])) {
+        throw new FacebookRestClientException($result['error_msg'],
+                                              $result['error_code']);
+      }
+    }
+    else {
+      $result = null;
+      $batch_item = array('m' => $method, 'p' => $params, 'r' => & $result);
+      $this->batch_queue[] = $batch_item;
+    }
+
+    return $result;
+  }
+
+  protected function convert_result($data, $method, $params) {
+    $is_xml = (empty($params['format']) ||
+               strtolower($params['format']) != 'json');
+    return ($is_xml) ? $this->convert_xml_to_result($data, $method, $params)
+                     : json_decode($data, true);
+  }
+
+  /**
+   * Change the response format
+   *
+   * @param string $format The response format (json, xml)
+   */
+  public function setFormat($format) {
+    $this->format = $format;
+    return $this;
+  }
+
+  /**
+   * get the current response serialization format
+   *
+   * @return string 'xml', 'json', or null (which means 'xml')
+   */
+  public function getFormat() {
+    return $this->format;
+  }
+
+  /**
+   * Calls the specified file-upload POST method with the specified parameters
+   *
+   * @param string $method Name of the Facebook method to invoke
+   * @param array  $params A map of param names => param values
+   * @param string $file   A path to the file to upload (required)
+   *
+   * @return array A dictionary representing the response.
+   */
+  public function call_upload_method($method, $params, $file, $server_addr = null) {
+    if (!$this->pending_batch()) {
+      if (!file_exists($file)) {
+        $code =
+          FacebookAPIErrorCodes::API_EC_PARAM;
+        $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+        throw new FacebookRestClientException($description, $code);
+      }
+
+      if ($this->format) {
+        $params['format'] = $this->format;
+      }
+      $data = $this->post_upload_request($method,
+                                         $params,
+                                         $file,
+                                         $server_addr);
+      $result = $this->convert_result($data, $method, $params);
+
+      if (is_array($result) && isset($result['error_code'])) {
+        throw new FacebookRestClientException($result['error_msg'],
+                                              $result['error_code']);
+      }
+    }
+    else {
+      $code =
+        FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE;
+      $description = FacebookAPIErrorCodes::$api_error_descriptions[$code];
+      throw new FacebookRestClientException($description, $code);
+    }
+
+    return $result;
+  }
+
+  protected function convert_xml_to_result($xml, $method, $params) {
+    $sxml = simplexml_load_string($xml);
+    $result = self::convert_simplexml_to_array($sxml);
+
+    if (!empty($GLOBALS['facebook_config']['debug'])) {
+      // output the raw xml and its corresponding php object, for debugging:
+      print '<div style="margin: 10px 30px; padding: 5px; border: 2px solid black; background: gray; color: white; font-size: 12px; font-weight: bold;">';
+      $this->cur_id++;
+      print $this->cur_id . ': Called ' . $method . ', show ' .
+            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'params\');">Params</a> | '.
+            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'xml\');">XML</a> | '.
+            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'sxml\');">SXML</a> | '.
+            '<a href=# onclick="return toggleDisplay(' . $this->cur_id . ', \'php\');">PHP</a>';
+      print '<pre id="params'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($params, true).'</pre>';
+      print '<pre id="xml'.$this->cur_id.'" style="display: none; overflow: auto;">'.htmlspecialchars($xml).'</pre>';
+      print '<pre id="php'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($result, true).'</pre>';
+      print '<pre id="sxml'.$this->cur_id.'" style="display: none; overflow: auto;">'.print_r($sxml, true).'</pre>';
+      print '</div>';
+    }
+    return $result;
+  }
+
+  protected function finalize_params($method, $params) {
+    list($get, $post) = $this->add_standard_params($method, $params);
+    // we need to do this before signing the params
+    $this->convert_array_values_to_json($post);
+    $post['sig'] = Facebook::generate_sig(array_merge($get, $post),
+                                          $this->secret);
+    return array($get, $post);
+  }
+
+  private function convert_array_values_to_json(&$params) {
+    foreach ($params as $key => &$val) {
+      if (is_array($val)) {
+        $val = json_encode($val);
+      }
+    }
+  }
+
+  /**
+   * Add the generally required params to our request.
+   * Params method, api_key, and v should be sent over as get.
+   */
+  private function add_standard_params($method, $params) {
+    $post = $params;
+    $get = array();
+    if ($this->call_as_apikey) {
+      $get['call_as_apikey'] = $this->call_as_apikey;
+    }
+    $get['method'] = $method;
+    $get['session_key'] = $this->session_key;
+    $get['api_key'] = $this->api_key;
+    $post['call_id'] = microtime(true);
+    if ($post['call_id'] <= $this->last_call_id) {
+      $post['call_id'] = $this->last_call_id + 0.001;
+    }
+    $this->last_call_id = $post['call_id'];
+    if (isset($post['v'])) {
+      $get['v'] = $post['v'];
+      unset($post['v']);
+    } else {
+      $get['v'] = '1.0';
+    }
+    if (isset($this->use_ssl_resources) &&
+        $this->use_ssl_resources) {
+      $post['return_ssl_resources'] = true;
+    }
+    return array($get, $post);
+  }
+
+  private function create_url_string($params) {
+    $post_params = array();
+    foreach ($params as $key => &$val) {
+      $post_params[] = $key.'='.urlencode($val);
+    }
+    return implode('&', $post_params);
+  }
+
+  private function run_multipart_http_transaction($method, $params, $file, $server_addr) {
+
+    // the format of this message is specified in RFC1867/RFC1341.
+    // we add twenty pseudo-random digits to the end of the boundary string.
+    $boundary = '--------------------------FbMuLtIpArT' .
+                sprintf("%010d", mt_rand()) .
+                sprintf("%010d", mt_rand());
+    $content_type = 'multipart/form-data; boundary=' . $boundary;
+    // within the message, we prepend two extra hyphens.
+    $delimiter = '--' . $boundary;
+    $close_delimiter = $delimiter . '--';
+    $content_lines = array();
+    foreach ($params as $key => &$val) {
+      $content_lines[] = $delimiter;
+      $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"';
+      $content_lines[] = '';
+      $content_lines[] = $val;
+    }
+    // now add the file data
+    $content_lines[] = $delimiter;
+    $content_lines[] =
+      'Content-Disposition: form-data; filename="' . $file . '"';
+    $content_lines[] = 'Content-Type: application/octet-stream';
+    $content_lines[] = '';
+    $content_lines[] = file_get_contents($file);
+    $content_lines[] = $close_delimiter;
+    $content_lines[] = '';
+    $content = implode("\r\n", $content_lines);
+    return $this->run_http_post_transaction($content_type, $content, $server_addr);
+  }
+
+  public function post_request($method, $params) {
+    list($get, $post) = $this->finalize_params($method, $params);
+    $post_string = $this->create_url_string($post);
+    $get_string = $this->create_url_string($get);
+    $url_with_get = $this->server_addr . '?' . $get_string;
+    if ($this->use_curl_if_available && function_exists('curl_init')) {
+      $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
+      $ch = curl_init();
+      curl_setopt($ch, CURLOPT_URL, $url_with_get);
+      curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string);
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+      curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
+      curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+      curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+      $result = $this->curl_exec($ch);
+      curl_close($ch);
+    } else {
+      $content_type = 'application/x-www-form-urlencoded';
+      $content = $post_string;
+      $result = $this->run_http_post_transaction($content_type,
+                                                 $content,
+                                                 $url_with_get);
+    }
+    return $result;
+  }
+
+  /**
+   * execute a curl transaction -- this exists mostly so subclasses can add
+   * extra options and/or process the response, if they wish.
+   *
+   * @param resource $ch a curl handle
+   */
+  protected function curl_exec($ch) {
+      $result = curl_exec($ch);
+      return $result;
+  }
+
+  private function post_upload_request($method, $params, $file, $server_addr = null) {
+    $server_addr = $server_addr ? $server_addr : $this->server_addr;
+    list($get, $post) = $this->finalize_params($method, $params);
+    $get_string = $this->create_url_string($get);
+    $url_with_get = $server_addr . '?' . $get_string;
+    if ($this->use_curl_if_available && function_exists('curl_init')) {
+      // prepending '@' causes cURL to upload the file; the key is ignored.
+      $post['_file'] = '@' . $file;
+      $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
+      $ch = curl_init();
+      curl_setopt($ch, CURLOPT_URL, $url_with_get);
+      // this has to come before the POSTFIELDS set!
+      curl_setopt($ch, CURLOPT_POST, 1);
+      // passing an array gets curl to use the multipart/form-data content type
+      curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
+      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+      curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
+      $result = $this->curl_exec($ch);
+      curl_close($ch);
+    } else {
+      $result = $this->run_multipart_http_transaction($method, $post,
+                                                      $file, $url_with_get);
+    }
+    return $result;
+  }
+
+  private function run_http_post_transaction($content_type, $content, $server_addr) {
+
+    $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion();
+    $content_length = strlen($content);
+    $context =
+      array('http' =>
+              array('method' => 'POST',
+                    'user_agent' => $user_agent,
+                    'header' => 'Content-Type: ' . $content_type . "\r\n" .
+                                'Content-Length: ' . $content_length,
+                    'content' => $content));
+    $context_id = stream_context_create($context);
+    $sock = fopen($server_addr, 'r', false, $context_id);
+
+    $result = '';
+    if ($sock) {
+      while (!feof($sock)) {
+        $result .= fgets($sock, 4096);
+      }
+      fclose($sock);
+    }
+    return $result;
+  }
+
+  public static function convert_simplexml_to_array($sxml) {
+    $arr = array();
+    if ($sxml) {
+      foreach ($sxml as $k => $v) {
+        if ($sxml['list']) {
+          $arr[] = self::convert_simplexml_to_array($v);
+        } else {
+          $arr[$k] = self::convert_simplexml_to_array($v);
+        }
+      }
+    }
+    if (sizeof($arr) > 0) {
+      return $arr;
+    } else {
+      return (string)$sxml;
+    }
+  }
+
+  protected function get_uid($uid) {
+    return $uid ? $uid : $this->user;
+  }
+}
+
+
+class FacebookRestClientException extends Exception {
+}
+
+// Supporting methods and values------
+
+/**
+ * Error codes and descriptions for the Facebook API.
+ */
+
+class FacebookAPIErrorCodes {
+
+  const API_EC_SUCCESS = 0;
+
+  /*
+   * GENERAL ERRORS
+   */
+  const API_EC_UNKNOWN = 1;
+  const API_EC_SERVICE = 2;
+  const API_EC_METHOD = 3;
+  const API_EC_TOO_MANY_CALLS = 4;
+  const API_EC_BAD_IP = 5;
+  const API_EC_HOST_API = 6;
+  const API_EC_HOST_UP = 7;
+  const API_EC_SECURE = 8;
+  const API_EC_RATE = 9;
+  const API_EC_PERMISSION_DENIED = 10;
+  const API_EC_DEPRECATED = 11;
+  const API_EC_VERSION = 12;
+  const API_EC_INTERNAL_FQL_ERROR = 13;
+  const API_EC_HOST_PUP = 14;
+
+  /*
+   * PARAMETER ERRORS
+   */
+  const API_EC_PARAM = 100;
+  const API_EC_PARAM_API_KEY = 101;
+  const API_EC_PARAM_SESSION_KEY = 102;
+  const API_EC_PARAM_CALL_ID = 103;
+  const API_EC_PARAM_SIGNATURE = 104;
+  const API_EC_PARAM_TOO_MANY = 105;
+  const API_EC_PARAM_USER_ID = 110;
+  const API_EC_PARAM_USER_FIELD = 111;
+  const API_EC_PARAM_SOCIAL_FIELD = 112;
+  const API_EC_PARAM_EMAIL = 113;
+  const API_EC_PARAM_USER_ID_LIST = 114;
+  const API_EC_PARAM_FIELD_LIST = 115;
+  const API_EC_PARAM_ALBUM_ID = 120;
+  const API_EC_PARAM_PHOTO_ID = 121;
+  const API_EC_PARAM_FEED_PRIORITY = 130;
+  const API_EC_PARAM_CATEGORY = 140;
+  const API_EC_PARAM_SUBCATEGORY = 141;
+  const API_EC_PARAM_TITLE = 142;
+  const API_EC_PARAM_DESCRIPTION = 143;
+  const API_EC_PARAM_BAD_JSON = 144;
+  const API_EC_PARAM_BAD_EID = 150;
+  const API_EC_PARAM_UNKNOWN_CITY = 151;
+  const API_EC_PARAM_BAD_PAGE_TYPE = 152;
+
+  /*
+   * USER PERMISSIONS ERRORS
+   */
+  const API_EC_PERMISSION = 200;
+  const API_EC_PERMISSION_USER = 210;
+  const API_EC_PERMISSION_NO_DEVELOPERS = 211;
+  const API_EC_PERMISSION_OFFLINE_ACCESS = 212;
+  const API_EC_PERMISSION_ALBUM = 220;
+  const API_EC_PERMISSION_PHOTO = 221;
+  const API_EC_PERMISSION_MESSAGE = 230;
+  const API_EC_PERMISSION_OTHER_USER = 240;
+  const API_EC_PERMISSION_STATUS_UPDATE = 250;
+  const API_EC_PERMISSION_PHOTO_UPLOAD = 260;
+  const API_EC_PERMISSION_VIDEO_UPLOAD = 261;
+  const API_EC_PERMISSION_SMS = 270;
+  const API_EC_PERMISSION_CREATE_LISTING = 280;
+  const API_EC_PERMISSION_CREATE_NOTE = 281;
+  const API_EC_PERMISSION_SHARE_ITEM = 282;
+  const API_EC_PERMISSION_EVENT = 290;
+  const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291;
+  const API_EC_PERMISSION_LIVEMESSAGE = 292;
+  const API_EC_PERMISSION_RSVP_EVENT = 299;
+
+  /*
+   * DATA EDIT ERRORS
+   */
+  const API_EC_EDIT = 300;
+  const API_EC_EDIT_USER_DATA = 310;
+  const API_EC_EDIT_PHOTO = 320;
+  const API_EC_EDIT_ALBUM_SIZE = 321;
+  const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322;
+  const API_EC_EDIT_PHOTO_TAG_PHOTO = 323;
+  const API_EC_EDIT_PHOTO_FILE = 324;
+  const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325;
+  const API_EC_EDIT_PHOTO_TAG_LIMIT = 326;
+  const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327;
+  const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328;
+
+  const API_EC_MALFORMED_MARKUP = 329;
+  const API_EC_EDIT_MARKUP = 330;
+
+  const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340;
+  const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341;
+  const API_EC_EDIT_FEED_TITLE_LINK = 342;
+  const API_EC_EDIT_FEED_TITLE_LENGTH = 343;
+  const API_EC_EDIT_FEED_TITLE_NAME = 344;
+  const API_EC_EDIT_FEED_TITLE_BLANK = 345;
+  const API_EC_EDIT_FEED_BODY_LENGTH = 346;
+  const API_EC_EDIT_FEED_PHOTO_SRC = 347;
+  const API_EC_EDIT_FEED_PHOTO_LINK = 348;
+
+  const API_EC_EDIT_VIDEO_SIZE = 350;
+  const API_EC_EDIT_VIDEO_INVALID_FILE = 351;
+  const API_EC_EDIT_VIDEO_INVALID_TYPE = 352;
+  const API_EC_EDIT_VIDEO_FILE = 353;
+
+  const API_EC_EDIT_FEED_TITLE_ARRAY = 360;
+  const API_EC_EDIT_FEED_TITLE_PARAMS = 361;
+  const API_EC_EDIT_FEED_BODY_ARRAY = 362;
+  const API_EC_EDIT_FEED_BODY_PARAMS = 363;
+  const API_EC_EDIT_FEED_PHOTO = 364;
+  const API_EC_EDIT_FEED_TEMPLATE = 365;
+  const API_EC_EDIT_FEED_TARGET = 366;
+  const API_EC_EDIT_FEED_MARKUP = 367;
+
+  /**
+   * SESSION ERRORS
+   */
+  const API_EC_SESSION_TIMED_OUT = 450;
+  const API_EC_SESSION_METHOD = 451;
+  const API_EC_SESSION_INVALID = 452;
+  const API_EC_SESSION_REQUIRED = 453;
+  const API_EC_SESSION_REQUIRED_FOR_SECRET = 454;
+  const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455;
+
+
+  /**
+   * FQL ERRORS
+   */
+  const FQL_EC_UNKNOWN_ERROR = 600;
+  const FQL_EC_PARSER = 601; // backwards compatibility
+  const FQL_EC_PARSER_ERROR = 601;
+  const FQL_EC_UNKNOWN_FIELD = 602;
+  const FQL_EC_UNKNOWN_TABLE = 603;
+  const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility
+  const FQL_EC_NO_INDEX = 604;
+  const FQL_EC_UNKNOWN_FUNCTION = 605;
+  const FQL_EC_INVALID_PARAM = 606;
+  const FQL_EC_INVALID_FIELD = 607;
+  const FQL_EC_INVALID_SESSION = 608;
+  const FQL_EC_UNSUPPORTED_APP_TYPE = 609;
+  const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610;
+  const FQL_EC_DEPRECATED_TABLE = 611;
+  const FQL_EC_EXTENDED_PERMISSION = 612;
+  const FQL_EC_RATE_LIMIT_EXCEEDED = 613;
+  const FQL_EC_UNRESOLVED_DEPENDENCY = 614;
+
+  const API_EC_REF_SET_FAILED = 700;
+
+  /**
+   * DATA STORE API ERRORS
+   */
+  const API_EC_DATA_UNKNOWN_ERROR = 800;
+  const API_EC_DATA_INVALID_OPERATION = 801;
+  const API_EC_DATA_QUOTA_EXCEEDED = 802;
+  const API_EC_DATA_OBJECT_NOT_FOUND = 803;
+  const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804;
+  const API_EC_DATA_DATABASE_ERROR = 805;
+  const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806;
+  const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807;
+  const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808;
+  const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809;
+  const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810;
+  const API_EC_DATA_MALFORMED_ACTION_LINK = 811;
+  const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812;
+
+  /*
+   * APPLICATION INFO ERRORS
+   */
+  const API_EC_NO_SUCH_APP = 900;
+
+  /*
+   * BATCH ERRORS
+   */
+  const API_EC_BATCH_TOO_MANY_ITEMS = 950;
+  const API_EC_BATCH_ALREADY_STARTED = 951;
+  const API_EC_BATCH_NOT_STARTED = 952;
+  const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953;
+
+  /*
+   * EVENT API ERRORS
+   */
+  const API_EC_EVENT_INVALID_TIME = 1000;
+
+  /*
+   * INFO BOX ERRORS
+   */
+  const API_EC_INFO_NO_INFORMATION = 1050;
+  const API_EC_INFO_SET_FAILED = 1051;
+
+  /*
+   * LIVEMESSAGE API ERRORS
+   */
+  const API_EC_LIVEMESSAGE_SEND_FAILED = 1100;
+  const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101;
+  const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102;
+
+  /*
+   * PAYMENTS API ERRORS
+   */
+  const API_EC_PAYMENTS_UNKNOWN = 1150;
+  const API_EC_PAYMENTS_APP_INVALID = 1151;
+  const API_EC_PAYMENTS_DATABASE = 1152;
+  const API_EC_PAYMENTS_PERMISSION_DENIED = 1153;
+  const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154;
+  const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155;
+  const API_EC_PAYMENTS_INVALID_ORDER = 1156;
+  const API_EC_PAYMENTS_INVALID_PARAM = 1157;
+  const API_EC_PAYMENTS_INVALID_OPERATION = 1158;
+  const API_EC_PAYMENTS_PAYMENT_FAILED = 1159;
+  const API_EC_PAYMENTS_DISABLED = 1160;
+
+  /*
+   * CONNECT SESSION ERRORS
+   */
+  const API_EC_CONNECT_FEED_DISABLED = 1300;
+
+  /*
+   * Platform tag bundles errors
+   */
+  const API_EC_TAG_BUNDLE_QUOTA = 1400;
+
+  /*
+   * SHARE
+   */
+  const API_EC_SHARE_BAD_URL = 1500;
+
+  /*
+   * NOTES
+   */
+  const API_EC_NOTE_CANNOT_MODIFY = 1600;
+
+  /*
+   * COMMENTS
+   */
+  const API_EC_COMMENTS_UNKNOWN = 1700;
+  const API_EC_COMMENTS_POST_TOO_LONG = 1701;
+  const API_EC_COMMENTS_DB_DOWN = 1702;
+  const API_EC_COMMENTS_INVALID_XID = 1703;
+  const API_EC_COMMENTS_INVALID_UID = 1704;
+  const API_EC_COMMENTS_INVALID_POST = 1705;
+  const API_EC_COMMENTS_INVALID_REMOVE = 1706;
+
+  /**
+   * This array is no longer maintained; to view the description of an error
+   * code, please look at the message element of the API response or visit
+   * the developer wiki at http://wiki.developers.facebook.com/.
+   */
+  public static $api_error_descriptions = array(
+      self::API_EC_SUCCESS           => 'Success',
+      self::API_EC_UNKNOWN           => 'An unknown error occurred',
+      self::API_EC_SERVICE           => 'Service temporarily unavailable',
+      self::API_EC_METHOD            => 'Unknown method',
+      self::API_EC_TOO_MANY_CALLS    => 'Application request limit reached',
+      self::API_EC_BAD_IP            => 'Unauthorized source IP address',
+      self::API_EC_PARAM             => 'Invalid parameter',
+      self::API_EC_PARAM_API_KEY     => 'Invalid API key',
+      self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid',
+      self::API_EC_PARAM_CALL_ID     => 'Call_id must be greater than previous',
+      self::API_EC_PARAM_SIGNATURE   => 'Incorrect signature',
+      self::API_EC_PARAM_USER_ID     => 'Invalid user id',
+      self::API_EC_PARAM_USER_FIELD  => 'Invalid user info field',
+      self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field',
+      self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list',
+      self::API_EC_PARAM_FIELD_LIST => 'Invalid field list',
+      self::API_EC_PARAM_ALBUM_ID    => 'Invalid album id',
+      self::API_EC_PARAM_BAD_EID     => 'Invalid eid',
+      self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city',
+      self::API_EC_PERMISSION        => 'Permissions error',
+      self::API_EC_PERMISSION_USER   => 'User not visible',
+      self::API_EC_PERMISSION_NO_DEVELOPERS  => 'Application has no developers',
+      self::API_EC_PERMISSION_ALBUM  => 'Album not visible',
+      self::API_EC_PERMISSION_PHOTO  => 'Photo not visible',
+      self::API_EC_PERMISSION_EVENT  => 'Creating and modifying events required the extended permission create_event',
+      self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event',
+      self::API_EC_EDIT_ALBUM_SIZE   => 'Album is full',
+      self::FQL_EC_PARSER            => 'FQL: Parser Error',
+      self::FQL_EC_UNKNOWN_FIELD     => 'FQL: Unknown Field',
+      self::FQL_EC_UNKNOWN_TABLE     => 'FQL: Unknown Table',
+      self::FQL_EC_NOT_INDEXABLE     => 'FQL: Statement not indexable',
+      self::FQL_EC_UNKNOWN_FUNCTION  => 'FQL: Attempted to call unknown function',
+      self::FQL_EC_INVALID_PARAM     => 'FQL: Invalid parameter passed in',
+      self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error',
+      self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation',
+      self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded',
+      self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found',
+      self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists',
+      self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again',
+      self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first',
+      self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch',
+      self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode'
+  );
+}
diff --git a/plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php b/plugins/Facebook/facebook/jsonwrapper/JSON/JSON.php
new file mode 100644 (file)
index 0000000..0cddbdd
--- /dev/null
@@ -0,0 +1,806 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in  Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package     Services_JSON
+ * @author      Michal Migurski <mike-json@teczno.com>
+ * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright   2005 Michal Migurski
+ * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license     http://www.opensource.org/licenses/bsd-license.php
+ * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE',   1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR',  2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR',  3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ',  4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+   /**
+    * constructs a new JSON instance
+    *
+    * @param    int     $use    object behavior flags; combine with boolean-OR
+    *
+    *                           possible values:
+    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
+    *                                   "{...}" syntax creates associative arrays
+    *                                   instead of objects in decode().
+    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
+    *                                   Values which can't be encoded (e.g. resources)
+    *                                   appear as NULL instead of throwing errors.
+    *                                   By default, a deeply-nested resource will
+    *                                   bubble up with an error, so all return values
+    *                                   from encode() should be checked with isError()
+    */
+    function Services_JSON($use = 0)
+    {
+        $this->use = $use;
+    }
+
+   /**
+    * convert a string from one UTF-16 char to one UTF-8 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf16  UTF-16 character
+    * @return   string  UTF-8 character
+    * @access   private
+    */
+    function utf162utf8($utf16)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+        }
+
+        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+        switch(true) {
+            case ((0x7F & $bytes) == $bytes):
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x7F & $bytes);
+
+            case (0x07FF & $bytes) == $bytes:
+                // return a 2-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xC0 | (($bytes >> 6) & 0x1F))
+                     . chr(0x80 | ($bytes & 0x3F));
+
+            case (0xFFFF & $bytes) == $bytes:
+                // return a 3-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xE0 | (($bytes >> 12) & 0x0F))
+                     . chr(0x80 | (($bytes >> 6) & 0x3F))
+                     . chr(0x80 | ($bytes & 0x3F));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * convert a string from one UTF-8 char to one UTF-16 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf8   UTF-8 character
+    * @return   string  UTF-16 character
+    * @access   private
+    */
+    function utf82utf16($utf8)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+        }
+
+        switch(strlen($utf8)) {
+            case 1:
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return $utf8;
+
+            case 2:
+                // return a UTF-16 character from a 2-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x07 & (ord($utf8{0}) >> 2))
+                     . chr((0xC0 & (ord($utf8{0}) << 6))
+                         | (0x3F & ord($utf8{1})));
+
+            case 3:
+                // return a UTF-16 character from a 3-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr((0xF0 & (ord($utf8{0}) << 4))
+                         | (0x0F & (ord($utf8{1}) >> 2)))
+                     . chr((0xC0 & (ord($utf8{1}) << 6))
+                         | (0x7F & ord($utf8{2})));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * encodes an arbitrary variable into JSON format
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function encode($var)
+    {
+        switch (gettype($var)) {
+            case 'boolean':
+                return $var ? 'true' : 'false';
+
+            case 'NULL':
+                return 'null';
+
+            case 'integer':
+                return (int) $var;
+
+            case 'double':
+            case 'float':
+                return (float) $var;
+
+            case 'string':
+                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+                $ascii = '';
+                $strlen_var = strlen($var);
+
+               /*
+                * Iterate over every character in the string,
+                * escaping with a slash or encoding to UTF-8 where necessary
+                */
+                for ($c = 0; $c < $strlen_var; ++$c) {
+
+                    $ord_var_c = ord($var{$c});
+
+                    switch (true) {
+                        case $ord_var_c == 0x08:
+                            $ascii .= '\b';
+                            break;
+                        case $ord_var_c == 0x09:
+                            $ascii .= '\t';
+                            break;
+                        case $ord_var_c == 0x0A:
+                            $ascii .= '\n';
+                            break;
+                        case $ord_var_c == 0x0C:
+                            $ascii .= '\f';
+                            break;
+                        case $ord_var_c == 0x0D:
+                            $ascii .= '\r';
+                            break;
+
+                        case $ord_var_c == 0x22:
+                        case $ord_var_c == 0x2F:
+                        case $ord_var_c == 0x5C:
+                            // double quote, slash, slosh
+                            $ascii .= '\\'.$var{$c};
+                            break;
+
+                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+                            // characters U-00000000 - U-0000007F (same as ASCII)
+                            $ascii .= $var{$c};
+                            break;
+
+                        case (($ord_var_c & 0xE0) == 0xC0):
+                            // characters U-00000080 - U-000007FF, mask 110XXXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+                            $c += 1;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF0) == 0xE0):
+                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}));
+                            $c += 2;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF8) == 0xF0):
+                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}));
+                            $c += 3;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFC) == 0xF8):
+                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}));
+                            $c += 4;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFE) == 0xFC):
+                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}),
+                                         ord($var{$c + 5}));
+                            $c += 5;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+                    }
+                }
+
+                return '"'.$ascii.'"';
+
+            case 'array':
+               /*
+                * As per JSON spec if any array key is not an integer
+                * we must treat the the whole array as an object. We
+                * also try to catch a sparsely populated associative
+                * array with numeric keys here because some JS engines
+                * will create an array with empty indexes up to
+                * max_index which can cause memory issues and because
+                * the keys, which may be relevant, will be remapped
+                * otherwise.
+                *
+                * As per the ECMA and JSON specification an object may
+                * have any string as a property. Unfortunately due to
+                * a hole in the ECMA specification if the key is a
+                * ECMA reserved word or starts with a digit the
+                * parameter is only accessible using ECMAScript's
+                * bracket notation.
+                */
+
+                // treat as a JSON object
+                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+                    $properties = array_map(array($this, 'name_value'),
+                                            array_keys($var),
+                                            array_values($var));
+
+                    foreach($properties as $property) {
+                        if(Services_JSON::isError($property)) {
+                            return $property;
+                        }
+                    }
+
+                    return '{' . join(',', $properties) . '}';
+                }
+
+                // treat it like a regular array
+                $elements = array_map(array($this, 'encode'), $var);
+
+                foreach($elements as $element) {
+                    if(Services_JSON::isError($element)) {
+                        return $element;
+                    }
+                }
+
+                return '[' . join(',', $elements) . ']';
+
+            case 'object':
+                $vars = get_object_vars($var);
+
+                $properties = array_map(array($this, 'name_value'),
+                                        array_keys($vars),
+                                        array_values($vars));
+
+                foreach($properties as $property) {
+                    if(Services_JSON::isError($property)) {
+                        return $property;
+                    }
+                }
+
+                return '{' . join(',', $properties) . '}';
+
+            default:
+                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+                    ? 'null'
+                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+        }
+    }
+
+   /**
+    * array-walking function for use in generating JSON-formatted name-value pairs
+    *
+    * @param    string  $name   name of key to use
+    * @param    mixed   $value  reference to an array element to be encoded
+    *
+    * @return   string  JSON-formatted name-value pair, like '"name":value'
+    * @access   private
+    */
+    function name_value($name, $value)
+    {
+        $encoded_value = $this->encode($value);
+
+        if(Services_JSON::isError($encoded_value)) {
+            return $encoded_value;
+        }
+
+        return $this->encode(strval($name)) . ':' . $encoded_value;
+    }
+
+   /**
+    * reduce a string by removing leading and trailing comments and whitespace
+    *
+    * @param    $str    string      string value to strip of comments and whitespace
+    *
+    * @return   string  string value stripped of comments and whitespace
+    * @access   private
+    */
+    function reduce_string($str)
+    {
+        $str = preg_replace(array(
+
+                // eliminate single line comments in '// ...' form
+                '#^\s*//(.+)$#m',
+
+                // eliminate multi-line comments in '/* ... */' form, at start of string
+                '#^\s*/\*(.+)\*/#Us',
+
+                // eliminate multi-line comments in '/* ... */' form, at end of string
+                '#/\*(.+)\*/\s*$#Us'
+
+            ), '', $str);
+
+        // eliminate extraneous space
+        return trim($str);
+    }
+
+   /**
+    * decodes a JSON string into appropriate variable
+    *
+    * @param    string  $str    JSON-formatted string
+    *
+    * @return   mixed   number, boolean, string, array, or object
+    *                   corresponding to given JSON input string.
+    *                   See argument 1 to Services_JSON() above for object-output behavior.
+    *                   Note that decode() always returns strings
+    *                   in ASCII or UTF-8 format!
+    * @access   public
+    */
+    function decode($str)
+    {
+        $str = $this->reduce_string($str);
+
+        switch (strtolower($str)) {
+            case 'true':
+                return true;
+
+            case 'false':
+                return false;
+
+            case 'null':
+                return null;
+
+            default:
+                $m = array();
+
+                if (is_numeric($str)) {
+                    // Lookie-loo, it's a number
+
+                    // This would work on its own, but I'm trying to be
+                    // good about returning integers where appropriate:
+                    // return (float)$str;
+
+                    // Return float or int, as appropriate
+                    return ((float)$str == (integer)$str)
+                        ? (integer)$str
+                        : (float)$str;
+
+                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+                    // STRINGS RETURNED IN UTF-8 FORMAT
+                    $delim = substr($str, 0, 1);
+                    $chrs = substr($str, 1, -1);
+                    $utf8 = '';
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+                        $ord_chrs_c = ord($chrs{$c});
+
+                        switch (true) {
+                            case $substr_chrs_c_2 == '\b':
+                                $utf8 .= chr(0x08);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\t':
+                                $utf8 .= chr(0x09);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\n':
+                                $utf8 .= chr(0x0A);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\f':
+                                $utf8 .= chr(0x0C);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\r':
+                                $utf8 .= chr(0x0D);
+                                ++$c;
+                                break;
+
+                            case $substr_chrs_c_2 == '\\"':
+                            case $substr_chrs_c_2 == '\\\'':
+                            case $substr_chrs_c_2 == '\\\\':
+                            case $substr_chrs_c_2 == '\\/':
+                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+                                    $utf8 .= $chrs{++$c};
+                                }
+                                break;
+
+                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+                                // single, escaped unicode character
+                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
+                                $utf8 .= $this->utf162utf8($utf16);
+                                $c += 5;
+                                break;
+
+                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+                                $utf8 .= $chrs{$c};
+                                break;
+
+                            case ($ord_chrs_c & 0xE0) == 0xC0:
+                                // characters U-00000080 - U-000007FF, mask 110XXXXX
+                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 2);
+                                ++$c;
+                                break;
+
+                            case ($ord_chrs_c & 0xF0) == 0xE0:
+                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 3);
+                                $c += 2;
+                                break;
+
+                            case ($ord_chrs_c & 0xF8) == 0xF0:
+                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 4);
+                                $c += 3;
+                                break;
+
+                            case ($ord_chrs_c & 0xFC) == 0xF8:
+                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 5);
+                                $c += 4;
+                                break;
+
+                            case ($ord_chrs_c & 0xFE) == 0xFC:
+                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 6);
+                                $c += 5;
+                                break;
+
+                        }
+
+                    }
+
+                    return $utf8;
+
+                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+                    // array, or object notation
+
+                    if ($str{0} == '[') {
+                        $stk = array(SERVICES_JSON_IN_ARR);
+                        $arr = array();
+                    } else {
+                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = array();
+                        } else {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = new stdClass();
+                        }
+                    }
+
+                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
+                                           'where' => 0,
+                                           'delim' => false));
+
+                    $chrs = substr($str, 1, -1);
+                    $chrs = $this->reduce_string($chrs);
+
+                    if ($chrs == '') {
+                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                            return $arr;
+
+                        } else {
+                            return $obj;
+
+                        }
+                    }
+
+                    //print("\nparsing {$chrs}\n");
+
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+                        $top = end($stk);
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+                            // found a comma that is not inside a string, array, etc.,
+                            // OR we've reached the end of the character list
+                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
+                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                                // we are in an array, so just push an element onto the stack
+                                array_push($arr, $this->decode($slice));
+
+                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                                // we are in an object, so figure
+                                // out the property name and set an
+                                // element in an associative array,
+                                // for now
+                                $parts = array();
+                                
+                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // "name":value pair
+                                    $key = $this->decode($parts[1]);
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // name:value pair, where name is unquoted
+                                    $key = $parts[1];
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                }
+
+                            }
+
+                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+                            // found a quote, and we are not inside a string
+                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+                            //print("Found start of string at {$c}\n");
+
+                        } elseif (($chrs{$c} == $top['delim']) &&
+                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
+                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+                            // found a quote, we're in a string, and it's not escaped
+                            // we know that it's not escaped becase there is _not_ an
+                            // odd number of backslashes at the end of the string so far
+                            array_pop($stk);
+                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '[') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-bracket, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+                            //print("Found start of array at {$c}\n");
+
+                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+                            // found a right-bracket, and we're in an array
+                            array_pop($stk);
+                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '{') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-brace, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+                            //print("Found start of object at {$c}\n");
+
+                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+                            // found a right-brace, and we're in an object
+                            array_pop($stk);
+                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($substr_chrs_c_2 == '/*') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a comment start, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+                            $c++;
+                            //print("Found start of comment at {$c}\n");
+
+                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+                            // found a comment end, and we're in one now
+                            array_pop($stk);
+                            $c++;
+
+                            for ($i = $top['where']; $i <= $c; ++$i)
+                                $chrs = substr_replace($chrs, ' ', $i, 1);
+
+                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        }
+
+                    }
+
+                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                        return $arr;
+
+                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                        return $obj;
+
+                    }
+
+                }
+        }
+    }
+
+    /**
+     * @todo Ultimately, this should just call PEAR::isError()
+     */
+    function isError($data, $code = null)
+    {
+        if (class_exists('pear')) {
+            return PEAR::isError($data, $code);
+        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+                                 is_subclass_of($data, 'services_json_error'))) {
+            return true;
+        }
+
+        return false;
+    }
+}
+
+if (class_exists('PEAR_Error')) {
+
+    class Services_JSON_Error extends PEAR_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+        }
+    }
+
+} else {
+
+    /**
+     * @todo Ultimately, this class shall be descended from PEAR_Error
+     */
+    class Services_JSON_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+
+        }
+    }
+
+}
+    
+?>
diff --git a/plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE b/plugins/Facebook/facebook/jsonwrapper/JSON/LICENSE
new file mode 100644 (file)
index 0000000..4ae6bef
--- /dev/null
@@ -0,0 +1,21 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper.php
new file mode 100644 (file)
index 0000000..29509de
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+# In PHP 5.2 or higher we don't need to bring this in
+if (!function_exists('json_encode')) {
+       require_once 'jsonwrapper_inner.php';
+} 
+?>
diff --git a/plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php b/plugins/Facebook/facebook/jsonwrapper/jsonwrapper_inner.php
new file mode 100644 (file)
index 0000000..36a3f28
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+require_once 'JSON/JSON.php';
+
+function json_encode($arg)
+{
+       global $services_json;
+       if (!isset($services_json)) {
+               $services_json = new Services_JSON();
+       }
+       return $services_json->encode($arg);
+}
+
+function json_decode($arg)
+{
+       global $services_json;
+       if (!isset($services_json)) {
+               $services_json = new Services_JSON();
+       }
+       return $services_json->decode($arg);
+}
+
+?>
diff --git a/plugins/Facebook/fbfavicon.ico b/plugins/Facebook/fbfavicon.ico
new file mode 100644 (file)
index 0000000..c57c034
Binary files /dev/null and b/plugins/Facebook/fbfavicon.ico differ