Merge branch '0.8.x' of git@gitorious.org:laconica/mainline into 0.8.x

This commit is contained in:
Evan Prodromou 2009-07-16 00:27:23 -04:00
commit 3c2006eb13
4 changed files with 374 additions and 77 deletions

View File

@ -107,13 +107,13 @@ class Facebook {
* @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');
$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');
$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);
}

View File

@ -55,6 +55,7 @@ class FacebookRestClient {
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;
@ -178,39 +179,32 @@ function toggleDisplay(id, type) {
private function execute_server_side_batch() {
$item_count = count($this->batch_queue);
$method_feed = array();
foreach($this->batch_queue as $batch_item) {
foreach ($this->batch_queue as $batch_item) {
$method = $batch_item['m'];
$params = $batch_item['p'];
$this->finalize_params($method, $params);
$method_feed[] = $this->create_post_string($method, $params);
list($get, $post) = $this->finalize_params($method, $params);
$method_feed[] = $this->create_url_string(array_merge($post, $get));
}
$method_feed_json = json_encode($method_feed);
$serial_only =
($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY);
$params = array('method_feed' => $method_feed_json,
'serial_only' => $serial_only);
if ($this->call_as_apikey) {
$params['call_as_apikey'] = $this->call_as_apikey;
}
$xml = $this->post_request('batch.run', $params);
$result = $this->convert_xml_to_result($xml, 'batch.run', $params);
$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++) {
for ($i = 0; $i < $item_count; $i++) {
$batch_item = $this->batch_queue[$i];
$batch_item_result_xml = $result[$i];
$batch_item_result = $this->convert_xml_to_result($batch_item_result_xml,
$batch_item['m'],
$batch_item['p']);
$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'])) {
@ -516,12 +510,20 @@ function toggleDisplay(id, type) {
* 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) {
return $this->call_method('facebook.events.create',
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));
}
}
/**
@ -529,13 +531,21 @@ function toggleDisplay(id, type) {
*
* @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) {
return $this->call_method('facebook.events.edit',
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));
'event_info' => $event_info));
}
}
/**
@ -935,7 +945,7 @@ function toggleDisplay(id, type) {
/**
* 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://developers.facebook.com/documentation.php?v=1.0&doc=fql
* info at http://wiki.developers.facebook.com/index.php/FQL
*
* @param string $query the query to evaluate
*
@ -946,6 +956,21 @@ function toggleDisplay(id, type) {
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.
@ -994,6 +1019,23 @@ function toggleDisplay(id, type) {
}
/**
* 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.
*
@ -1168,6 +1210,44 @@ function toggleDisplay(id, type) {
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.
*
@ -1233,7 +1313,6 @@ function toggleDisplay(id, type) {
* notes.
*/
public function &notes_get($uid, $note_ids = null) {
return $this->call_method('notes.get',
array('uid' => $uid,
'note_ids' => $note_ids));
@ -1631,6 +1710,63 @@ function toggleDisplay(id, type) {
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
@ -1642,11 +1778,16 @@ function toggleDisplay(id, type) {
* @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,
* 'profiles' => array of profile metadata of users/pages in posts
* 'albums' => array of album metadata in posts
* '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,
@ -2849,6 +2990,7 @@ function toggleDisplay(id, type) {
array('uids' => $uids ? json_encode($uids) : null));
}
/* UTILITY FUNCTIONS */
/**
@ -2862,18 +3004,15 @@ function toggleDisplay(id, type) {
* 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);
if (empty($params['format']) || strtolower($params['format']) != 'json') {
$result = $this->convert_xml_to_result($data, $method, $params);
}
else {
$result = json_decode($data, true);
}
$result = $this->convert_result($data, $method, $params);
if (is_array($result) && isset($result['error_code'])) {
throw new FacebookRestClientException($result['error_msg'],
$result['error_code']);
@ -2888,6 +3027,32 @@ function toggleDisplay(id, type) {
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
*
@ -2906,8 +3071,14 @@ function toggleDisplay(id, type) {
throw new FacebookRestClientException($description, $code);
}
$xml = $this->post_upload_request($method, $params, $file, $server_addr);
$result = $this->convert_xml_to_result($xml, $method, $params);
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'],
@ -2946,11 +3117,13 @@ function toggleDisplay(id, type) {
return $result;
}
private function finalize_params($method, &$params) {
$this->add_standard_params($method, $params);
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($params);
$params['sig'] = Facebook::generate_sig($params, $this->secret);
$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) {
@ -2961,28 +3134,38 @@ function toggleDisplay(id, type) {
}
}
private function add_standard_params($method, &$params) {
/**
* 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) {
$params['call_as_apikey'] = $this->call_as_apikey;
$get['call_as_apikey'] = $this->call_as_apikey;
}
$params['method'] = $method;
$params['session_key'] = $this->session_key;
$params['api_key'] = $this->api_key;
$params['call_id'] = microtime(true);
if ($params['call_id'] <= $this->last_call_id) {
$params['call_id'] = $this->last_call_id + 0.001;
$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 = $params['call_id'];
if (!isset($params['v'])) {
$params['v'] = '1.0';
$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) {
$params['return_ssl_resources'] = true;
$post['return_ssl_resources'] = true;
}
return array($get, $post);
}
private function create_post_string($method, $params) {
private function create_url_string($params) {
$post_params = array();
foreach ($params as $key => &$val) {
$post_params[] = $key.'='.urlencode($val);
@ -3022,48 +3205,64 @@ function toggleDisplay(id, type) {
}
public function post_request($method, $params) {
$this->finalize_params($method, $params);
$post_string = $this->create_post_string($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, $this->server_addr);
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 = curl_exec($ch);
$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,
$this->server_addr);
$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;
$this->finalize_params($method, $params);
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.
$params['_file'] = '@' . $file;
$post['_file'] = '@' . $file;
$useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion();
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $server_addr);
curl_setopt($ch, CURLOPT_URL, $url_with_get);
// this has to come before the POSTFIELDS set!
curl_setopt($ch, CURLOPT_POST, 1 );
curl_setopt($ch, CURLOPT_POST, 1);
// passing an array gets curl to use the multipart/form-data content type
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, $useragent);
$result = curl_exec($ch);
$result = $this->curl_exec($ch);
curl_close($ch);
} else {
$result = $this->run_multipart_http_transaction($method, $params, $file, $server_addr);
$result = $this->run_multipart_http_transaction($method, $post,
$file, $url_with_get);
}
return $result;
}
@ -3110,7 +3309,7 @@ function toggleDisplay(id, type) {
}
}
private function get_uid($uid) {
protected function get_uid($uid) {
return $uid ? $uid : $this->user;
}
}
@ -3145,6 +3344,7 @@ class FacebookAPIErrorCodes {
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
@ -3179,6 +3379,7 @@ class FacebookAPIErrorCodes {
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;
@ -3267,6 +3468,7 @@ class FacebookAPIErrorCodes {
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;
@ -3318,6 +3520,21 @@ class FacebookAPIErrorCodes {
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
*/
@ -3347,6 +3564,7 @@ class FacebookAPIErrorCodes {
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

View File

@ -122,7 +122,9 @@ class FBConnectPlugin extends Plugin
FB_RequireFeatures(
["XFBML"],
function() {
FB.Facebook.init("%s", "../xd_receiver.html");
FB.init("%s", "../xd_receiver.html",
{"doNotUseCachedConnectState":true });
}
); }
@ -220,11 +222,11 @@ class FBConnectPlugin extends Plugin
try {
$facebook = getFacebook();
$fbuid = getFacebook()->get_loggedin_user();
$fbuid = $facebook->api_client->users_getLoggedInUser();
} catch (Exception $e) {
common_log(LOG_WARNING,
'Problem getting Facebook client: ' .
'Problem getting Facebook user: ' .
$e->getMessage());
}
@ -297,9 +299,9 @@ class FBConnectPlugin extends Plugin
$title = _('Logout from the site');
$text = _('Logout');
$html = sprintf('<li id="nav_logout"><a href="%s" title="%s" ' .
'onclick="FB.Connect.logout(function() { goto_logout() })">%s</a></li>',
$logout_url, $title, $text);
$html = sprintf('<li id="nav_logout"><a href="#" title="%s" ' .
'onclick="FB.Connect.logoutAndRedirect(\'%s\');">%s</a></li>',
$title, $logout_url, $text);
$action->raw($html);

77
plugins/FBConnect/README Normal file
View File

@ -0,0 +1,77 @@
This plugin allows you to utilize Facebook Connect with Laconica.
Supported Facebook Connect features:
- Authenticate (register/login/logout -- works similar to OpenID)
- Associate an existing Laconica account with a Facebook account
- Disconnect a Facebook account from a Laconica account
Future planned functionality:
- Invite Facebook friends to use your Laconica installation
- Auto-subscribe Facebook friends already using Laconica
- Share Laconica favorite notices to your Facebook stream
To use the plugin you will need to configure a Facebook application
to point to your Laconica 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 Laconica 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 Laconica site. E.g.:
http://SITE/PATH_TO_LACONICA/
After you application is created and configured, you'll need to add its
API key and secret to your Laconica config.php file:
$config['facebook']['apikey'] = 'APIKEY';
$config['facebook']['secret'] = 'SECRET';
Finally, to enable the plugin, add the following stanza to your
config.php:
require_once(INSTALLDIR.'/plugins/FBConnect/FBConnectPlugin.php');
$fbc = new FBConnectPlugin();
To try out the plugin, fire up your browser and connect to:
http://SITE/PATH_TO_LACONICA/main/facebooklogin
or, if you do not have fancy URLs turned on:
http://SITE/PATH_TO_LACONICA/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 Laconica 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 Laconica account, and therefore haven't
already set a local password.
Helpful links
=============
Facebook Connect Homepage:
http://developers.facebook.com/connect.php