Merge branch '0.9.x' of gitorious.org:statusnet/mainline into 0.9.x

This commit is contained in:
Brion Vibber 2010-11-17 16:13:40 -08:00
commit 3ed5673058
16 changed files with 4481 additions and 8 deletions

View File

@ -379,6 +379,14 @@ GetValidDaemons: Just before determining which daemons to run
HandleQueuedNotice: Handle a queued notice at queue time (or immediately if no queue)
- &$notice: notice to handle
StartHtmlElement: Reight before outputting the HTML element - allows plugins to add namespaces
- $action: the current action
- &$attrs: attributes for the HTML element
EndHtmlElement: Right after outputting the HTML element
- $action: the current action
- &$attrs: attributes for the HTML element
StartShowHeadElements: Right after the <head> tag
- $action: the current action

View File

@ -22,7 +22,7 @@
* @category Action
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @copyright 2009 StatusNet, Inc.
* @copyright 2009-2010 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/
*/
@ -40,12 +40,33 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
* @category Action
* @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/
*
*/
class CurrentUserDesignAction extends Action
{
protected $cur = null; // The current user
/**
* For initializing members of the class. Set a the
* current user here.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($argarray)
{
parent::prepare($argarray);
$this->cur = common_current_user();
return true;
}
/**
* A design for this action
*
@ -55,11 +76,9 @@ class CurrentUserDesignAction extends Action
*/
function getDesign()
{
$cur = common_current_user();
if (!empty($this->cur)) {
if (!empty($cur)) {
$design = $cur->getDesign();
$design = $this->cur->getDesign();
if (!empty($design)) {
return $design;
@ -68,4 +87,10 @@ class CurrentUserDesignAction extends Action
return parent::getDesign();
}
function getCurrentUser()
{
return $this->cur;
}
}

View File

@ -119,9 +119,16 @@ class HTMLOutputter extends XMLOutputter
$language = $this->getLanguage();
$this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xml:lang' => $language,
'lang' => $language));
$attrs = array(
'xmlns' => 'http://www.w3.org/1999/xhtml',
'xml:lang' => $language,
'lang' => $language
);
if (Event::handle('StartHtmlElement', array($this, &$attrs))) {
$this->elementStart('html', $attrs);
Event::handle('EndHtmlElement', array($this, &$attrs));
}
}
function getLanguage()

View File

@ -1501,6 +1501,7 @@ function common_request_id()
function common_log($priority, $msg, $filename=null)
{
if(Event::handle('StartLog', array(&$priority, &$msg, &$filename))){
$msg = (empty($filename)) ? $msg : basename($filename) . ' - ' . $msg;
$msg = '[' . common_request_id() . '] ' . $msg;
$logfile = common_config('site', 'logfile');
if ($logfile) {

View File

@ -0,0 +1,539 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* A plugin for integrating Facebook with StatusNet. Includes single-sign-on
* and publishing notices to Facebook using Facebook's Graph API.
*
* PHP version 5
*
* 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 Pugin
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
define("FACEBOOK_SERVICE", 2);
/**
* Main class for Facebook plugin
*
* @category Plugin
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
class FacebookBridgePlugin extends Plugin
{
public $appId = null; // Facebook application ID
public $secret = null; // Facebook application secret
public $facebook = null; // Facebook application instance
public $dir = null; // Facebook SSO plugin dir
/**
* Initializer for this plugin
*
* Gets an instance of the Facebook API client object
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function initialize()
{
$this->facebook = Facebookclient::getFacebook(
$this->appId,
$this->secret
);
return true;
}
/**
* Load related modules when needed
*
* @param string $cls Name of the class to be loaded
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onAutoload($cls)
{
$dir = dirname(__FILE__);
//common_debug("class = " . $cls);
switch ($cls)
{
case 'Facebook': // Facebook PHP SDK
include_once $dir . '/extlib/facebook.php';
return false;
case 'FacebookloginAction':
case 'FacebookfinishloginAction':
case 'FacebookadminpanelAction':
case 'FacebooksettingsAction':
case 'FacebookdeauthorizeAction':
include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
return false;
case 'Facebookclient':
case 'FacebookQueueHandler':
include_once $dir . '/lib/' . strtolower($cls) . '.php';
return false;
case 'Notice_to_item':
include_once $dir . '/classes/' . $cls . '.php';
return false;
default:
return true;
}
}
/**
* Database schema setup
*
* We maintain a table mapping StatusNet notices to Facebook items
*
* @see Schema
* @see ColumnDef
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onCheckSchema()
{
$schema = Schema::get();
$schema->ensureTable('notice_to_item', Notice_to_item::schemaDef());
return true;
}
/*
* Does this $action need the Facebook JavaScripts?
*/
function needsScripts($action)
{
static $needy = array(
'FacebookloginAction',
'FacebookfinishloginAction',
'FacebookadminpanelAction',
'FacebooksettingsAction'
);
if (in_array(get_class($action), $needy)) {
return true;
} else {
return false;
}
}
/**
* Map URLs to actions
*
* @param Net_URL_Mapper $m path-to-action mapper
*
* @return boolean hook value; true means continue processing, false means stop.
*/
function onRouterInitialized($m)
{
// Always add the admin panel route
$m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
// Only add these routes if an application has been setup on
// Facebook for the plugin to use.
if ($this->hasApplication()) {
$m->connect(
'main/facebooklogin',
array('action' => 'facebooklogin')
);
$m->connect(
'main/facebookfinishlogin',
array('action' => 'facebookfinishlogin')
);
$m->connect(
'settings/facebook',
array('action' => 'facebooksettings')
);
$m->connect(
'facebook/deauthorize',
array('action' => 'facebookdeauthorize')
);
}
return true;
}
/*
* Add a login tab for Facebook, but only if there's a Facebook
* application defined for the plugin to use.
*
* @param Action &action the current action
*
* @return void
*/
function onEndLoginGroupNav(&$action)
{
$action_name = $action->trimmed('action');
if ($this->hasApplication()) {
$action->menuItem(
common_local_url('facebooklogin'),
_m('MENU', 'Facebook'),
// TRANS: Tooltip for menu item "Facebook".
_m('Login or register using Facebook'),
'facebooklogin' === $action_name
);
}
return true;
}
/**
* Add a Facebook tab to the admin panels
*
* @param Widget $nav Admin panel nav
*
* @return boolean hook value
*/
function onEndAdminPanelNav($nav)
{
if (AdminPanelAction::canAdmin('facebook')) {
$action_name = $nav->action->trimmed('action');
$nav->out->menuItem(
common_local_url('facebookadminpanel'),
// TRANS: Menu item.
_m('MENU','Facebook'),
// TRANS: Tooltip for menu item "Facebook".
_m('Facebook integration configuration'),
$action_name == 'facebookadminpanel',
'nav_facebook_admin_panel'
);
}
return true;
}
/*
* Add a tab for user-level Facebook settings if the user
* has a link to Facebook
*
* @param Action &action the current action
*
* @return void
*/
function onEndConnectSettingsNav(&$action)
{
if ($this->hasApplication()) {
$action_name = $action->trimmed('action');
// CurrentUserDesignAction stores the current user in $cur
$user = $action->getCurrentUser();
$flink = null;
if (!empty($user)) {
$flink = Foreign_link::getByUserID(
$user->id,
FACEBOOK_SERVICE
);
}
if (!empty($flink)) {
$action->menuItem(
common_local_url('facebooksettings'),
// TRANS: Menu item tab.
_m('MENU','Facebook'),
// TRANS: Tooltip for menu item "Facebook".
_m('Facebook settings'),
$action_name === 'facebooksettings'
);
}
}
}
/*
* Is there a Facebook application for the plugin to use?
*
* Checks to see if a Facebook application ID and secret
* have been configured and a valid Facebook API client
* object exists.
*
*/
function hasApplication()
{
if (!empty($this->facebook)) {
$appId = $this->facebook->getAppId();
$secret = $this->facebook->getApiSecret();
if (!empty($appId) && !empty($secret)) {
return true;
}
}
return false;
}
/*
* Output a Facebook div for the Facebook JavaSsript SDK to use
*
* @param Action $action the current action
*
*/
function onStartShowHeader($action)
{
// output <div id="fb-root"></div> as close to <body> as possible
$action->element('div', array('id' => 'fb-root'));
return true;
}
/*
* Load the Facebook JavaScript SDK on pages that need them.
*
* @param Action $action the current action
*
*/
function onEndShowScripts($action)
{
if ($this->needsScripts($action)) {
$action->script('https://connect.facebook.net/en_US/all.js');
$script = <<<ENDOFSCRIPT
FB.init({appId: %1\$s, session: %2\$s, status: true, cookie: true, xfbml: true});
$('#facebook_button').bind('click', function(event) {
event.preventDefault();
FB.login(function(response) {
if (response.session && response.perms) {
window.location.href = '%3\$s';
} else {
// NOP (user cancelled login)
}
}, {perms:'read_stream,publish_stream,offline_access,user_status,user_location,user_website,email'});
});
ENDOFSCRIPT;
$action->inlineScript(
sprintf($script,
json_encode($this->facebook->getAppId()),
json_encode($this->facebook->getSession()),
common_local_url('facebookfinishlogin')
)
);
}
}
/*
* Log the user out of Facebook, per the Facebook authentication guide
*
* @param Action action the current action
*/
function onEndLogout($action)
{
if ($this->hasApplication()) {
$session = $this->facebook->getSession();
$fbuser = null;
$fbuid = null;
if ($session) {
try {
$fbuid = $this->facebook->getUser();
$fbuser = $this->facebook->api('/me');
} catch (FacebookApiException $e) {
common_log(LOG_ERROR, $e, __FILE__);
}
}
if (!empty($fbuser)) {
$logoutUrl = $this->facebook->getLogoutUrl(
array('next' => common_local_url('public'))
);
common_log(
LOG_INFO,
sprintf(
"Logging user out of Facebook (fbuid = %s)",
$fbuid
),
__FILE__
);
common_debug("LOGOUT URL = $logoutUrl");
common_redirect($logoutUrl, 303);
}
}
}
/*
* Add fbml namespace to our HTML, so Facebook's JavaScript SDK can parse
* and render XFBML tags
*
* @param Action $action the current action
* @param array $attrs array of attributes for the HTML tag
*
* @return nothing
*/
function onStartHtmlElement($action, $attrs) {
if ($this->needsScripts($action)) {
$attrs = array_merge(
$attrs,
array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
);
}
return true;
}
/**
* Add a Facebook queue item for each notice
*
* @param Notice $notice the notice
* @param array &$transports the list of transports (queues)
*
* @return boolean hook return
*/
function onStartEnqueueNotice($notice, &$transports)
{
if (self::hasApplication() && $notice->isLocal()) {
array_push($transports, 'facebook');
}
return true;
}
/**
* Register Facebook notice queue handler
*
* @param QueueManager $manager
*
* @return boolean hook return
*/
function onEndInitializeQueueManager($manager)
{
if (self::hasApplication()) {
$manager->connect('facebook', 'FacebookQueueHandler');
}
return true;
}
/*
* Use SSL for Facebook stuff
*
* @param string $action name
* @param boolean $ssl outval to force SSL
* @return mixed hook return value
*/
function onSensitiveAction($action, &$ssl)
{
$sensitive = array(
'facebookadminpanel',
'facebooksettings',
'facebooklogin',
'facebookfinishlogin'
);
if (in_array($action, $sensitive)) {
$ssl = true;
return false;
} else {
return true;
}
}
/**
* If a notice gets deleted, remove the Notice_to_item mapping and
* delete the item on Facebook
*
* @param User $user The user doing the deleting
* @param Notice $notice The notice getting deleted
*
* @return boolean hook value
*/
function onStartDeleteOwnNotice(User $user, Notice $notice)
{
$client = new Facebookclient($notice);
$client->streamRemove();
return true;
}
/**
* Notify remote users when their notices get favorited.
*
* @param Profile or User $profile of local user doing the faving
* @param Notice $notice being favored
* @return hook return value
*/
function onEndFavorNotice(Profile $profile, Notice $notice)
{
$client = new Facebookclient($notice);
$client->like();
return true;
}
/**
* Notify remote users when their notices get de-favorited.
*
* @param Profile $profile Profile person doing the de-faving
* @param Notice $notice Notice being favored
*
* @return hook return value
*/
function onEndDisfavorNotice(Profile $profile, Notice $notice)
{
$client = new Facebookclient($notice);
$client->unLike();
return true;
}
/*
* Add version info for this plugin
*
* @param array &$versions plugin version descriptions
*/
function onPluginVersion(&$versions)
{
$versions[] = array(
'name' => 'Facebook Single-Sign-On',
'version' => STATUSNET_VERSION,
'author' => 'Craig Andrews, Zach Copley',
'homepage' => 'http://status.net/wiki/Plugin:FacebookBridge',
'rawdescription' =>
_m('A plugin for integrating StatusNet with Facebook.')
);
return true;
}
}

View File

@ -0,0 +1,212 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Facebook integration administration panel
*
* 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 2010 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')) {
exit(1);
}
/**
* Administer global Facebook integration settings
*
* @category Admin
* @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 FacebookadminpanelAction extends AdminPanelAction
{
/**
* Returns the page title
*
* @return string page title
*/
function title()
{
return _m('Facebook');
}
/**
* Instructions for using this form.
*
* @return string instructions
*/
function getInstructions()
{
return _m('Facebook integration settings');
}
/**
* Show the Facebook admin panel form
*
* @return void
*/
function showForm()
{
$form = new FacebookAdminPanelForm($this);
$form->show();
return;
}
/**
* Save settings from the form
*
* @return void
*/
function saveSettings()
{
static $settings = array(
'facebook' => array('appid', 'secret'),
);
$values = array();
foreach ($settings as $section => $parts) {
foreach ($parts as $setting) {
$values[$section][$setting]
= $this->trimmed($setting);
}
}
// This throws an exception on validation errors
$this->validate($values);
// assert(all values are valid);
$config = new Config();
$config->query('BEGIN');
foreach ($settings as $section => $parts) {
foreach ($parts as $setting) {
Config::save($section, $setting, $values[$section][$setting]);
}
}
$config->query('COMMIT');
return;
}
function validate(&$values)
{
// appId, key and secret (can't be too long)
if (mb_strlen($values['facebook']['appid']) > 255) {
$this->clientError(
_m("Invalid Facebook ID. Max length is 255 characters.")
);
}
if (mb_strlen($values['facebook']['secret']) > 255) {
$this->clientError(
_m("Invalid Facebook secret. Max length is 255 characters.")
);
}
}
}
class FacebookAdminPanelForm extends AdminForm
{
/**
* ID of the form
*
* @return int ID of the form
*/
function id()
{
return 'facebookadminpanel';
}
/**
* class of the form
*
* @return string class of the form
*/
function formClass()
{
return 'form_settings';
}
/**
* Action of the form
*
* @return string URL of the action
*/
function action()
{
return common_local_url('facebookadminpanel');
}
/**
* Data elements of the form
*
* @return void
*/
function formData()
{
$this->out->elementStart(
'fieldset',
array('id' => 'settings_facebook-application')
);
$this->out->element('legend', null, _m('Facebook application settings'));
$this->out->elementStart('ul', 'form_data');
$this->li();
$this->input(
'appid',
_m('Application ID'),
_m('ID of your Facebook application'),
'facebook'
);
$this->unli();
$this->li();
$this->input(
'secret',
_m('Secret'),
_m('Application secret'),
'facebook'
);
$this->unli();
$this->out->elementEnd('ul');
$this->out->elementEnd('fieldset');
}
/**
* Action elements
*
* @return void
*/
function formActions()
{
$this->out->submit('submit', _m('Save'), 'submit', null, _m('Save Facebook settings'));
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* An action that handles deauthorize callbacks from Facebook
*
* PHP version 5
*
* 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 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
/*
* Action class for handling deauthorize callbacks from Facebook. If the user
* doesn't have a password let her know she'll need to contact the site
* admin to get back into her account (if possible).
*/
class FacebookdeauthorizeAction extends Action
{
private $facebook;
/**
* For initializing members of the class.
*
* @param array $args misc. arguments
*
* @return boolean true
*/
function prepare($args)
{
$this->facebook = Facebookclient::getFacebook();
return true;
}
/**
* Handler method
*
* @param array $args is ignored since it's now passed in in prepare()
*/
function handle($args)
{
parent::handle($args);
$data = $this->facebook->getSignedRequest();
if (isset($data['user_id'])) {
$fbuid = $data['user_id'];
$flink = Foreign_link::getByForeignID($fbuid, FACEBOOK_SERVICE);
$user = $flink->getUser();
// Remove the link to Facebook
$result = $flink->delete();
if (!$result) {
common_log_db_error($flink, 'DELETE', __FILE__);
common_log(
LOG_WARNING,
sprintf(
'Unable to delete Facebook foreign link '
. 'for %s (%d), fbuid %d',
$user->nickname,
$user->id,
$fbuid
),
__FILE__
);
return;
}
common_log(
LOG_INFO,
sprintf(
'Facebook callback: %s (%d), fbuid %d has deauthorized '
. 'the Facebook application.',
$user->nickname,
$user->id,
$fbuid
),
__FILE__
);
// Warn the user about being locked out of their account
// if we can.
if (empty($user->password) && !empty($user->email)) {
Facebookclient::emailWarn($user);
} else {
common_log(
LOG_WARNING,
sprintf(
'%s (%d), fbuid %d has deauthorized his/her Facebook '
. 'connection but hasn\'t set a password so s/he '
. 'is locked out.',
$user->nickname,
$user->id,
$fbuid
),
__FILE__
);
}
} else {
if (!empty($data)) {
common_log(
LOG_WARNING,
sprintf(
'Facebook called the deauthorize callback '
. ' but didn\'t provide a user ID.'
),
__FILE__
);
} else {
// It probably wasn't Facebook that hit this action,
// so redirect to the public timeline
common_redirect(common_local_url('public'), 303);
}
}
}
}

View File

@ -0,0 +1,688 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Login or register a local user based on a Facebook user
*
* 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 2010 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')) {
exit(1);
}
class FacebookfinishloginAction extends Action
{
private $facebook = null; // Facebook client
private $fbuid = null; // Facebook user ID
private $fbuser = null; // Facebook user object (JSON)
function prepare($args) {
parent::prepare($args);
$this->facebook = new Facebook(
array(
'appId' => common_config('facebook', 'appid'),
'secret' => common_config('facebook', 'secret'),
'cookie' => true,
)
);
// Check for a Facebook user session
$session = $this->facebook->getSession();
$me = null;
if ($session) {
try {
$this->fbuid = $this->facebook->getUser();
$this->fbuser = $this->facebook->api('/me');
} catch (FacebookApiException $e) {
common_log(LOG_ERROR, $e, __FILE__);
}
}
if (!empty($this->fbuser)) {
// OKAY, all is well... proceed to register
common_debug("Found a valid Facebook user.", __FILE__);
} else {
// This shouldn't happen in the regular course of things
list($proxy, $ip) = common_client_ip();
common_log(
LOG_WARNING,
sprintf(
'Failed Facebook authentication attempt, proxy = %s, ip = %s.',
$proxy,
$ip
),
__FILE__
);
$this->clientError(
_m('You must be logged into Facebook to register a local account using Facebook.')
);
}
return true;
}
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
// User is already logged in, are her accounts already linked?
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if (!empty($flink)) {
// User already has a linked Facebook account and shouldn't be here!
common_debug(
sprintf(
'There\'s already a local user %d linked with Facebook user %s.',
$flink->user_id,
$this->fbuid
)
);
$this->clientError(
_m('There is already a local account linked with that Facebook account.')
);
} else {
// Possibly reconnect an existing account
$this->connectUser();
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$this->handlePost();
} else {
$this->tryLogin();
}
}
function handlePost()
{
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(
_m('There was a problem with your session token. Try again, please.')
);
return;
}
if ($this->arg('create')) {
if (!$this->boolean('license')) {
$this->showForm(
_m('You can\'t register if you don\'t agree to the license.'),
$this->trimmed('newname')
);
return;
}
// We has a valid Facebook session and the Facebook user has
// agreed to the SN license, so create a new user
$this->createNewUser();
} else if ($this->arg('connect')) {
$this->connectNewUser();
} else {
$this->showForm(
_m('An unknown error has occured.'),
$this->trimmed('newname')
);
}
}
function showPageNotice()
{
if ($this->error) {
$this->element('div', array('class' => 'error'), $this->error);
} else {
$this->element(
'div', 'instructions',
// TRANS: %s is the site name.
sprintf(
_m('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 local account, or connect with an existing local account.'),
common_config('site', 'name')
)
);
}
}
function title()
{
// TRANS: Page title.
return _m('Facebook Setup');
}
function showForm($error=null, $username=null)
{
$this->error = $error;
$this->username = $username;
$this->showPage();
}
function showPage()
{
parent::showPage();
}
/**
* @fixme much of this duplicates core code, which is very fragile.
* Should probably be replaced with an extensible mini version of
* the core registration form.
*/
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('facebookfinishlogin')));
$this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
// TRANS: Legend.
$this->element('legend', null, _m('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'));
// TRANS: %s is the name of the license used by the user for their status updates.
$message = _m('My text and files are available under %s ' .
'except this private data: password, ' .
'email address, IM address, and phone number.');
$link = '<a href="' .
htmlspecialchars(common_config('license', 'url')) .
'">' .
htmlspecialchars(common_config('license', 'title')) .
'</a>';
$this->raw(sprintf(htmlspecialchars($message), $link));
$this->elementEnd('label');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->element('legend', null,
// TRANS: Legend.
_m('Create new account'));
$this->element('p', null,
_m('Create a new user with this nickname.'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
// TRANS: Field label.
$this->input('newname', _m('New nickname'),
($this->username) ? $this->username : '',
_m('1-64 lowercase letters or numbers, no punctuation or spaces'));
$this->elementEnd('li');
$this->elementEnd('ul');
// TRANS: Submit button.
$this->submit('create', _m('BUTTON','Create'));
$this->elementEnd('fieldset');
$this->elementStart('fieldset');
// TRANS: Legend.
$this->element('legend', null,
_m('Connect existing account'));
$this->element('p', null,
_m('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');
// TRANS: Field label.
$this->input('nickname', _m('Existing nickname'));
$this->elementEnd('li');
$this->elementStart('li');
$this->password('password', _m('Password'));
$this->elementEnd('li');
$this->elementEnd('ul');
// TRANS: Submit button.
$this->submit('connect', _m('BUTTON','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')) {
// TRANS: Client error trying to register with registrations not allowed.
$this->clientError(_m('Registration not allowed.'));
return;
}
$invite = null;
if (common_config('site', 'inviteonly')) {
$code = $_SESSION['invitecode'];
if (empty($code)) {
// TRANS: Client error trying to register with registrations 'invite only'.
$this->clientError(_m('Registration not allowed.'));
return;
}
$invite = Invitation::staticGet($code);
if (empty($invite)) {
// TRANS: Client error trying to register with an invalid invitation code.
$this->clientError(_m('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(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
return;
}
if (!User::allowed_nickname($nickname)) {
$this->showForm(_m('Nickname not allowed.'));
return;
}
if (User::staticGet('nickname', $nickname)) {
$this->showForm(_m('Nickname already in use. Try another one.'));
return;
}
$args = array(
'nickname' => $nickname,
'fullname' => $this->fbuser['first_name']
. ' ' . $this->fbuser['last_name'],
'homepage' => $this->fbuser['website'],
'bio' => $this->fbuser['about'],
'location' => $this->fbuser['location']['name']
);
// It's possible that the email address is already in our
// DB. It's a unique key, so we need to check
if ($this->isNewEmail($this->fbuser['email'])) {
$args['email'] = $this->fbuser['email'];
$args['email_confirmed'] = true;
}
if (!empty($invite)) {
$args['code'] = $invite->code;
}
$user = User::register($args);
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_m('Error connecting user to Facebook.'));
return;
}
// Add a Foreign_user record
Facebookclient::addFacebookUser($this->fbuser);
$this->setAvatar($user);
common_set_user($user);
common_real_login(true);
common_log(
LOG_INFO,
sprintf(
'Registered new user %s (%d) from Facebook user %s, (fbuid %d)',
$user->nickname,
$user->id,
$this->fbuser['name'],
$this->fbuid
),
__FILE__
);
$this->goHome($user->nickname);
}
/*
* Attempt to download the user's Facebook picture and create a
* StatusNet avatar for the new user.
*/
function setAvatar($user)
{
$picUrl = sprintf(
'http://graph.facebook.com/%s/picture?type=large',
$this->fbuid
);
// fetch the picture from Facebook
$client = new HTTPClient();
// fetch the actual picture
$response = $client->get($picUrl);
if ($response->isOk()) {
$finalUrl = $client->getUrl();
// Make sure the filename is unique becuase it's possible for a user
// to deauthorize our app, and then come back in as a new user but
// have the same Facebook picture (avatar URLs have a unique index
// and their URLs are based on the filenames).
$filename = 'facebook-' . common_good_rand(4) . '-'
. substr(strrchr($finalUrl, '/'), 1);
$ok = file_put_contents(
Avatar::path($filename),
$response->getBody()
);
if (!$ok) {
common_log(
LOG_WARNING,
sprintf(
'Couldn\'t save Facebook avatar %s',
$tmp
),
__FILE__
);
} else {
// save it as an avatar
$profile = $user->getProfile();
if ($profile->setOriginal($filename)) {
common_log(
LOG_INFO,
sprintf(
'Saved avatar for %s (%d) from Facebook picture for '
. '%s (fbuid %d), filename = %s',
$user->nickname,
$user->id,
$this->fbuser['name'],
$this->fbuid,
$filename
),
__FILE__
);
}
}
}
}
function connectNewUser()
{
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
if (!common_check_user($nickname, $password)) {
$this->showForm(_m('Invalid username or password.'));
return;
}
$user = User::staticGet('nickname', $nickname);
if (!empty($user)) {
common_debug(
sprintf(
'Found a legit user to connect to Facebook: %s (%d)',
$user->nickname,
$user->id
),
__FILE__
);
}
$this->tryLinkUser($user);
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
function connectUser()
{
$user = common_current_user();
$this->tryLinkUser($user);
common_redirect(common_local_url('facebookfinishlogin'), 303);
}
function tryLinkUser($user)
{
$result = $this->flinkUser($user->id, $this->fbuid);
if (empty($result)) {
$this->serverError(_m('Error connecting user to Facebook.'));
return;
}
common_debug(
sprintf(
'Connected Facebook user %s (fbuid %d) to local user %s (%d)',
$this->fbuser['name'],
$this->fbuid,
$user->nickname,
$user->id
),
__FILE__
);
}
function tryLogin()
{
common_debug(
sprintf(
'Trying login for Facebook user %s',
$this->fbuid
),
__FILE__
);
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
if (!empty($flink)) {
$user = $flink->getUser();
if (!empty($user)) {
common_log(
LOG_INFO,
sprintf(
'Logged in Facebook user %s as user %d (%s)',
$this->fbuid,
$user->nickname,
$user->id
),
__FILE__
);
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
} else {
common_debug(
sprintf(
'No flink found for fbuid: %s - new user',
$this->fbuid
),
__FILE__
);
$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_SERVICE;
// Pull the access token from the Facebook cookies
$flink->credentials = $this->facebook->getAccessToken();
$flink->created = common_sql_now();
$flink_id = $flink->insert();
return $flink_id;
}
function bestNewNickname()
{
if (!empty($this->fbuser['name'])) {
$nickname = $this->nicknamize($this->fbuser['name']);
if ($this->isNewNickname($nickname)) {
return $nickname;
}
}
// Try the full name
$fullname = trim($this->fbuser['first_name'] .
' ' . $this->fbuser['last_name']);
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);
}
/*
* Is the desired nickname already taken?
*
* @return boolean result
*/
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;
}
/*
* Do we already have a user record with this email?
* (emails have to be unique but they can change)
*
* @param string $email the email address to check
*
* @return boolean result
*/
function isNewEmail($email)
{
// we shouldn't have to validate the format
$result = User::staticGet('email', $email);
if (empty($result)) {
common_debug("XXXXXXXXXXXXXXXXXX We've never seen this email before!!!");
return true;
}
common_debug("XXXXXXXXXXXXXXXXXX dupe email address!!!!");
return false;
}
}

View File

@ -0,0 +1,122 @@
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, Inc.
*
* An action for logging in with Facebook
*
* PHP version 5
*
* 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 2010 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET')) {
exit(1);
}
class FacebookloginAction extends Action
{
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
$this->clientError(_m('Already logged in.'));
} else {
$this->showPage();
}
}
function getInstructions()
{
// TRANS: Instructions.
return _m('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()
{
// TRANS: Page title.
return _m('Login with Facebook');
}
function showContent() {
$this->elementStart('fieldset');
$facebook = Facebookclient::getFacebook();
// Degrade to plain link if JavaScript is not available
$this->elementStart(
'a',
array(
'href' => $facebook->getLoginUrl(
array(
'next' => common_local_url('facebookfinishlogin'),
'cancel' => common_local_url('facebooklogin')
)
),
'id' => 'facebook_button'
)
);
$attrs = array(
'src' => common_path(
'plugins/FacebookBridge/images/login-button.png',
true
),
'alt' => 'Login with Facebook',
'title' => 'Login with Facebook'
);
$this->element('img', $attrs);
$this->elementEnd('a');
/*
$this->element('div', array('id' => 'fb-root'));
$this->script(
sprintf(
'http://connect.facebook.net/en_US/all.js#appId=%s&xfbml=1',
common_config('facebook', 'appid')
)
);
$this->element('fb:facepile', array('max-rows' => '2', 'width' =>'300'));
*/
$this->elementEnd('fieldset');
}
function showLocalNav()
{
$nav = new LoginGroupNav($this);
$nav->show();
}
}

View File

@ -0,0 +1,271 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Edit user settings for Facebook
*
* 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 2010 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')) {
exit(1);
}
/**
* Edit user settings for Facebook
*
* @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/
*
* @see SettingsAction
*/
class FacebooksettingsAction extends ConnectSettingsAction {
private $facebook; // Facebook PHP-SDK client obj
private $flink;
private $user;
/**
* For initializing members of the class.
*
* @param array $argarray misc. arguments
*
* @return boolean true
*/
function prepare($args) {
parent::prepare($args);
$this->facebook = new Facebook(
array(
'appId' => common_config('facebook', 'appid'),
'secret' => common_config('facebook', 'secret'),
'cookie' => true,
)
);
$this->user = common_current_user();
$this->flink = Foreign_link::getByUserID(
$this->user->id,
FACEBOOK_SERVICE
);
return true;
}
/*
* Check the sessions token and dispatch
*/
function handlePost($args) {
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(
_m('There was a problem with your session token. Try again, please.')
);
return;
}
if ($this->arg('save')) {
$this->saveSettings();
} else if ($this->arg('disconnect')) {
$this->disconnect();
}
}
/**
* Returns the page title
*
* @return string page title
*/
function title() {
// TRANS: Page title for Facebook settings.
return _m('Facebook settings');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions() {
return _('Facebook settings');
}
/*
* Show the settings form if he/she has a link to Facebook
*
* @return void
*/
function showContent() {
if (!empty($this->flink)) {
$this->elementStart(
'form',
array(
'method' => 'post',
'id' => 'form_settings_facebook',
'class' => 'form_settings',
'action' => common_local_url('facebooksettings')
)
);
$this->hidden('token', common_session_token());
$this->element('p', 'form_note', _m('Connected Facebook user'));
$this->elementStart('p', array('class' => 'facebook-user-display'));
$this->element(
'fb:profile-pic',
array(
'uid' => $this->flink->foreign_id,
'size' => 'small',
'linked' => 'true',
'facebook-logo' => 'true'
)
);
$this->element(
'fb:name',
array('uid' => $this->flink->foreign_id, 'useyou' => 'false')
);
$this->elementEnd('p');
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->checkbox(
'noticesync',
_m('Publish my notices to Facebook.'),
($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND) : true
);
$this->elementEnd('li');
$this->elementStart('li');
$this->checkbox(
'replysync',
_m('Send "@" replies to Facebook.'),
($this->flink) ? ($this->flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true
);
$this->elementEnd('li');
$this->elementStart('li');
// TRANS: Submit button to save synchronisation settings.
$this->submit('save', _m('BUTTON', 'Save'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementStart('fieldset');
// TRANS: Legend.
$this->element('legend', null, _m('Disconnect my account from Facebook'));
if (empty($this->user->password)) {
$this->elementStart('p', array('class' => 'form_guide'));
$msg = sprintf(
_m(
'Disconnecting your Faceboook would make it impossible to '
. 'log in! Please [set a password](%s) first.'
),
common_local_url('passwordsettings')
);
$this->raw(common_markup_to_html($msg));
$this->elementEnd('p');
} else {
$msg = sprintf(
_m(
'Keep your %1$s account but disconnect from Facebook. ' .
'You\'ll use your 1%$s password to log in.'
),
common_config('site', 'name')
);
// TRANS: Submit button.
$this->submit('disconnect', _m('BUTTON', 'Disconnect'));
}
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
}
/*
* Save the user's Facebook settings
*
* @return void
*/
function saveSettings() {
$noticesync = $this->boolean('noticesync');
$replysync = $this->boolean('replysync');
$original = clone($this->flink);
$this->flink->set_flags($noticesync, false, $replysync, false);
$result = $this->flink->update($original);
if ($result === false) {
$this->showForm(_m('There was a problem saving your sync preferences.'));
} else {
// TRANS: Confirmation that synchronisation settings have been saved into the system.
$this->showForm(_m('Sync preferences saved.'), true);
}
}
/*
* Disconnect the user's Facebook account - deletes the Foreign_link
* and shows the user a success message if all goes well.
*/
function disconnect() {
$result = $this->flink->delete();
$this->flink = null;
if ($result === false) {
common_log_db_error($user, 'DELETE', __FILE__);
$this->serverError(_m('Couldn\'t delete link to Facebook.'));
return;
}
$this->showForm(_m('You have disconnected from Facebook.'), true);
}
}

View File

@ -0,0 +1,190 @@
<?php
/**
* Data class for storing notice-to-Facebook-item mappings
*
* PHP version 5
*
* @category Data
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2010, StatusNet, 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')) {
exit(1);
}
require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
/**
* Data class for mapping notices to Facebook stream items
*
* Note that notice_id is unique only within a single database; if you
* want to share this data for some reason, get the notice's URI and use
* that instead, since it's universally unique.
*
* @category Action
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
* @link http://status.net/
*
* @see DB_DataObject
*/
class Notice_to_item extends Memcached_DataObject
{
public $__table = 'notice_to_item'; // table name
public $notice_id; // int(4) primary_key not_null
public $item_id; // varchar(255) not null
public $created; // datetime
/**
* Get an instance by key
*
* This is a utility method to get a single instance with a given key value.
*
* @param string $k Key to use to lookup
* @param mixed $v Value to lookup
*
* @return Notice_to_item object found, or null for no hits
*
*/
function staticGet($k, $v=null)
{
return Memcached_DataObject::staticGet('Notice_to_item', $k, $v);
}
/**
* return table definition for DB_DataObject
*
* DB_DataObject needs to know something about the table to manipulate
* instances. This method provides all the DB_DataObject needs to know.
*
* @return array array of column definitions
*/
function table()
{
return array(
'notice_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
'item_id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL
);
}
static function schemaDef()
{
return array(
new ColumnDef('notice_id', 'integer', null, false, 'PRI'),
new ColumnDef('item_id', 'varchar', 255, false, 'UNI'),
new ColumnDef('created', 'datetime', null, false)
);
}
/**
* return key definitions for DB_DataObject
*
* DB_DataObject needs to know about keys that the table has, since it
* won't appear in StatusNet's own keys list. In most cases, this will
* simply reference your keyTypes() function.
*
* @return array list of key field names
*/
function keys()
{
return array_keys($this->keyTypes());
}
/**
* return key definitions for Memcached_DataObject
*
* Our caching system uses the same key definitions, but uses a different
* method to get them. This key information is used to store and clear
* cached data, so be sure to list any key that will be used for static
* lookups.
*
* @return array associative array of key definitions, field name to type:
* 'K' for primary key: for compound keys, add an entry for each component;
* 'U' for unique keys: compound keys are not well supported here.
*/
function keyTypes()
{
return array('notice_id' => 'K', 'item_id' => 'U');
}
/**
* Magic formula for non-autoincrementing integer primary keys
*
* If a table has a single integer column as its primary key, DB_DataObject
* assumes that the column is auto-incrementing and makes a sequence table
* to do this incrementation. Since we don't need this for our class, we
* overload this method and return the magic formula that DB_DataObject needs.
*
* @return array magic three-false array that stops auto-incrementing.
*/
function sequenceKey()
{
return array(false, false, false);
}
/**
* Save a mapping between a notice and a Facebook item
*
* @param integer $notice_id ID of the notice in StatusNet
* @param integer $item_id ID of the stream item on Facebook
*
* @return Notice_to_item new object for this value
*/
static function saveNew($notice_id, $item_id)
{
$n2i = Notice_to_item::staticGet('notice_id', $notice_id);
if (!empty($n2i)) {
return $n2i;
}
$n2i = Notice_to_item::staticGet('item_id', $item_id);
if (!empty($n2i)) {
return $n2i;
}
common_debug(
"Mapping notice {$notice_id} to Facebook item {$item_id}",
__FILE__
);
$n2i = new Notice_to_item();
$n2i->notice_id = $notice_id;
$n2i->item_id = $item_id;
$n2i->created = common_sql_now();
$n2i->insert();
return $n2i;
}
}

View File

@ -0,0 +1,963 @@
<?php
if (!function_exists('curl_init')) {
throw new Exception('Facebook needs the CURL PHP extension.');
}
if (!function_exists('json_decode')) {
throw new Exception('Facebook needs the JSON PHP extension.');
}
/**
* Thrown when an API call returns an exception.
*
* @author Naitik Shah <naitik@facebook.com>
*/
class FacebookApiException extends Exception
{
/**
* The result from the API server that represents the exception information.
*/
protected $result;
/**
* Make a new API Exception with the given result.
*
* @param Array $result the result from the API server
*/
public function __construct($result) {
$this->result = $result;
$code = isset($result['error_code']) ? $result['error_code'] : 0;
if (isset($result['error_description'])) {
// OAuth 2.0 Draft 10 style
$msg = $result['error_description'];
} else if (isset($result['error']) && is_array($result['error'])) {
// OAuth 2.0 Draft 00 style
$msg = $result['error']['message'];
} else if (isset($result['error_msg'])) {
// Rest server style
$msg = $result['error_msg'];
} else {
$msg = 'Unknown Error. Check getResult()';
}
parent::__construct($msg, $code);
}
/**
* Return the associated result object returned by the API server.
*
* @returns Array the result from the API server
*/
public function getResult() {
return $this->result;
}
/**
* Returns the associated type for the error. This will default to
* 'Exception' when a type is not available.
*
* @return String
*/
public function getType() {
if (isset($this->result['error'])) {
$error = $this->result['error'];
if (is_string($error)) {
// OAuth 2.0 Draft 10 style
return $error;
} else if (is_array($error)) {
// OAuth 2.0 Draft 00 style
if (isset($error['type'])) {
return $error['type'];
}
}
}
return 'Exception';
}
/**
* To make debugging easier.
*
* @returns String the string representation of the error
*/
public function __toString() {
$str = $this->getType() . ': ';
if ($this->code != 0) {
$str .= $this->code . ': ';
}
return $str . $this->message;
}
}
/**
* Provides access to the Facebook Platform.
*
* @author Naitik Shah <naitik@facebook.com>
*/
class Facebook
{
/**
* Version.
*/
const VERSION = '2.1.2';
/**
* Default options for curl.
*/
public static $CURL_OPTS = array(
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_USERAGENT => 'facebook-php-2.0',
);
/**
* List of query parameters that get automatically dropped when rebuilding
* the current URL.
*/
protected static $DROP_QUERY_PARAMS = array(
'session',
'signed_request',
);
/**
* Maps aliases to Facebook domains.
*/
public static $DOMAIN_MAP = array(
'api' => 'https://api.facebook.com/',
'api_read' => 'https://api-read.facebook.com/',
'graph' => 'https://graph.facebook.com/',
'www' => 'https://www.facebook.com/',
);
/**
* The Application ID.
*/
protected $appId;
/**
* The Application API Secret.
*/
protected $apiSecret;
/**
* The active user session, if one is available.
*/
protected $session;
/**
* The data from the signed_request token.
*/
protected $signedRequest;
/**
* Indicates that we already loaded the session as best as we could.
*/
protected $sessionLoaded = false;
/**
* Indicates if Cookie support should be enabled.
*/
protected $cookieSupport = false;
/**
* Base domain for the Cookie.
*/
protected $baseDomain = '';
/**
* Indicates if the CURL based @ syntax for file uploads is enabled.
*/
protected $fileUploadSupport = false;
/**
* Initialize a Facebook Application.
*
* The configuration:
* - appId: the application ID
* - secret: the application secret
* - cookie: (optional) boolean true to enable cookie support
* - domain: (optional) domain for the cookie
* - fileUpload: (optional) boolean indicating if file uploads are enabled
*
* @param Array $config the application configuration
*/
public function __construct($config) {
$this->setAppId($config['appId']);
$this->setApiSecret($config['secret']);
if (isset($config['cookie'])) {
$this->setCookieSupport($config['cookie']);
}
if (isset($config['domain'])) {
$this->setBaseDomain($config['domain']);
}
if (isset($config['fileUpload'])) {
$this->setFileUploadSupport($config['fileUpload']);
}
}
/**
* Set the Application ID.
*
* @param String $appId the Application ID
*/
public function setAppId($appId) {
$this->appId = $appId;
return $this;
}
/**
* Get the Application ID.
*
* @return String the Application ID
*/
public function getAppId() {
return $this->appId;
}
/**
* Set the API Secret.
*
* @param String $appId the API Secret
*/
public function setApiSecret($apiSecret) {
$this->apiSecret = $apiSecret;
return $this;
}
/**
* Get the API Secret.
*
* @return String the API Secret
*/
public function getApiSecret() {
return $this->apiSecret;
}
/**
* Set the Cookie Support status.
*
* @param Boolean $cookieSupport the Cookie Support status
*/
public function setCookieSupport($cookieSupport) {
$this->cookieSupport = $cookieSupport;
return $this;
}
/**
* Get the Cookie Support status.
*
* @return Boolean the Cookie Support status
*/
public function useCookieSupport() {
return $this->cookieSupport;
}
/**
* Set the base domain for the Cookie.
*
* @param String $domain the base domain
*/
public function setBaseDomain($domain) {
$this->baseDomain = $domain;
return $this;
}
/**
* Get the base domain for the Cookie.
*
* @return String the base domain
*/
public function getBaseDomain() {
return $this->baseDomain;
}
/**
* Set the file upload support status.
*
* @param String $domain the base domain
*/
public function setFileUploadSupport($fileUploadSupport) {
$this->fileUploadSupport = $fileUploadSupport;
return $this;
}
/**
* Get the file upload support status.
*
* @return String the base domain
*/
public function useFileUploadSupport() {
return $this->fileUploadSupport;
}
/**
* Get the data from a signed_request token
*
* @return String the base domain
*/
public function getSignedRequest() {
if (!$this->signedRequest) {
if (isset($_REQUEST['signed_request'])) {
$this->signedRequest = $this->parseSignedRequest(
$_REQUEST['signed_request']);
}
}
return $this->signedRequest;
}
/**
* Set the Session.
*
* @param Array $session the session
* @param Boolean $write_cookie indicate if a cookie should be written. this
* value is ignored if cookie support has been disabled.
*/
public function setSession($session=null, $write_cookie=true) {
$session = $this->validateSessionObject($session);
$this->sessionLoaded = true;
$this->session = $session;
if ($write_cookie) {
$this->setCookieFromSession($session);
}
return $this;
}
/**
* Get the session object. This will automatically look for a signed session
* sent via the signed_request, Cookie or Query Parameters if needed.
*
* @return Array the session
*/
public function getSession() {
if (!$this->sessionLoaded) {
$session = null;
$write_cookie = true;
// try loading session from signed_request in $_REQUEST
$signedRequest = $this->getSignedRequest();
if ($signedRequest) {
// sig is good, use the signedRequest
$session = $this->createSessionFromSignedRequest($signedRequest);
}
// try loading session from $_REQUEST
if (!$session && isset($_REQUEST['session'])) {
$session = json_decode(
get_magic_quotes_gpc()
? stripslashes($_REQUEST['session'])
: $_REQUEST['session'],
true
);
$session = $this->validateSessionObject($session);
}
// try loading session from cookie if necessary
if (!$session && $this->useCookieSupport()) {
$cookieName = $this->getSessionCookieName();
if (isset($_COOKIE[$cookieName])) {
$session = array();
parse_str(trim(
get_magic_quotes_gpc()
? stripslashes($_COOKIE[$cookieName])
: $_COOKIE[$cookieName],
'"'
), $session);
$session = $this->validateSessionObject($session);
// write only if we need to delete a invalid session cookie
$write_cookie = empty($session);
}
}
$this->setSession($session, $write_cookie);
}
return $this->session;
}
/**
* Get the UID from the session.
*
* @return String the UID if available
*/
public function getUser() {
$session = $this->getSession();
return $session ? $session['uid'] : null;
}
/**
* Gets a OAuth access token.
*
* @return String the access token
*/
public function getAccessToken() {
$session = $this->getSession();
// either user session signed, or app signed
if ($session) {
return $session['access_token'];
} else {
return $this->getAppId() .'|'. $this->getApiSecret();
}
}
/**
* Get a Login URL for use with redirects. By default, full page redirect is
* assumed. If you are using the generated URL with a window.open() call in
* JavaScript, you can pass in display=popup as part of the $params.
*
* The parameters:
* - next: the url to go to after a successful login
* - cancel_url: the url to go to after the user cancels
* - req_perms: comma separated list of requested extended perms
* - display: can be "page" (default, full page) or "popup"
*
* @param Array $params provide custom parameters
* @return String the URL for the login flow
*/
public function getLoginUrl($params=array()) {
$currentUrl = $this->getCurrentUrl();
return $this->getUrl(
'www',
'login.php',
array_merge(array(
'api_key' => $this->getAppId(),
'cancel_url' => $currentUrl,
'display' => 'page',
'fbconnect' => 1,
'next' => $currentUrl,
'return_session' => 1,
'session_version' => 3,
'v' => '1.0',
), $params)
);
}
/**
* Get a Logout URL suitable for use with redirects.
*
* The parameters:
* - next: the url to go to after a successful logout
*
* @param Array $params provide custom parameters
* @return String the URL for the logout flow
*/
public function getLogoutUrl($params=array()) {
return $this->getUrl(
'www',
'logout.php',
array_merge(array(
'next' => $this->getCurrentUrl(),
'access_token' => $this->getAccessToken(),
), $params)
);
}
/**
* Get a login status URL to fetch the status from facebook.
*
* The parameters:
* - ok_session: the URL to go to if a session is found
* - no_session: the URL to go to if the user is not connected
* - no_user: the URL to go to if the user is not signed into facebook
*
* @param Array $params provide custom parameters
* @return String the URL for the logout flow
*/
public function getLoginStatusUrl($params=array()) {
return $this->getUrl(
'www',
'extern/login_status.php',
array_merge(array(
'api_key' => $this->getAppId(),
'no_session' => $this->getCurrentUrl(),
'no_user' => $this->getCurrentUrl(),
'ok_session' => $this->getCurrentUrl(),
'session_version' => 3,
), $params)
);
}
/**
* Make an API call.
*
* @param Array $params the API call parameters
* @return the decoded response
*/
public function api(/* polymorphic */) {
$args = func_get_args();
if (is_array($args[0])) {
return $this->_restserver($args[0]);
} else {
return call_user_func_array(array($this, '_graph'), $args);
}
}
/**
* Invoke the old restserver.php endpoint.
*
* @param Array $params method call object
* @return the decoded response object
* @throws FacebookApiException
*/
protected function _restserver($params) {
// generic application level parameters
$params['api_key'] = $this->getAppId();
$params['format'] = 'json-strings';
$result = json_decode($this->_oauthRequest(
$this->getApiUrl($params['method']),
$params
), true);
// results are returned, errors are thrown
if (is_array($result) && isset($result['error_code'])) {
throw new FacebookApiException($result);
}
return $result;
}
/**
* Invoke the Graph API.
*
* @param String $path the path (required)
* @param String $method the http method (default 'GET')
* @param Array $params the query/post data
* @return the decoded response object
* @throws FacebookApiException
*/
protected function _graph($path, $method='GET', $params=array()) {
if (is_array($method) && empty($params)) {
$params = $method;
$method = 'GET';
}
$params['method'] = $method; // method override as we always do a POST
$result = json_decode($this->_oauthRequest(
$this->getUrl('graph', $path),
$params
), true);
// results are returned, errors are thrown
if (is_array($result) && isset($result['error'])) {
$e = new FacebookApiException($result);
switch ($e->getType()) {
// OAuth 2.0 Draft 00 style
case 'OAuthException':
// OAuth 2.0 Draft 10 style
case 'invalid_token':
$this->setSession(null);
}
throw $e;
}
return $result;
}
/**
* Make a OAuth Request
*
* @param String $path the path (required)
* @param Array $params the query/post data
* @return the decoded response object
* @throws FacebookApiException
*/
protected function _oauthRequest($url, $params) {
if (!isset($params['access_token'])) {
$params['access_token'] = $this->getAccessToken();
}
// json_encode all params values that are not strings
foreach ($params as $key => $value) {
if (!is_string($value)) {
$params[$key] = json_encode($value);
}
}
return $this->makeRequest($url, $params);
}
/**
* Makes an HTTP request. This method can be overriden by subclasses if
* developers want to do fancier things or use something other than curl to
* make the request.
*
* @param String $url the URL to make the request to
* @param Array $params the parameters to use for the POST body
* @param CurlHandler $ch optional initialized curl handle
* @return String the response text
*/
protected function makeRequest($url, $params, $ch=null) {
if (!$ch) {
$ch = curl_init();
}
$opts = self::$CURL_OPTS;
if ($this->useFileUploadSupport()) {
$opts[CURLOPT_POSTFIELDS] = $params;
} else {
$opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
}
$opts[CURLOPT_URL] = $url;
// disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
// for 2 seconds if the server does not support this header.
if (isset($opts[CURLOPT_HTTPHEADER])) {
$existing_headers = $opts[CURLOPT_HTTPHEADER];
$existing_headers[] = 'Expect:';
$opts[CURLOPT_HTTPHEADER] = $existing_headers;
} else {
$opts[CURLOPT_HTTPHEADER] = array('Expect:');
}
curl_setopt_array($ch, $opts);
$result = curl_exec($ch);
if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
self::errorLog('Invalid or no certificate authority found, using bundled information');
curl_setopt($ch, CURLOPT_CAINFO,
dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
$result = curl_exec($ch);
}
if ($result === false) {
$e = new FacebookApiException(array(
'error_code' => curl_errno($ch),
'error' => array(
'message' => curl_error($ch),
'type' => 'CurlException',
),
));
curl_close($ch);
throw $e;
}
curl_close($ch);
return $result;
}
/**
* The name of the Cookie that contains the session.
*
* @return String the cookie name
*/
protected function getSessionCookieName() {
return 'fbs_' . $this->getAppId();
}
/**
* Set a JS Cookie based on the _passed in_ session. It does not use the
* currently stored session -- you need to explicitly pass it in.
*
* @param Array $session the session to use for setting the cookie
*/
protected function setCookieFromSession($session=null) {
if (!$this->useCookieSupport()) {
return;
}
$cookieName = $this->getSessionCookieName();
$value = 'deleted';
$expires = time() - 3600;
$domain = $this->getBaseDomain();
if ($session) {
$value = '"' . http_build_query($session, null, '&') . '"';
if (isset($session['base_domain'])) {
$domain = $session['base_domain'];
}
$expires = $session['expires'];
}
// prepend dot if a domain is found
if ($domain) {
$domain = '.' . $domain;
}
// if an existing cookie is not set, we dont need to delete it
if ($value == 'deleted' && empty($_COOKIE[$cookieName])) {
return;
}
if (headers_sent()) {
self::errorLog('Could not set cookie. Headers already sent.');
// ignore for code coverage as we will never be able to setcookie in a CLI
// environment
// @codeCoverageIgnoreStart
} else {
setcookie($cookieName, $value, $expires, '/', $domain);
}
// @codeCoverageIgnoreEnd
}
/**
* Validates a session_version=3 style session object.
*
* @param Array $session the session object
* @return Array the session object if it validates, null otherwise
*/
protected function validateSessionObject($session) {
// make sure some essential fields exist
if (is_array($session) &&
isset($session['uid']) &&
isset($session['access_token']) &&
isset($session['sig'])) {
// validate the signature
$session_without_sig = $session;
unset($session_without_sig['sig']);
$expected_sig = self::generateSignature(
$session_without_sig,
$this->getApiSecret()
);
if ($session['sig'] != $expected_sig) {
self::errorLog('Got invalid session signature in cookie.');
$session = null;
}
// check expiry time
} else {
$session = null;
}
return $session;
}
/**
* Returns something that looks like our JS session object from the
* signed token's data
*
* TODO: Nuke this once the login flow uses OAuth2
*
* @param Array the output of getSignedRequest
* @return Array Something that will work as a session
*/
protected function createSessionFromSignedRequest($data) {
if (!isset($data['oauth_token'])) {
return null;
}
$session = array(
'uid' => $data['user_id'],
'access_token' => $data['oauth_token'],
'expires' => $data['expires'],
);
// put a real sig, so that validateSignature works
$session['sig'] = self::generateSignature(
$session,
$this->getApiSecret()
);
return $session;
}
/**
* Parses a signed_request and validates the signature.
* Then saves it in $this->signed_data
*
* @param String A signed token
* @param Boolean Should we remove the parts of the payload that
* are used by the algorithm?
* @return Array the payload inside it or null if the sig is wrong
*/
protected function parseSignedRequest($signed_request) {
list($encoded_sig, $payload) = explode('.', $signed_request, 2);
// decode the data
$sig = self::base64UrlDecode($encoded_sig);
$data = json_decode(self::base64UrlDecode($payload), true);
if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
return null;
}
// check sig
$expected_sig = hash_hmac('sha256', $payload,
$this->getApiSecret(), $raw = true);
if ($sig !== $expected_sig) {
self::errorLog('Bad Signed JSON signature!');
return null;
}
return $data;
}
/**
* Build the URL for api given parameters.
*
* @param $method String the method name.
* @return String the URL for the given parameters
*/
protected function getApiUrl($method) {
static $READ_ONLY_CALLS =
array('admin.getallocation' => 1,
'admin.getappproperties' => 1,
'admin.getbannedusers' => 1,
'admin.getlivestreamvialink' => 1,
'admin.getmetrics' => 1,
'admin.getrestrictioninfo' => 1,
'application.getpublicinfo' => 1,
'auth.getapppublickey' => 1,
'auth.getsession' => 1,
'auth.getsignedpublicsessiondata' => 1,
'comments.get' => 1,
'connect.getunconnectedfriendscount' => 1,
'dashboard.getactivity' => 1,
'dashboard.getcount' => 1,
'dashboard.getglobalnews' => 1,
'dashboard.getnews' => 1,
'dashboard.multigetcount' => 1,
'dashboard.multigetnews' => 1,
'data.getcookies' => 1,
'events.get' => 1,
'events.getmembers' => 1,
'fbml.getcustomtags' => 1,
'feed.getappfriendstories' => 1,
'feed.getregisteredtemplatebundlebyid' => 1,
'feed.getregisteredtemplatebundles' => 1,
'fql.multiquery' => 1,
'fql.query' => 1,
'friends.arefriends' => 1,
'friends.get' => 1,
'friends.getappusers' => 1,
'friends.getlists' => 1,
'friends.getmutualfriends' => 1,
'gifts.get' => 1,
'groups.get' => 1,
'groups.getmembers' => 1,
'intl.gettranslations' => 1,
'links.get' => 1,
'notes.get' => 1,
'notifications.get' => 1,
'pages.getinfo' => 1,
'pages.isadmin' => 1,
'pages.isappadded' => 1,
'pages.isfan' => 1,
'permissions.checkavailableapiaccess' => 1,
'permissions.checkgrantedapiaccess' => 1,
'photos.get' => 1,
'photos.getalbums' => 1,
'photos.gettags' => 1,
'profile.getinfo' => 1,
'profile.getinfooptions' => 1,
'stream.get' => 1,
'stream.getcomments' => 1,
'stream.getfilters' => 1,
'users.getinfo' => 1,
'users.getloggedinuser' => 1,
'users.getstandardinfo' => 1,
'users.hasapppermission' => 1,
'users.isappuser' => 1,
'users.isverified' => 1,
'video.getuploadlimits' => 1);
$name = 'api';
if (isset($READ_ONLY_CALLS[strtolower($method)])) {
$name = 'api_read';
}
return self::getUrl($name, 'restserver.php');
}
/**
* Build the URL for given domain alias, path and parameters.
*
* @param $name String the name of the domain
* @param $path String optional path (without a leading slash)
* @param $params Array optional query parameters
* @return String the URL for the given parameters
*/
protected function getUrl($name, $path='', $params=array()) {
$url = self::$DOMAIN_MAP[$name];
if ($path) {
if ($path[0] === '/') {
$path = substr($path, 1);
}
$url .= $path;
}
if ($params) {
$url .= '?' . http_build_query($params, null, '&');
}
return $url;
}
/**
* Returns the Current URL, stripping it of known FB parameters that should
* not persist.
*
* @return String the current URL
*/
protected function getCurrentUrl() {
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
? 'https://'
: 'http://';
$currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
$parts = parse_url($currentUrl);
// drop known fb params
$query = '';
if (!empty($parts['query'])) {
$params = array();
parse_str($parts['query'], $params);
foreach(self::$DROP_QUERY_PARAMS as $key) {
unset($params[$key]);
}
if (!empty($params)) {
$query = '?' . http_build_query($params, null, '&');
}
}
// use port if non default
$port =
isset($parts['port']) &&
(($protocol === 'http://' && $parts['port'] !== 80) ||
($protocol === 'https://' && $parts['port'] !== 443))
? ':' . $parts['port'] : '';
// rebuild
return $protocol . $parts['host'] . $port . $parts['path'] . $query;
}
/**
* Generate a signature for the given params and secret.
*
* @param Array $params the parameters to sign
* @param String $secret the secret to sign with
* @return String the generated signature
*/
protected static function generateSignature($params, $secret) {
// work with sorted data
ksort($params);
// generate the base string
$base_string = '';
foreach($params as $key => $value) {
$base_string .= $key . '=' . $value;
}
$base_string .= $secret;
return md5($base_string);
}
/**
* Prints to the error log if you aren't in command line mode.
*
* @param String log message
*/
protected static function errorLog($msg) {
// disable error log if we are running in a CLI environment
// @codeCoverageIgnoreStart
if (php_sapi_name() != 'cli') {
error_log($msg);
}
// uncomment this if you want to see the errors on the page
// print 'error_log: '.$msg."\n";
// @codeCoverageIgnoreEnd
}
/**
* Base64 encoding that doesn't need to be urlencode()ed.
* Exactly the same as base64_encode except it uses
* - instead of +
* _ instead of /
*
* @param String base64UrlEncodeded string
*/
protected static function base64UrlDecode($input) {
return base64_decode(strtr($input, '-_', '+/'));
}
}

View File

@ -0,0 +1,121 @@
-----BEGIN CERTIFICATE-----
MIIFgjCCBGqgAwIBAgIQDKKbZcnESGaLDuEaVk6fQjANBgkqhkiG9w0BAQUFADBm
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBDQS0zMB4XDTEwMDExMzAwMDAwMFoXDTEzMDQxMTIzNTk1OVowaDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEX
MBUGA1UEChMORmFjZWJvb2ssIEluYy4xFzAVBgNVBAMUDiouZmFjZWJvb2suY29t
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9rzj7QIuLM3sdHu1HcI1VcR3g
b5FExKNV646agxSle1aQ/sJev1mh/u91ynwqd2BQmM0brZ1Hc3QrfYyAaiGGgEkp
xbhezyfeYhAyO0TKAYxPnm2cTjB5HICzk6xEIwFbA7SBJ2fSyW1CFhYZyo3tIBjj
19VjKyBfpRaPkzLmRwIDAQABo4ICrDCCAqgwHwYDVR0jBBgwFoAUUOpzidsp+xCP
nuUBINTeeZlIg/cwHQYDVR0OBBYEFPp+tsFBozkjrHlEnZ9J4cFj2eM0MA4GA1Ud
DwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMF8GA1UdHwRYMFYwKaAnoCWGI2h0dHA6
Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9jYTMtZmIuY3JsMCmgJ6AlhiNodHRwOi8vY3Js
NC5kaWdpY2VydC5jb20vY2EzLWZiLmNybDCCAcYGA1UdIASCAb0wggG5MIIBtQYL
YIZIAYb9bAEDAAEwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0
LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIB
UgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkA
YwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEA
bgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMA
UABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkA
IABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwA
aQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8A
cgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMA
ZQAuMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF
AAOCAQEACOkTIdxMy11+CKrbGNLBSg5xHaTvu/v1wbyn3dO/mf68pPfJnX6ShPYy
4XM4Vk0x4uaFaU4wAGke+nCKGi5dyg0Esg7nemLNKEJaFAJZ9enxZm334lSCeARy
wlDtxULGOFRyGIZZPmbV2eNq5xdU/g3IuBEhL722mTpAye9FU/J8Wsnw54/gANyO
Gzkewigua8ip8Lbs9Cht399yAfbfhUP1DrAm/xEcnHrzPr3cdCtOyJaM6SRPpRqH
ITK5Nc06tat9lXVosSinT3KqydzxBYua9gCFFiR3x3DgZfvXkC6KDdUlDrNcJUub
a1BHnLLP4mxTHL6faAXYd05IxNn/IA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGVTCCBT2gAwIBAgIQCFH5WYFBRcq94CTiEsnCDjANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA3MDQwMzAwMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR
CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv
KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5
BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf
1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs
zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d
32duXvsCAwEAAaOCAvcwggLzMA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w
ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3
LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH
AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy
AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj
AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg
AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ
AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt
AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj
AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl
AHIAZQBuAGMAZQAuMA8GA1UdEwEB/wQFMAMBAf8wNAYIKwYBBQUHAQEEKDAmMCQG
CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSBhzCB
hDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFz
c3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu
Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAW
gBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTe
eZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAF1PhPGoiNOjsrycbeUpSXfh59bcqdg1
rslx3OXb3J0kIZCmz7cBHJvUV5eR13UWpRLXuT0uiT05aYrWNTf58SHEW0CtWakv
XzoAKUMncQPkvTAyVab+hA4LmzgZLEN8rEO/dTHlIxxFVbdpCJG1z9fVsV7un5Tk
1nq5GMO41lJjHBC6iy9tXcwFOPRWBW3vnuzoYTYMFEuFFFoMg08iXFnLjIpx2vrF
EIRYzwfu45DC9fkpx1ojcflZtGQriLCnNseaIGHr+k61rmsb5OPs4tk8QUmoIKRU
9ZKNu8BVIASm2LAXFszj0Mi0PeXZhMbT9m5teMl5Q+h6N/9cNUm/ocU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEQjCCA6ugAwIBAgIEQoclDjANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEy
MjIxNTI3MjdaFw0xNDA3MjIxNTU3MjdaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD
1ZQ0Z6IKHLBfaaZAscS3so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80lt
cZF+Y7arpl/DpIT4T2JRvvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46
OFBbdzEbjbPHJEWap6xtABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZd
HFMsfpjNGgYWpGhz0DQEE1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdm
t4i3ePLKCqg4qwpkwr9mXZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggET
MIIBDzASBgNVHRMBAf8ECDAGAQH/AgEBMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggr
BgEFBQcDAgYIKwYBBQUHAwQwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdo
dHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8v
Y3JsLmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMB0GA1UdDgQWBBSxPsNpA/i/RwHU
mCYaCALvY2QrwzALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7
UISX8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEF
BQADgYEAUuVY7HCc/9EvhaYzC1rAIo348LtGIiMduEl5Xa24G8tmJnDioD2GU06r
1kjLX/ktCdpdBgXadbjtdrZXTP59uN0AXlsdaTiFufsqVLPvkp5yMnqnuI3E2o6p
NpAkoQSbB6kUCNnXcW26valgOjDLZFOnr241QiwdBAJAAE/rRa8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Queuehandler for Facebook transport
*
* 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 2010 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')) {
exit(1);
}
class FacebookQueueHandler extends QueueHandler
{
function transport()
{
return 'facebook';
}
function handle($notice)
{
if ($this->_isLocal($notice)) {
return Facebookclient::facebookBroadcastNotice($notice);
}
return true;
}
/**
* Determine whether the notice was locally created
*
* @param Notice $notice the notice
*
* @return boolean locality
*/
function _isLocal($notice)
{
return ($notice->is_local == Notice::LOCAL_PUBLIC ||
$notice->is_local == Notice::LOCAL_NONPUBLIC);
}
}