3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2010, StatusNet, Inc.
6 * A plugin for single-sign-in (SSO) with Facebook
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 * @author Zach Copley <zach@status.net>
26 * @copyright 2010 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET')) {
35 define("FACEBOOK_SERVICE", 2);
38 * Main class for Facebook single-sign-on plugin
41 * Simple plugins can be implemented as a single module. Others are more complex
42 * and require additional modules; these should use their own directory, like
43 * 'local/plugins/{$name}/'. All files related to the plugin, including images,
44 * JavaScript, CSS, external libraries or PHP modules should go in the plugin
49 * @author Zach Copley <zach@status.net>
50 * @copyright 2010 StatusNet, Inc.
51 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
52 * @link http://status.net/
54 class FacebookSSOPlugin extends Plugin
56 public $appId = null; // Facebook application ID
57 public $apikey = null; // Facebook API key (for deprecated "Old REST API")
58 public $secret = null; // Facebook application secret
59 public $facebook = null; // Facebook application instance
60 public $dir = null; // Facebook SSO plugin dir
63 * Initializer for this plugin
65 * Plugins overload this method to do any initialization they need,
66 * like connecting to remote servers or creating paths or so on.
68 * @return boolean hook value; true means continue processing, false means stop.
72 $this->facebook = Facebookclient::getFacebook(
82 * Cleanup for this plugin
84 * Plugins overload this method to do any cleanup they need,
85 * like disconnecting from remote servers or deleting temp files or so on.
87 * @return boolean hook value; true means continue processing, false means stop.
95 * Load related modules when needed
97 * Most non-trivial plugins will require extra modules to do their work. Typically
98 * these include data classes, action classes, widget classes, or external libraries.
100 * This method receives a class name and loads the PHP file related to that class. By
101 * tradition, action classes typically have files named for the action, all lower-case.
102 * Data classes are in files with the data class name, initial letter capitalized.
104 * Note that this method will be called for *all* overloaded classes, not just ones
105 * in this plugin! So, make sure to return true by default to let other plugins, and
106 * the core code, get a chance.
108 * @param string $cls Name of the class to be loaded
110 * @return boolean hook value; true means continue processing, false means stop.
112 function onAutoload($cls)
115 $dir = dirname(__FILE__);
117 //common_debug("class = " . $cls);
121 case 'Facebook': // New JavaScript SDK
122 include_once $dir . '/extlib/facebook.php';
124 case 'FacebookRestClient': // Old REST lib
125 include_once $dir . '/extlib/facebookapi_php5_restlib.php';
127 case 'FacebookloginAction':
128 case 'FacebookregisterAction':
129 case 'FacebookadminpanelAction':
130 case 'FacebooksettingsAction':
131 include_once $dir . '/actions/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
133 case 'Facebookclient':
134 case 'Facebookqueuehandler':
135 include_once $dir . '/lib/' . strtolower($cls) . '.php';
144 * Does this $action need the Facebook JavaScripts?
146 function needsScripts($action)
148 static $needy = array(
149 'FacebookloginAction',
150 'FacebookregisterAction',
151 'FacebookadminpanelAction',
152 'FacebooksettingsAction'
155 if (in_array(get_class($action), $needy)) {
163 * Map URLs to actions
165 * This event handler lets the plugin map URLs on the site to actions (and
166 * thus an action handler class). Note that the action handler class for an
167 * action will be named 'FoobarAction', where action = 'foobar'. The class
168 * must be loaded in the onAutoload() method.
170 * @param Net_URL_Mapper $m path-to-action mapper
172 * @return boolean hook value; true means continue processing, false means stop.
174 function onRouterInitialized($m)
176 // Always add the admin panel route
177 $m->connect('admin/facebook', array('action' => 'facebookadminpanel'));
179 // Only add these routes if an application has been setup on
180 // Facebook for the plugin to use.
181 if ($this->hasApplication()) {
184 'main/facebooklogin',
185 array('action' => 'facebooklogin')
188 'main/facebookregister',
189 array('action' => 'facebookregister')
194 array('action' => 'facebooksettings')
203 * Add a login tab for Facebook, but only if there's a Facebook
204 * application defined for the plugin to use.
206 * @param Action &action the current action
210 function onEndLoginGroupNav(&$action)
212 $action_name = $action->trimmed('action');
214 if ($this->hasApplication()) {
217 common_local_url('facebooklogin'),
218 _m('MENU', 'Facebook'),
219 // TRANS: Tooltip for menu item "Facebook".
220 _m('Login or register using Facebook'),
221 'facebooklogin' === $action_name
229 * Add a Facebook tab to the admin panels
231 * @param Widget $nav Admin panel nav
233 * @return boolean hook value
235 function onEndAdminPanelNav($nav)
237 if (AdminPanelAction::canAdmin('facebook')) {
239 $action_name = $nav->action->trimmed('action');
242 common_local_url('facebookadminpanel'),
244 _m('MENU','Facebook'),
245 // TRANS: Tooltip for menu item "Facebook".
246 _m('Facebook integration configuration'),
247 $action_name == 'facebookadminpanel',
248 'nav_facebook_admin_panel'
256 * Add a tab for user-level Facebook settings
258 * @param Action &action the current action
262 function onEndConnectSettingsNav(&$action)
264 if ($this->hasApplication()) {
265 $action_name = $action->trimmed('action');
268 common_local_url('facebooksettings'),
269 // TRANS: Menu item tab.
270 _m('MENU','Facebook'),
271 // TRANS: Tooltip for menu item "Facebook".
272 _m('Facebook settings'),
273 $action_name === 'facebooksettings'
281 * Is there a Facebook application for the plugin to use?
283 function hasApplication()
285 if (!empty($this->facebook)) {
287 $appId = $this->facebook->getAppId();
288 $secret = $this->facebook->getApiSecret();
290 if (!empty($appId) && !empty($secret)) {
299 function onStartShowHeader($action)
301 if ($this->needsScripts($action)) {
303 // output <div id="fb-root"></div> as close to <body> as possible
304 $action->element('div', array('id' => 'fb-root'));
306 $dir = dirname(__FILE__);
308 $script = <<<ENDOFSCRIPT
309 window.fbAsyncInit = function() {
313 session : %s, // don't refetch the session when PHP already has it
314 status : true, // check login status
315 cookie : true, // enable cookies to allow the server to access the session
316 xfbml : true // parse XFBML
319 // whenever the user logs in, refresh the page
323 window.location.reload();
329 var e = document.createElement('script');
330 e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
332 document.getElementById('fb-root').appendChild(e);
336 $action->inlineScript(
338 json_encode($this->facebook->getAppId()),
339 json_encode($this->facebook->getSession())
348 * Log the user out of Facebook, per the Facebook authentication guide
350 * @param Action action the action
352 function onEndLogout($action)
354 if ($this->hasApplication()) {
355 $session = $this->facebook->getSession();
361 $fbuid = $this->facebook->getUser();
362 $fbuser = $this->facebook->api('/me');
363 } catch (FacebookApiException $e) {
364 common_log(LOG_ERROR, $e, __FILE__);
368 if (!empty($fbuser)) {
370 $logoutUrl = $this->facebook->getLogoutUrl(
371 array('next' => common_local_url('public'))
377 "Logging user out of Facebook (fbuid = %s)",
382 common_debug("LOGOUT URL = $logoutUrl");
383 common_redirect($logoutUrl, 303);
390 * Add fbml namespace so Facebook's JavaScript SDK can parse and render
391 * XFBML tags (e.g: <fb:login-button>)
393 * @param Action $action current action
394 * @param array $attrs array of attributes for the HTML tag
398 function onStartHtmlElement($action, $attrs) {
400 if ($this->needsScripts($action)) {
401 $attrs = array_merge(
403 array('xmlns:fb' => 'http://www.facebook.com/2008/fbml')
411 * Add version info for this plugin
413 * @param array &$versions plugin version descriptions
415 function onPluginVersion(&$versions)
418 'name' => 'Facebook Single-Sign-On',
419 'version' => STATUSNET_VERSION,
420 'author' => 'Craig Andrews, Zach Copley',
421 'homepage' => 'http://status.net/wiki/Plugin:FacebookSSO',
423 _m('A plugin for integrating StatusNet with Facebook.')