Gather all the Facebook stuff together in one place

This commit is contained in:
Zach Copley
2009-10-21 01:07:03 +00:00
parent 78e5a5980a
commit d07c9d8750
18 changed files with 4 additions and 76 deletions

View File

@@ -0,0 +1,114 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Menu for login group of actions
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Menu
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/widget.php';
/**
* Menu for login group of actions
*
* @category Output
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see Widget
*/
class FBCLoginGroupNav extends Widget
{
var $action = null;
/**
* Construction
*
* @param Action $action current action, used for output
*/
function __construct($action=null)
{
parent::__construct($action);
$this->action = $action;
}
/**
* Show the menu
*
* @return void
*/
function show()
{
$this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
$this->action->element('dt', null, _('Local views'));
$this->action->elementStart('dd');
// action => array('prompt', 'title')
$menu = array();
if (!common_config('site','openidonly')) {
$menu['login'] = array(_('Login'),
_('Login with a username and password'));
if (!(common_config('site','closed') || common_config('site','inviteonly'))) {
$menu['register'] = array(_('Register'),
_('Sign up for a new account'));
}
}
if (common_config('openid', 'enabled')) {
$menu['openidlogin'] = array(_('OpenID'),
_('Login or register with OpenID'));
}
$menu['FBConnectLogin'] = array(_('Facebook'),
_('Login or register using Facebook'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
$action_name === $menuaction);
}
$this->action->elementEnd('ul');
$this->action->elementEnd('dd');
$this->action->elementEnd('dl');
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Menu for login group of actions
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Menu
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/lib/widget.php';
/**
* A widget for showing the connect group local nav menu
*
* @category Output
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*
* @see Widget
*/
class FBCSettingsNav extends Widget
{
var $action = null;
/**
* Construction
*
* @param Action $action current action, used for output
*/
function __construct($action=null)
{
parent::__construct($action);
$this->action = $action;
}
/**
* Show the menu
*
* @return void
*/
function show()
{
$this->action->elementStart('dl', array('id' => 'site_nav_local_views'));
$this->action->element('dt', null, _('Local views'));
$this->action->elementStart('dd');
# action => array('prompt', 'title')
$menu = array();
if (common_config('xmpp', 'enabled')) {
$menu['imsettings'] =
array(_('IM'),
_('Updates by instant messenger (IM)'));
}
if (common_config('sms', 'enabled')) {
$menu['smssettings'] =
array(_('SMS'),
_('Updates by SMS'));
}
if (common_config('twitter', 'enabled')) {
$menu['twittersettings'] =
array(_('Twitter'),
_('Twitter integration options'));
}
$menu['FBConnectSettings'] =
array(_('Facebook'),
_('Facebook Connect settings'));
$action_name = $this->action->trimmed('action');
$this->action->elementStart('ul', array('class' => 'nav'));
foreach ($menu as $menuaction => $menudesc) {
$this->action->menuItem(common_local_url($menuaction),
$menudesc[0],
$menudesc[1],
$action_name === $menuaction);
}
$this->action->elementEnd('ul');
$this->action->elementEnd('dd');
$this->action->elementEnd('dl');
}
}

View File

@@ -0,0 +1,68 @@
<?php
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
/*
* Generates the cross domain communication channel file
* (xd_receiver.html). By generating it we can add some caching
* instructions.
*
* See: http://wiki.developers.facebook.com/index.php/Cross_Domain_Communication_Channel
*/
class FBC_XDReceiverAction extends Action
{
/**
* Do we need to write to the database?
*
* @return boolean true
*/
function isReadonly()
{
return true;
}
/**
* Handle a request
*
* @param array $args Arguments from $_REQUEST
*
* @return void
*/
function handle($args)
{
// Parent handling, including cache check
parent::handle($args);
$this->showPage();
}
function showPage()
{
// cache the xd_receiver
header('Cache-Control: max-age=225065900');
header('Expires:');
header('Pragma:');
$this->startXML('html');
$language = $this->getLanguage();
$this->elementStart('html', array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xml:lang' => $language,
'lang' => $language));
$this->elementStart('head');
$this->element('title', null, 'cross domain receiver page');
$this->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/XdCommReceiver.debug.js');
$this->elementEnd('head');
$this->elementStart('body');
$this->elementEnd('body');
$this->elementEnd('html');
}
}

View File

@@ -0,0 +1,461 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Plugin to enable Facebook Connect
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
class FBConnectauthAction extends Action
{
var $fbuid = null;
var $fb_fields = null;
function prepare($args) {
parent::prepare($args);
$this->fbuid = getFacebook()->get_loggedin_user();
if ($this->fbuid > 0) {
$this->fb_fields = $this->getFacebookFields($this->fbuid,
array('first_name', 'last_name', 'name'));
} else {
list($proxy, $ip) = common_client_ip();
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
"Failed auth attempt, proxy = $proxy, ip = $ip.");
$this->clientError(_('You must be logged into Facebook to ' .
'use Facebook Connect.'));
}
return true;
}
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
// User is already logged in. Does she already have a linked Facebook acct?
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
if (!empty($flink)) {
// User already has a linked Facebook account and shouldn't be here
common_debug('Facebook Connect Plugin - ' .
'There is already a local user (' . $flink->user_id .
') linked with this Facebook (' . $this->fbuid . ').');
// We don't want these cookies
getFacebook()->clear_cookie_state();
$this->clientError(_('There is already a local user linked with this Facebook.'));
} else {
// User came from the Facebook connect settings tab, and
// probably just wants to link/relink their Facebook account
$this->connectUser();
}
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. Try again, please.'));
return;
}
if ($this->arg('create')) {
if (!$this->boolean('license')) {
$this->showForm(_('You can\'t register if you don\'t agree to the license.'),
$this->trimmed('newname'));
return;
}
$this->createNewUser();
} else if ($this->arg('connect')) {
$this->connectNewUser();
} else {
common_debug('Facebook Connect Plugin - ' .
print_r($this->args, true));
$this->showForm(_('Something weird happened.'),
$this->trimmed('newname'));
}
} else {
$this->tryLogin();
}
}
function showPageNotice()
{
if ($this->error) {
$this->element('div', array('class' => 'error'), $this->error);
} else {
$this->element('div', 'instructions',
sprintf(_('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new account, or connect with your existing account, if you have one.'), common_config('site', 'name')));
}
}
function title()
{
return _('Facebook Account Setup');
}
function showForm($error=null, $username=null)
{
$this->error = $error;
$this->username = $username;
$this->showPage();
}
function showPage()
{
parent::showPage();
}
function showContent()
{
if (!empty($this->message_text)) {
$this->element('p', null, $this->message);
return;
}
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_facebook_connect',
'class' => 'form_settings',
'action' => common_local_url('FBConnectAuth')));
$this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
$this->element('legend', null, _('Connection options'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->element('input', array('type' => 'checkbox',
'id' => 'license',
'class' => 'checkbox',
'name' => 'license',
'value' => 'true'));
$this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
$this->text(_('My text and files are available under '));
$this->element('a', array('href' => common_config('license', 'url')),
common_config('license', 'title'));
$this->text(_(' except this private data: password, email address, IM address, phone number.'));
$this->elementEnd('label');
$this->elementEnd('li');
$this->elementEnd('ul');
$this->elementStart('fieldset');
$this->hidden('token', common_session_token());
$this->element('legend', null,
_('Create new account'));
$this->element('p', null,
_('Create a new user with this nickname.'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('newname', _('New nickname'),
($this->username) ? $this->username : '',
_('1-64 lowercase letters or numbers, no punctuation or spaces'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('create', _('Create'));
$this->elementEnd('fieldset');
$this->elementStart('fieldset');
$this->element('legend', null,
_('Connect existing account'));
$this->element('p', null,
_('If you already have an account, login with your username and password to connect it to your Facebook.'));
$this->elementStart('ul', 'form_data');
$this->elementStart('li');
$this->input('nickname', _('Existing nickname'));
$this->elementEnd('li');
$this->elementStart('li');
$this->password('password', _('Password'));
$this->elementEnd('li');
$this->elementEnd('ul');
$this->submit('connect', _('Connect'));
$this->elementEnd('fieldset');
$this->elementEnd('fieldset');
$this->elementEnd('form');
}
function message($msg)
{
$this->message_text = $msg;
$this->showPage();
}
function createNewUser()
{
if (common_config('site', 'closed')) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = null;
if (common_config('site', 'inviteonly')) {
$code = $_SESSION['invitecode'];
if (empty($code)) {
$this->clientError(_('Registration not allowed.'));
return;
}
$invite = Invitation::staticGet($code);
if (empty($invite)) {
$this->clientError(_('Not a valid invitation code.'));
return;
}
}
$nickname = $this->trimmed('newname');
if (!Validate::string($nickname, array('min_length' => 1,
'max_length' => 64,
'format' => NICKNAME_FMT))) {
$this->showForm(_('Nickname must have only lowercase letters and numbers and no spaces.'));
return;
}
if (!User::allowed_nickname($nickname)) {
$this->showForm(_('Nickname not allowed.'));
return;
}
if (User::staticGet('nickname', $nickname)) {
$this->showForm(_('Nickname already in use. Try another one.'));
return;
}
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
$args = array('nickname' => $nickname, 'fullname' => $fullname);
if (!empty($invite)) {
$args['code'] = $invite->code;
}
$user = User::register($args);
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_set_user($user);
common_real_login(true);
common_debug('Facebook Connect Plugin - ' .
"Registered new user $user->id from Facebook user $this->fbuid");
common_redirect(common_local_url('showstream', array('nickname' => $user->nickname)),
303);
}
function connectNewUser()
{
$nickname = $this->trimmed('nickname');
$password = $this->trimmed('password');
if (!common_check_user($nickname, $password)) {
$this->showForm(_('Invalid username or password.'));
return;
}
$user = User::staticGet('nickname', $nickname);
if (!empty($user)) {
common_debug('Facebook Connect Plugin - ' .
"Legit user to connect to Facebook: $nickname");
}
$result = $this->flinkUser($user->id, $this->fbuid);
if (!$result) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug('Facebook Connnect Plugin - ' .
"Connected Facebook user $this->fbuid to local user $user->id");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
function connectUser()
{
$user = common_current_user();
$result = $this->flinkUser($user->id, $this->fbuid);
if (empty($result)) {
$this->serverError(_('Error connecting user to Facebook.'));
return;
}
common_debug('Facebook Connect Plugin - ' .
"Connected Facebook user $this->fbuid to local user $user->id");
// Return to Facebook connection settings tab
common_redirect(common_local_url('FBConnectSettings'), 303);
}
function tryLogin()
{
common_debug('Facebook Connect Plugin - ' .
"Trying login for Facebook user $this->fbuid.");
$flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_CONNECT_SERVICE);
if (!empty($flink)) {
$user = $flink->getUser();
if (!empty($user)) {
common_debug('Facebook Connect Plugin - ' .
"Logged in Facebook user $flink->foreign_id as user $user->id ($user->nickname)");
common_set_user($user);
common_real_login(true);
$this->goHome($user->nickname);
}
} else {
common_debug('Facebook Connect Plugin - ' .
"No flink found for fbuid: $this->fbuid - new user");
$this->showForm(null, $this->bestNewNickname());
}
}
function goHome($nickname)
{
$url = common_get_returnto();
if ($url) {
// We don't have to return to it again
common_set_returnto(null);
} else {
$url = common_local_url('all',
array('nickname' =>
$nickname));
}
common_redirect($url, 303);
}
function flinkUser($user_id, $fbuid)
{
$flink = new Foreign_link();
$flink->user_id = $user_id;
$flink->foreign_id = $fbuid;
$flink->service = FACEBOOK_CONNECT_SERVICE;
$flink->created = common_sql_now();
$flink_id = $flink->insert();
return $flink_id;
}
function bestNewNickname()
{
if (!empty($this->fb_fields['name'])) {
$nickname = $this->nicknamize($this->fb_fields['name']);
if ($this->isNewNickname($nickname)) {
return $nickname;
}
}
// Try the full name
$fullname = trim($this->fb_fields['firstname'] .
' ' . $this->fb_fields['lastname']);
if (!empty($fullname)) {
$fullname = $this->nicknamize($fullname);
if ($this->isNewNickname($fullname)) {
return $fullname;
}
}
return null;
}
// Given a string, try to make it work as a nickname
function nicknamize($str)
{
$str = preg_replace('/\W/', '', $str);
return strtolower($str);
}
function isNewNickname($str)
{
if (!Validate::string($str, array('min_length' => 1,
'max_length' => 64,
'format' => NICKNAME_FMT))) {
return false;
}
if (!User::allowed_nickname($str)) {
return false;
}
if (User::staticGet('nickname', $str)) {
return false;
}
return true;
}
// XXX: Consider moving this to lib/facebookutil.php
function getFacebookFields($fb_uid, $fields) {
try {
$facebook = getFacebook();
$infos = $facebook->api_client->users_getInfo($fb_uid, $fields);
if (empty($infos)) {
return null;
}
return reset($infos);
} catch (Exception $e) {
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
"Facebook client failure when requesting " .
join(",", $fields) . " on uid " . $fb_uid .
" : ". $e->getMessage());
return null;
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* StatusNet - a distributed open-source microblogging tool
* Copyright (C) 2008, Controlez-Vous, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectPlugin.php';
class FBConnectLoginAction extends Action
{
function handle($args)
{
parent::handle($args);
if (common_is_real_login()) {
$this->clientError(_('Already logged in.'));
}
$this->showPage();
}
function getInstructions()
{
return _('Login with your Facebook Account');
}
function showPageNotice()
{
$instr = $this->getInstructions();
$output = common_markup_to_html($instr);
$this->elementStart('div', 'instructions');
$this->raw($output);
$this->elementEnd('div');
}
function title()
{
return _('Facebook Login');
}
function showContent() {
$this->elementStart('fieldset');
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
$this->elementEnd('fieldset');
}
}

View File

@@ -0,0 +1,36 @@
/** Styles for Facebook logo and Facebook user profile avatar.
*
* @package StatusNet
* @author Sarven Capadisli <csarven@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
#site_nav_global_primary #nav_fb {
position:relative;
margin-left:18px;
}
#nav_fb #fbc_profile-pic {
position:absolute;
top:-3px;
left:-18px;
display:inline;
border:1px solid #3B5998;
padding:1px;
}
#nav_fb #fb_favicon {
position:absolute;
top:-13px;
left:-25px;
display:inline;
}
#settings_facebook_connect_options legend {
display:none;
}
#form_settings_facebook_connect fieldset fieldset legend {
display:block;
}

View File

@@ -0,0 +1,367 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Plugin to enable Facebook Connect
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Plugin
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
define("FACEBOOK_CONNECT_SERVICE", 3);
require_once INSTALLDIR . '/lib/facebookutil.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectAuth.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectLogin.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBConnectSettings.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCLoginGroupNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBCSettingsNav.php';
require_once INSTALLDIR . '/plugins/FBConnect/FBC_XDReceiver.php';
/**
* Plugin to enable Facebook Connect
*
* @category Plugin
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class FBConnectPlugin extends Plugin
{
function __construct()
{
parent::__construct();
}
// Hook in new actions
function onRouterInitialized(&$m) {
$m->connect('main/facebookconnect', array('action' => 'FBConnectAuth'));
$m->connect('main/facebooklogin', array('action' => 'FBConnectLogin'));
$m->connect('settings/facebook', array('action' => 'FBConnectSettings'));
$m->connect('xd_receiver.html', array('action' => 'FBC_XDReceiver'));
}
// Add in xmlns:fb
function onStartShowHTML($action)
{
if ($this->reqFbScripts($action)) {
// XXX: Horrible hack to make Safari, FF2, and Chrome work with
// Facebook Connect. These browser cannot use Facebook's
// DOM parsing routines unless the mime type of the page is
// text/html even though Facebook Connect uses XHTML. This is
// A bug in Facebook Connect, and this is a temporary solution
// until they fix their JavaScript libs.
header('Content-Type: text/html');
$action->extraHeaders();
$action->startXML('html');
$language = $action->getLanguage();
$action->elementStart('html',
array('xmlns' => 'http://www.w3.org/1999/xhtml',
'xmlns:fb' => 'http://www.facebook.com/2008/fbml',
'xml:lang' => $language,
'lang' => $language));
return false;
} else {
return true;
}
}
// Note: this script needs to appear in the <body>
function onEndShowScripts($action)
{
if ($this->reqFbScripts($action)) {
$apikey = common_config('facebook', 'apikey');
$plugin_path = common_path('plugins/FBConnect');
$login_url = common_local_url('FBConnectAuth');
$logout_url = common_local_url('logout');
// XXX: Facebook says we don't need this FB_RequireFeatures(),
// but we actually do, for IE and Safari. Gar.
$js = '<script type="text/javascript">';
$js .= ' $(document).ready(function () {';
$js .= ' FB_RequireFeatures(';
$js .= ' ["XFBML"], function() {';
$js .= ' FB.init("%1$s", "../xd_receiver.html");';
$js .= ' }';
$js .= ' );';
$js .= ' });';
$js .= ' function goto_login() {';
$js .= ' window.location = "%2$s";';
$js .= ' }';
// The below function alters the logout link so that it logs the user out
// of Facebook Connect as well as the site. However, for some pages
// (FB Connect Settings) we need to output the FB Connect scripts (to
// show an existing FB connection even if the user isn't authenticated
// with Facebook connect) but NOT alter the logout link. And the only
// way to reliably do that is with the FB Connect .js libs. Crazy.
$js .= ' FB.ensureInit(function() {';
$js .= ' FB.Connect.ifUserConnected(';
$js .= ' function() { ';
$js .= ' $(\'#nav_logout a\').attr(\'href\', \'#\');';
$js .= ' $(\'#nav_logout a\').click(function() {';
$js .= ' FB.Connect.logoutAndRedirect(\'%3$s\');';
$js .= ' return false;';
$js .= ' })';
$js .= ' },';
$js .= ' function() {';
$js .= ' return false;';
$js .= ' }';
$js .= ' );';
$js .= ' });';
$js .= '</script>';
$js = sprintf($js, $apikey, $login_url, $logout_url);
// Compress the bugger down a bit
$js = str_replace(' ', '', $js);
$action->raw(" $js"); // leading two spaces to make it line up
}
}
// Note: this script needs to appear as close as possible to </body>
function onEndShowFooter($action)
{
if ($this->reqFbScripts($action)) {
$action->script('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php');
}
}
function onEndShowStatusNetStyles($action)
{
if ($this->reqFbScripts($action)) {
$action->cssLink('plugins/FBConnect/FBConnectPlugin.css');
}
}
/**
* Does the Action we're plugged into require the FB Scripts? We only
* want to output FB namespace, scripts, CSS, etc. on the pages that
* really need them.
*
* @param Action the action in question
*
* @return boolean true
*/
function reqFbScripts($action) {
// If you're logged in w/FB Connect, you always need the FB stuff
$fbuid = $this->loggedIn();
if (!empty($fbuid)) {
return true;
}
// List of actions that require FB stuff
$needy = array('FBConnectLoginAction',
'FBConnectauthAction',
'FBConnectSettingsAction');
if (in_array(get_class($action), $needy)) {
return true;
}
return false;
}
/**
* Is the user currently logged in with FB Connect?
*
* @return mixed $fbuid the Facebook ID of the logged in user, or null
*/
function loggedIn()
{
$user = common_current_user();
if (!empty($user)) {
$flink = Foreign_link::getByUserId($user->id,
FACEBOOK_CONNECT_SERVICE);
$fbuid = 0;
if (!empty($flink)) {
try {
$facebook = getFacebook();
$fbuid = $facebook->get_loggedin_user();
} catch (Exception $e) {
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
'Problem getting Facebook user: ' .
$e->getMessage());
}
if ($fbuid > 0) {
return $fbuid;
}
}
}
return null;
}
function onStartPrimaryNav($action)
{
$user = common_current_user();
$connect = 'FBConnectSettings';
if (common_config('xmpp', 'enabled')) {
$connect = 'imsettings';
} else if (common_config('sms', 'enabled')) {
$connect = 'smssettings';
} else if (common_config('twitter', 'enabled')) {
$connect = 'twittersettings';
}
if (!empty($user)) {
$fbuid = $this->loggedIn();
if (!empty($fbuid)) {
/* Default FB silhouette pic for FB users who haven't
uploaded a profile pic yet. */
$silhouetteUrl =
'http://static.ak.fbcdn.net/pics/q_silhouette.gif';
$url = $this->getProfilePicURL($fbuid);
$action->elementStart('li', array('id' => 'nav_fb'));
$action->element('img', array('id' => 'fbc_profile-pic',
'src' => (!empty($url)) ? $url : $silhouetteUrl,
'alt' => 'Facebook Connect User',
'width' => '16'), '');
$iconurl = common_path('plugins/FBConnect/fbfavicon.ico');
$action->element('img', array('id' => 'fb_favicon',
'src' => $iconurl));
$action->elementEnd('li');
}
}
return true;
}
function onStartShowLocalNavBlock($action)
{
$action_name = get_class($action);
$login_actions = array('LoginAction', 'RegisterAction',
'OpenidloginAction', 'FBConnectLoginAction');
if (in_array($action_name, $login_actions)) {
$nav = new FBCLoginGroupNav($action);
$nav->show();
return false;
}
$connect_actions = array('SmssettingsAction', 'ImsettingsAction',
'TwittersettingsAction', 'FBConnectSettingsAction');
if (in_array($action_name, $connect_actions)) {
$nav = new FBCSettingsNav($action);
$nav->show();
return false;
}
return true;
}
function onStartLogout($action)
{
$action->logout();
$fbuid = $this->loggedIn();
if (!empty($fbuid)) {
try {
$facebook = getFacebook();
$facebook->expire_session();
} catch (Exception $e) {
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
'Could\'t logout of Facebook: ' .
$e->getMessage());
}
}
return true;
}
function getProfilePicURL($fbuid)
{
$facebook = getFacebook();
$url = null;
try {
$fqry = 'SELECT pic_square FROM user WHERE uid = %s';
$result = $facebook->api_client->fql_query(sprintf($fqry, $fbuid));
if (!empty($result)) {
$url = $result[0]['pic_square'];
}
} catch (Exception $e) {
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
"Facebook client failure requesting profile pic!");
}
return $url;
}
}

View File

@@ -0,0 +1,203 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Facebook Connect settings
*
* PHP version 5
*
* LICENCE: This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @category Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1);
}
require_once INSTALLDIR.'/lib/connectsettingsaction.php';
/**
* Facebook Connect settings action
*
* @category Settings
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/
*/
class FBConnectSettingsAction extends ConnectSettingsAction
{
/**
* Title of the page
*
* @return string Title of the page
*/
function title()
{
return _('Facebook Connect Settings');
}
/**
* Instructions for use
*
* @return instructions for use
*/
function getInstructions()
{
return _('Manage how your account connects to Facebook');
}
/**
* Content area of the page
*
* Shows a form for uploading an avatar.
*
* @return void
*/
function showContent()
{
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
$this->elementStart('form', array('method' => 'post',
'id' => 'form_settings_facebook',
'class' => 'form_settings',
'action' =>
common_local_url('FBConnectSettings')));
if (!$flink) {
$this->element('p', 'instructions',
_('There is no Facebook user connected to this account.'));
$this->element('fb:login-button', array('onlogin' => 'goto_login()',
'length' => 'long'));
} else {
$this->element('p', 'form_note',
_('Connected Facebook user'));
$this->elementStart('p', array('class' => 'facebook-user-display'));
$this->elementStart('fb:profile-pic',
array('uid' => $flink->foreign_id,
'size' => 'small',
'linked' => 'true',
'facebook-logo' => 'true'));
$this->elementEnd('fb:profile-pic');
$this->elementStart('fb:name', array('uid' => $flink->foreign_id,
'useyou' => 'false'));
$this->elementEnd('fb:name');
$this->elementEnd('p');
$this->hidden('token', common_session_token());
$this->elementStart('fieldset');
$this->element('legend', null, _('Disconnect my account from Facebook'));
if (!$user->password) {
$this->elementStart('p', array('class' => 'form_guide'));
$this->text(_('Disconnecting your Faceboook ' .
'would make it impossible to log in! Please '));
$this->element('a',
array('href' => common_local_url('passwordsettings')),
_('set a password'));
$this->text(_(' first.'));
$this->elementEnd('p');
} else {
$note = 'Keep your %s account but disconnect from Facebook. ' .
'You\'ll use your %s password to log in.';
$site = common_config('site', 'name');
$this->element('p', 'instructions',
sprintf($note, $site, $site));
$this->submit('disconnect', _('Disconnect'));
}
$this->elementEnd('fieldset');
}
$this->elementEnd('form');
}
/**
* Handle post
*
* Disconnects the current Facebook user from the current user's account
*
* @return void
*/
function handlePost()
{
// CSRF protection
$token = $this->trimmed('token');
if (!$token || $token != common_session_token()) {
$this->showForm(_('There was a problem with your session token. '.
'Try again, please.'));
return;
}
if ($this->arg('disconnect')) {
$user = common_current_user();
$flink = Foreign_link::getByUserID($user->id, FACEBOOK_CONNECT_SERVICE);
$result = $flink->delete();
if ($result === false) {
common_log_db_error($user, 'DELETE', __FILE__);
$this->serverError(_('Couldn\'t delete link to Facebook.'));
return;
}
try {
// Clear FB Connect cookies out
$facebook = getFacebook();
$facebook->clear_cookie_state();
} catch (Exception $e) {
common_log(LOG_WARNING, 'Facebook Connect Plugin - ' .
'Couldn\'t clear Facebook cookies: ' .
$e->getMessage());
}
$this->showForm(_('You have disconnected from Facebook.'), true);
} else {
$this->showForm(_('Not sure what you\'re trying to do.'));
return;
}
}
}

View File

@@ -1,4 +1,8 @@
// Facebook plugin
require_once(INSTALLDIR . '/plugins/Facebook/FacebookPlugin.php');
$fb = new FacebookPlugin();
TODO:

View File

@@ -0,0 +1,598 @@
<?php
// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
// +---------------------------------------------------------------------------+
// | Copyright (c) 2007 Facebook, Inc. |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | 1. Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | 2. Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// +---------------------------------------------------------------------------+
// | For help with this library, contact developers-help@facebook.com |
// +---------------------------------------------------------------------------+
include_once 'facebookapi_php5_restlib.php';
define('FACEBOOK_API_VALIDATION_ERROR', 1);
class Facebook {
public $api_client;
public $api_key;
public $secret;
public $generate_session_secret;
public $session_expires;
public $fb_params;
public $user;
public $profile_user;
public $canvas_user;
protected $base_domain;
/*
* Create a Facebook client like this:
*
* $fb = new Facebook(API_KEY, SECRET);
*
* This will automatically pull in any parameters, validate them against the
* session signature, and chuck them in the public $fb_params member variable.
*
* @param api_key your Developer API key
* @param secret your Developer API secret
* @param generate_session_secret whether to automatically generate a session
* if the user doesn't have one, but
* there is an auth token present in the url,
*/
public function __construct($api_key, $secret, $generate_session_secret=false) {
$this->api_key = $api_key;
$this->secret = $secret;
$this->generate_session_secret = $generate_session_secret;
$this->api_client = new FacebookRestClient($api_key, $secret, null);
$this->validate_fb_params();
// Set the default user id for methods that allow the caller to
// pass an explicit uid instead of using a session key.
$defaultUser = null;
if ($this->user) {
$defaultUser = $this->user;
} else if ($this->profile_user) {
$defaultUser = $this->profile_user;
} else if ($this->canvas_user) {
$defaultUser = $this->canvas_user;
}
$this->api_client->set_user($defaultUser);
if (isset($this->fb_params['friends'])) {
$this->api_client->friends_list = explode(',', $this->fb_params['friends']);
}
if (isset($this->fb_params['added'])) {
$this->api_client->added = $this->fb_params['added'];
}
if (isset($this->fb_params['canvas_user'])) {
$this->api_client->canvas_user = $this->fb_params['canvas_user'];
}
}
/*
* Validates that the parameters passed in were sent from Facebook. It does so
* by validating that the signature matches one that could only be generated
* by using your application's secret key.
*
* Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE,
* in that order. $_POST and $_GET are always more up-to-date than cookies,
* so we prefer those if they are available.
*
* For nitty-gritty details of when each of these is used, check out
* http://wiki.developers.facebook.com/index.php/Verifying_The_Signature
*
* @param bool resolve_auth_token convert an auth token into a session
*/
public function validate_fb_params($resolve_auth_token=true) {
$this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig');
// note that with preload FQL, it's possible to receive POST params in
// addition to GET, so use a different prefix to differentiate them
if (!$this->fb_params) {
$fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig');
$fb_post_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_post_sig');
$this->fb_params = array_merge($fb_params, $fb_post_params);
}
// Okay, something came in via POST or GET
if ($this->fb_params) {
$user = isset($this->fb_params['user']) ?
$this->fb_params['user'] : null;
$this->profile_user = isset($this->fb_params['profile_user']) ?
$this->fb_params['profile_user'] : null;
$this->canvas_user = isset($this->fb_params['canvas_user']) ?
$this->fb_params['canvas_user'] : null;
$this->base_domain = isset($this->fb_params['base_domain']) ?
$this->fb_params['base_domain'] : null;
if (isset($this->fb_params['session_key'])) {
$session_key = $this->fb_params['session_key'];
} else if (isset($this->fb_params['profile_session_key'])) {
$session_key = $this->fb_params['profile_session_key'];
} else {
$session_key = null;
}
$expires = isset($this->fb_params['expires']) ?
$this->fb_params['expires'] : null;
$this->set_user($user,
$session_key,
$expires);
}
// if no Facebook parameters were found in the GET or POST variables,
// then fall back to cookies, which may have cached user information
// Cookies are also used to receive session data via the Javascript API
else if ($cookies =
$this->get_valid_fb_params($_COOKIE, null, $this->api_key)) {
$base_domain_cookie = 'base_domain_' . $this->api_key;
if (isset($_COOKIE[$base_domain_cookie])) {
$this->base_domain = $_COOKIE[$base_domain_cookie];
}
// use $api_key . '_' as a prefix for the cookies in case there are
// multiple facebook clients on the same domain.
$expires = isset($cookies['expires']) ? $cookies['expires'] : null;
$this->set_user($cookies['user'],
$cookies['session_key'],
$expires);
}
// finally, if we received no parameters, but the 'auth_token' GET var
// is present, then we are in the middle of auth handshake,
// so go ahead and create the session
else if ($resolve_auth_token && isset($_GET['auth_token']) &&
$session = $this->do_get_session($_GET['auth_token'])) {
if ($this->generate_session_secret &&
!empty($session['secret'])) {
$session_secret = $session['secret'];
}
if (isset($session['base_domain'])) {
$this->base_domain = $session['base_domain'];
}
$this->set_user($session['uid'],
$session['session_key'],
$session['expires'],
isset($session_secret) ? $session_secret : null);
}
return !empty($this->fb_params);
}
// Store a temporary session secret for the current session
// for use with the JS client library
public function promote_session() {
try {
$session_secret = $this->api_client->auth_promoteSession();
if (!$this->in_fb_canvas()) {
$this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret);
}
return $session_secret;
} catch (FacebookRestClientException $e) {
// API_EC_PARAM means we don't have a logged in user, otherwise who
// knows what it means, so just throw it.
if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
throw $e;
}
}
}
public function do_get_session($auth_token) {
try {
return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret);
} catch (FacebookRestClientException $e) {
// API_EC_PARAM means we don't have a logged in user, otherwise who
// knows what it means, so just throw it.
if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) {
throw $e;
}
}
}
// Invalidate the session currently being used, and clear any state associated
// with it. Note that the user will still remain logged into Facebook.
public function expire_session() {
if ($this->api_client->auth_expireSession()) {
$this->clear_cookie_state();
return true;
} else {
return false;
}
}
/** Logs the user out of all temporary application sessions as well as their
* Facebook session. Note this will only work if the user has a valid current
* session with the application.
*
* @param string $next URL to redirect to upon logging out
*
*/
public function logout($next) {
$logout_url = $this->get_logout_url($next);
// Clear any stored state
$this->clear_cookie_state();
$this->redirect($logout_url);
}
/**
* Clears any persistent state stored about the user, including
* cookies and information related to the current session in the
* client.
*
*/
public function clear_cookie_state() {
if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) {
$cookies = array('user', 'session_key', 'expires', 'ss');
foreach ($cookies as $name) {
setcookie($this->api_key . '_' . $name, false, time() - 3600);
unset($_COOKIE[$this->api_key . '_' . $name]);
}
setcookie($this->api_key, false, time() - 3600);
unset($_COOKIE[$this->api_key]);
}
// now, clear the rest of the stored state
$this->user = 0;
$this->api_client->session_key = 0;
}
public function redirect($url) {
if ($this->in_fb_canvas()) {
echo '<fb:redirect url="' . $url . '"/>';
} else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) {
// make sure facebook.com url's load in the full frame so that we don't
// get a frame within a frame.
echo "<script type=\"text/javascript\">\ntop.location.href = \"$url\";\n</script>";
} else {
header('Location: ' . $url);
}
exit;
}
public function in_frame() {
return isset($this->fb_params['in_canvas'])
|| isset($this->fb_params['in_iframe']);
}
public function in_fb_canvas() {
return isset($this->fb_params['in_canvas']);
}
public function get_loggedin_user() {
return $this->user;
}
public function get_canvas_user() {
return $this->canvas_user;
}
public function get_profile_user() {
return $this->profile_user;
}
public static function current_url() {
return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
}
// require_add and require_install have been removed.
// see http://developer.facebook.com/news.php?blog=1&story=116 for more details
public function require_login() {
if ($user = $this->get_loggedin_user()) {
return $user;
}
$this->redirect($this->get_login_url(self::current_url(), $this->in_frame()));
}
public function require_frame() {
if (!$this->in_frame()) {
$this->redirect($this->get_login_url(self::current_url(), true));
}
}
public static function get_facebook_url($subdomain='www') {
return 'http://' . $subdomain . '.facebook.com';
}
public function get_install_url($next=null) {
// this was renamed, keeping for compatibility's sake
return $this->get_add_url($next);
}
public function get_add_url($next=null) {
$page = self::get_facebook_url().'/add.php';
$params = array('api_key' => $this->api_key);
if ($next) {
$params['next'] = $next;
}
return $page . '?' . http_build_query($params);
}
public function get_login_url($next, $canvas) {
$page = self::get_facebook_url().'/login.php';
$params = array('api_key' => $this->api_key,
'v' => '1.0');
if ($next) {
$params['next'] = $next;
}
if ($canvas) {
$params['canvas'] = '1';
}
return $page . '?' . http_build_query($params);
}
public function get_logout_url($next) {
$page = self::get_facebook_url().'/logout.php';
$params = array('app_key' => $this->api_key,
'session_key' => $this->api_client->session_key);
if ($next) {
$params['connect_next'] = 1;
$params['next'] = $next;
}
return $page . '?' . http_build_query($params);
}
public function set_user($user, $session_key, $expires=null, $session_secret=null) {
if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user'])
|| $_COOKIE[$this->api_key . '_user'] != $user)) {
$this->set_cookies($user, $session_key, $expires, $session_secret);
}
$this->user = $user;
$this->api_client->session_key = $session_key;
$this->session_expires = $expires;
}
public function set_cookies($user, $session_key, $expires=null, $session_secret=null) {
$cookies = array();
$cookies['user'] = $user;
$cookies['session_key'] = $session_key;
if ($expires != null) {
$cookies['expires'] = $expires;
}
if ($session_secret != null) {
$cookies['ss'] = $session_secret;
}
foreach ($cookies as $name => $val) {
setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain);
$_COOKIE[$this->api_key . '_' . $name] = $val;
}
$sig = self::generate_sig($cookies, $this->secret);
setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain);
$_COOKIE[$this->api_key] = $sig;
if ($this->base_domain != null) {
$base_domain_cookie = 'base_domain_' . $this->api_key;
setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain);
$_COOKIE[$base_domain_cookie] = $this->base_domain;
}
}
/**
* Tries to undo the badness of magic quotes as best we can
* @param string $val Should come directly from $_GET, $_POST, etc.
* @return string val without added slashes
*/
public static function no_magic_quotes($val) {
if (get_magic_quotes_gpc()) {
return stripslashes($val);
} else {
return $val;
}
}
/*
* Get the signed parameters that were sent from Facebook. Validates the set
* of parameters against the included signature.
*
* Since Facebook sends data to your callback URL via unsecured means, the
* signature is the only way to make sure that the data actually came from
* Facebook. So if an app receives a request at the callback URL, it should
* always verify the signature that comes with against your own secret key.
* Otherwise, it's possible for someone to spoof a request by
* pretending to be someone else, i.e.:
* www.your-callback-url.com/?fb_user=10101
*
* This is done automatically by verify_fb_params.
*
* @param assoc $params a full array of external parameters.
* presumed $_GET, $_POST, or $_COOKIE
* @param int $timeout number of seconds that the args are good for.
* Specifically good for forcing cookies to expire.
* @param string $namespace prefix string for the set of parameters we want
* to verify. i.e., fb_sig or fb_post_sig
*
* @return assoc the subset of parameters containing the given prefix,
* and also matching the signature associated with them.
* OR an empty array if the params do not validate
*/
public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') {
$prefix = $namespace . '_';
$prefix_len = strlen($prefix);
$fb_params = array();
if (empty($params)) {
return array();
}
foreach ($params as $name => $val) {
// pull out only those parameters that match the prefix
// note that the signature itself ($params[$namespace]) is not in the list
if (strpos($name, $prefix) === 0) {
$fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val);
}
}
// validate that the request hasn't expired. this is most likely
// for params that come from $_COOKIE
if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) {
return array();
}
// validate that the params match the signature
$signature = isset($params[$namespace]) ? $params[$namespace] : null;
if (!$signature || (!$this->verify_signature($fb_params, $signature))) {
return array();
}
return $fb_params;
}
/**
* Validates the account that a user was trying to set up an
* independent account through Facebook Connect.
*
* @param user The user attempting to set up an independent account.
* @param hash The hash passed to the reclamation URL used.
* @return bool True if the user is the one that selected the
* reclamation link.
*/
public function verify_account_reclamation($user, $hash) {
return $hash == md5($user . $this->secret);
}
/**
* Validates that a given set of parameters match their signature.
* Parameters all match a given input prefix, such as "fb_sig".
*
* @param $fb_params an array of all Facebook-sent parameters,
* not including the signature itself
* @param $expected_sig the expected result to check against
*/
public function verify_signature($fb_params, $expected_sig) {
return self::generate_sig($fb_params, $this->secret) == $expected_sig;
}
/**
* Validate the given signed public session data structure with
* public key of the app that
* the session proof belongs to.
*
* @param $signed_data the session info that is passed by another app
* @param string $public_key Optional public key of the app. If this
* is not passed, function will make an API call to get it.
* return true if the session proof passed verification.
*/
public function verify_signed_public_session_data($signed_data,
$public_key = null) {
// If public key is not already provided, we need to get it through API
if (!$public_key) {
$public_key = $this->api_client->auth_getAppPublicKey(
$signed_data['api_key']);
}
// Create data to verify
$data_to_serialize = $signed_data;
unset($data_to_serialize['sig']);
$serialized_data = implode('_', $data_to_serialize);
// Decode signature
$signature = base64_decode($signed_data['sig']);
$result = openssl_verify($serialized_data, $signature, $public_key,
OPENSSL_ALGO_SHA1);
return $result == 1;
}
/*
* Generate a signature using the application secret key.
*
* The only two entities that know your secret key are you and Facebook,
* according to the Terms of Service. Since nobody else can generate
* the signature, you can rely on it to verify that the information
* came from Facebook.
*
* @param $params_array an array of all Facebook-sent parameters,
* NOT INCLUDING the signature itself
* @param $secret your app's secret key
*
* @return a hash to be checked against the signature provided by Facebook
*/
public static function generate_sig($params_array, $secret) {
$str = '';
ksort($params_array);
// Note: make sure that the signature parameter is not already included in
// $params_array.
foreach ($params_array as $k=>$v) {
$str .= "$k=$v";
}
$str .= $secret;
return md5($str);
}
public function encode_validationError($summary, $message) {
return json_encode(
array('errorCode' => FACEBOOK_API_VALIDATION_ERROR,
'errorTitle' => $summary,
'errorMessage' => $message));
}
public function encode_multiFeedStory($feed, $next) {
return json_encode(
array('method' => 'multiFeedStory',
'content' =>
array('next' => $next,
'feed' => $feed)));
}
public function encode_feedStory($feed, $next) {
return json_encode(
array('method' => 'feedStory',
'content' =>
array('next' => $next,
'feed' => $feed)));
}
public function create_templatizedFeedStory($title_template, $title_data=array(),
$body_template='', $body_data = array(), $body_general=null,
$image_1=null, $image_1_link=null,
$image_2=null, $image_2_link=null,
$image_3=null, $image_3_link=null,
$image_4=null, $image_4_link=null) {
return array('title_template'=> $title_template,
'title_data' => $title_data,
'body_template'=> $body_template,
'body_data' => $body_data,
'body_general' => $body_general,
'image_1' => $image_1,
'image_1_link' => $image_1_link,
'image_2' => $image_2,
'image_2_link' => $image_2_link,
'image_3' => $image_3,
'image_3_link' => $image_3_link,
'image_4' => $image_4,
'image_4_link' => $image_4_link);
}
}

View File

@@ -0,0 +1,104 @@
<?php
// Copyright 2004-2009 Facebook. All Rights Reserved.
//
// +---------------------------------------------------------------------------+
// | Facebook Platform PHP5 client |
// +---------------------------------------------------------------------------+
// | Copyright (c) 2007 Facebook, Inc. |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | 1. Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | 2. Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
// | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
// | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
// | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
// | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
// | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
// | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// +---------------------------------------------------------------------------+
// | For help with this library, contact developers-help@facebook.com |
// +---------------------------------------------------------------------------+
//
/**
* This class extends and modifies the "Facebook" class to better
* suit desktop apps.
*/
class FacebookDesktop extends Facebook {
// the application secret, which differs from the session secret
public $app_secret;
public $verify_sig;
public function __construct($api_key, $secret) {
$this->app_secret = $secret;
$this->verify_sig = false;
parent::__construct($api_key, $secret);
}
public function do_get_session($auth_token) {
$this->api_client->secret = $this->app_secret;
$this->api_client->session_key = null;
$session_info = parent::do_get_session($auth_token);
if (!empty($session_info['secret'])) {
// store the session secret
$this->set_session_secret($session_info['secret']);
}
return $session_info;
}
public function set_session_secret($session_secret) {
$this->secret = $session_secret;
$this->api_client->secret = $session_secret;
}
public function require_login() {
if ($this->get_loggedin_user()) {
try {
// try a session-based API call to ensure that we have the correct
// session secret
$user = $this->api_client->users_getLoggedInUser();
// now that we have a valid session secret, verify the signature
$this->verify_sig = true;
if ($this->validate_fb_params(false)) {
return $user;
} else {
// validation failed
return null;
}
} catch (FacebookRestClientException $ex) {
if (isset($_GET['auth_token'])) {
// if we have an auth_token, use it to establish a session
$session_info = $this->do_get_session($_GET['auth_token']);
if ($session_info) {
return $session_info['uid'];
}
}
}
}
// if we get here, we need to redirect the user to log in
$this->redirect($this->get_login_url(self::current_url(), $this->in_fb_canvas()));
}
public function verify_signature($fb_params, $expected_sig) {
// we don't want to verify the signature until we have a valid
// session secret
if ($this->verify_sig) {
return parent::verify_signature($fb_params, $expected_sig);
} else {
return true;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,806 @@
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* Converts to and from JSON format.
*
* JSON (JavaScript Object Notation) is a lightweight data-interchange
* format. It is easy for humans to read and write. It is easy for machines
* to parse and generate. It is based on a subset of the JavaScript
* Programming Language, Standard ECMA-262 3rd Edition - December 1999.
* This feature can also be found in Python. JSON is a text format that is
* completely language independent but uses conventions that are familiar
* to programmers of the C-family of languages, including C, C++, C#, Java,
* JavaScript, Perl, TCL, and many others. These properties make JSON an
* ideal data-interchange language.
*
* This package provides a simple encoder and decoder for JSON notation. It
* is intended for use with client-side Javascript applications that make
* use of HTTPRequest to perform server communication functions - data can
* be encoded into JSON notation for use in a client-side javascript, or
* decoded from incoming Javascript requests. JSON format is native to
* Javascript, and can be directly eval()'ed with no further parsing
* overhead
*
* All strings should be in ASCII or UTF-8 format!
*
* LICENSE: Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met: Redistributions of source code must retain the
* above copyright notice, this list of conditions and the following
* disclaimer. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* @category
* @package Services_JSON
* @author Michal Migurski <mike-json@teczno.com>
* @author Matt Knapp <mdknapp[at]gmail[dot]com>
* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
* @copyright 2005 Michal Migurski
* @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
* @license http://www.opensource.org/licenses/bsd-license.php
* @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198
*/
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_SLICE', 1);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_STR', 2);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_ARR', 3);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_OBJ', 4);
/**
* Marker constant for Services_JSON::decode(), used to flag stack state
*/
define('SERVICES_JSON_IN_CMT', 5);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_LOOSE_TYPE', 16);
/**
* Behavior switch for Services_JSON::decode()
*/
define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
/**
* Converts to and from JSON format.
*
* Brief example of use:
*
* <code>
* // create a new instance of Services_JSON
* $json = new Services_JSON();
*
* // convert a complexe value to JSON notation, and send it to the browser
* $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
* $output = $json->encode($value);
*
* print($output);
* // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
*
* // accept incoming POST data, assumed to be in JSON notation
* $input = file_get_contents('php://input', 1000000);
* $value = $json->decode($input);
* </code>
*/
class Services_JSON
{
/**
* constructs a new JSON instance
*
* @param int $use object behavior flags; combine with boolean-OR
*
* possible values:
* - SERVICES_JSON_LOOSE_TYPE: loose typing.
* "{...}" syntax creates associative arrays
* instead of objects in decode().
* - SERVICES_JSON_SUPPRESS_ERRORS: error suppression.
* Values which can't be encoded (e.g. resources)
* appear as NULL instead of throwing errors.
* By default, a deeply-nested resource will
* bubble up with an error, so all return values
* from encode() should be checked with isError()
*/
function Services_JSON($use = 0)
{
$this->use = $use;
}
/**
* convert a string from one UTF-16 char to one UTF-8 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
* @access private
*/
function utf162utf8($utf16)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
switch(true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* convert a string from one UTF-8 char to one UTF-16 char
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibye string extension.
*
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
* @access private
*/
function utf82utf16($utf8)
{
// oh please oh please oh please oh please oh please
if(function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch(strlen($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8{0}) >> 2))
. chr((0xC0 & (ord($utf8{0}) << 6))
| (0x3F & ord($utf8{1})));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8{0}) << 4))
| (0x0F & (ord($utf8{1}) >> 2)))
. chr((0xC0 & (ord($utf8{1}) << 6))
| (0x7F & ord($utf8{2})));
}
// ignoring UTF-32 for now, sorry
return '';
}
/**
* encodes an arbitrary variable into JSON format
*
* @param mixed $var any number, boolean, string, array, or object to be encoded.
* see argument 1 to Services_JSON() above for array-parsing behavior.
* if var is a strng, note that encode() always expects it
* to be in ASCII or UTF-8 format!
*
* @return mixed JSON string representation of input var or an error if a problem occurs
* @access public
*/
function encode($var)
{
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false';
case 'NULL':
return 'null';
case 'integer':
return (int) $var;
case 'double':
case 'float':
return (float) $var;
case 'string':
// STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
$ascii = '';
$strlen_var = strlen($var);
/*
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($c = 0; $c < $strlen_var; ++$c) {
$ord_var_c = ord($var{$c});
switch (true) {
case $ord_var_c == 0x08:
$ascii .= '\b';
break;
case $ord_var_c == 0x09:
$ascii .= '\t';
break;
case $ord_var_c == 0x0A:
$ascii .= '\n';
break;
case $ord_var_c == 0x0C:
$ascii .= '\f';
break;
case $ord_var_c == 0x0D:
$ascii .= '\r';
break;
case $ord_var_c == 0x22:
case $ord_var_c == 0x2F:
case $ord_var_c == 0x5C:
// double quote, slash, slosh
$ascii .= '\\'.$var{$c};
break;
case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $var{$c};
break;
case (($ord_var_c & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c, ord($var{$c + 1}));
$c += 1;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}));
$c += 2;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}));
$c += 3;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}),
ord($var{$c + 4}));
$c += 4;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ord_var_c & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ord_var_c,
ord($var{$c + 1}),
ord($var{$c + 2}),
ord($var{$c + 3}),
ord($var{$c + 4}),
ord($var{$c + 5}));
$c += 5;
$utf16 = $this->utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return '"'.$ascii.'"';
case 'array':
/*
* As per JSON spec if any array key is not an integer
* we must treat the the whole array as an object. We
* also try to catch a sparsely populated associative
* array with numeric keys here because some JS engines
* will create an array with empty indexes up to
* max_index which can cause memory issues and because
* the keys, which may be relevant, will be remapped
* otherwise.
*
* As per the ECMA and JSON specification an object may
* have any string as a property. Unfortunately due to
* a hole in the ECMA specification if the key is a
* ECMA reserved word or starts with a digit the
* parameter is only accessible using ECMAScript's
* bracket notation.
*/
// treat as a JSON object
if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
$properties = array_map(array($this, 'name_value'),
array_keys($var),
array_values($var));
foreach($properties as $property) {
if(Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
}
// treat it like a regular array
$elements = array_map(array($this, 'encode'), $var);
foreach($elements as $element) {
if(Services_JSON::isError($element)) {
return $element;
}
}
return '[' . join(',', $elements) . ']';
case 'object':
$vars = get_object_vars($var);
$properties = array_map(array($this, 'name_value'),
array_keys($vars),
array_values($vars));
foreach($properties as $property) {
if(Services_JSON::isError($property)) {
return $property;
}
}
return '{' . join(',', $properties) . '}';
default:
return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
? 'null'
: new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
}
}
/**
* array-walking function for use in generating JSON-formatted name-value pairs
*
* @param string $name name of key to use
* @param mixed $value reference to an array element to be encoded
*
* @return string JSON-formatted name-value pair, like '"name":value'
* @access private
*/
function name_value($name, $value)
{
$encoded_value = $this->encode($value);
if(Services_JSON::isError($encoded_value)) {
return $encoded_value;
}
return $this->encode(strval($name)) . ':' . $encoded_value;
}
/**
* reduce a string by removing leading and trailing comments and whitespace
*
* @param $str string string value to strip of comments and whitespace
*
* @return string string value stripped of comments and whitespace
* @access private
*/
function reduce_string($str)
{
$str = preg_replace(array(
// eliminate single line comments in '// ...' form
'#^\s*//(.+)$#m',
// eliminate multi-line comments in '/* ... */' form, at start of string
'#^\s*/\*(.+)\*/#Us',
// eliminate multi-line comments in '/* ... */' form, at end of string
'#/\*(.+)\*/\s*$#Us'
), '', $str);
// eliminate extraneous space
return trim($str);
}
/**
* decodes a JSON string into appropriate variable
*
* @param string $str JSON-formatted string
*
* @return mixed number, boolean, string, array, or object
* corresponding to given JSON input string.
* See argument 1 to Services_JSON() above for object-output behavior.
* Note that decode() always returns strings
* in ASCII or UTF-8 format!
* @access public
*/
function decode($str)
{
$str = $this->reduce_string($str);
switch (strtolower($str)) {
case 'true':
return true;
case 'false':
return false;
case 'null':
return null;
default:
$m = array();
if (is_numeric($str)) {
// Lookie-loo, it's a number
// This would work on its own, but I'm trying to be
// good about returning integers where appropriate:
// return (float)$str;
// Return float or int, as appropriate
return ((float)$str == (integer)$str)
? (integer)$str
: (float)$str;
} elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
// STRINGS RETURNED IN UTF-8 FORMAT
$delim = substr($str, 0, 1);
$chrs = substr($str, 1, -1);
$utf8 = '';
$strlen_chrs = strlen($chrs);
for ($c = 0; $c < $strlen_chrs; ++$c) {
$substr_chrs_c_2 = substr($chrs, $c, 2);
$ord_chrs_c = ord($chrs{$c});
switch (true) {
case $substr_chrs_c_2 == '\b':
$utf8 .= chr(0x08);
++$c;
break;
case $substr_chrs_c_2 == '\t':
$utf8 .= chr(0x09);
++$c;
break;
case $substr_chrs_c_2 == '\n':
$utf8 .= chr(0x0A);
++$c;
break;
case $substr_chrs_c_2 == '\f':
$utf8 .= chr(0x0C);
++$c;
break;
case $substr_chrs_c_2 == '\r':
$utf8 .= chr(0x0D);
++$c;
break;
case $substr_chrs_c_2 == '\\"':
case $substr_chrs_c_2 == '\\\'':
case $substr_chrs_c_2 == '\\\\':
case $substr_chrs_c_2 == '\\/':
if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
($delim == "'" && $substr_chrs_c_2 != '\\"')) {
$utf8 .= $chrs{++$c};
}
break;
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
. chr(hexdec(substr($chrs, ($c + 4), 2)));
$utf8 .= $this->utf162utf8($utf16);
$c += 5;
break;
case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
$utf8 .= $chrs{$c};
break;
case ($ord_chrs_c & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 2);
++$c;
break;
case ($ord_chrs_c & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 3);
$c += 2;
break;
case ($ord_chrs_c & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 4);
$c += 3;
break;
case ($ord_chrs_c & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 5);
$c += 4;
break;
case ($ord_chrs_c & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $c, 6);
$c += 5;
break;
}
}
return $utf8;
} elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
// array, or object notation
if ($str{0} == '[') {
$stk = array(SERVICES_JSON_IN_ARR);
$arr = array();
} else {
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = array();
} else {
$stk = array(SERVICES_JSON_IN_OBJ);
$obj = new stdClass();
}
}
array_push($stk, array('what' => SERVICES_JSON_SLICE,
'where' => 0,
'delim' => false));
$chrs = substr($str, 1, -1);
$chrs = $this->reduce_string($chrs);
if ($chrs == '') {
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} else {
return $obj;
}
}
//print("\nparsing {$chrs}\n");
$strlen_chrs = strlen($chrs);
for ($c = 0; $c <= $strlen_chrs; ++$c) {
$top = end($stk);
$substr_chrs_c_2 = substr($chrs, $c, 2);
if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
$slice = substr($chrs, $top['where'], ($c - $top['where']));
array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
//print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
if (reset($stk) == SERVICES_JSON_IN_ARR) {
// we are in an array, so just push an element onto the stack
array_push($arr, $this->decode($slice));
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
$parts = array();
if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// "name":value pair
$key = $this->decode($parts[1]);
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
} elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
// name:value pair, where name is unquoted
$key = $parts[1];
$val = $this->decode($parts[2]);
if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
$obj[$key] = $val;
} else {
$obj->$key = $val;
}
}
}
} elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
// found a quote, and we are not inside a string
array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
//print("Found start of string at {$c}\n");
} elseif (($chrs{$c} == $top['delim']) &&
($top['what'] == SERVICES_JSON_IN_STR) &&
((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
// found a quote, we're in a string, and it's not escaped
// we know that it's not escaped becase there is _not_ an
// odd number of backslashes at the end of the string so far
array_pop($stk);
//print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '[') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-bracket, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
//print("Found start of array at {$c}\n");
} elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
// found a right-bracket, and we're in an array
array_pop($stk);
//print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($chrs{$c} == '{') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a left-brace, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
//print("Found start of object at {$c}\n");
} elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
// found a right-brace, and we're in an object
array_pop($stk);
//print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
} elseif (($substr_chrs_c_2 == '/*') &&
in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
// found a comment start, and we are in an array, object, or slice
array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
$c++;
//print("Found start of comment at {$c}\n");
} elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
// found a comment end, and we're in one now
array_pop($stk);
$c++;
for ($i = $top['where']; $i <= $c; ++$i)
$chrs = substr_replace($chrs, ' ', $i, 1);
//print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
}
}
if (reset($stk) == SERVICES_JSON_IN_ARR) {
return $arr;
} elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
return $obj;
}
}
}
}
/**
* @todo Ultimately, this should just call PEAR::isError()
*/
function isError($data, $code = null)
{
if (class_exists('pear')) {
return PEAR::isError($data, $code);
} elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
is_subclass_of($data, 'services_json_error'))) {
return true;
}
return false;
}
}
if (class_exists('PEAR_Error')) {
class Services_JSON_Error extends PEAR_Error
{
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
}
}
} else {
/**
* @todo Ultimately, this class shall be descended from PEAR_Error
*/
class Services_JSON_Error
{
function Services_JSON_Error($message = 'unknown error', $code = null,
$mode = null, $options = null, $userinfo = null)
{
}
}
}
?>

View File

@@ -0,0 +1,21 @@
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,6 @@
<?php
# In PHP 5.2 or higher we don't need to bring this in
if (!function_exists('json_encode')) {
require_once 'jsonwrapper_inner.php';
}
?>

View File

@@ -0,0 +1,23 @@
<?php
require_once 'JSON/JSON.php';
function json_encode($arg)
{
global $services_json;
if (!isset($services_json)) {
$services_json = new Services_JSON();
}
return $services_json->encode($arg);
}
function json_decode($arg)
{
global $services_json;
if (!isset($services_json)) {
$services_json = new Services_JSON();
}
return $services_json->decode($arg);
}
?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB