Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Checked in FBConnect r91 from 2008 under /tags for archival purposes

  • Loading branch information...
commit 1bfadd6a1850e0112df041105edf1008492873d4 0 parents
@garbear authored
21 FBConnect.alias.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * FBConnect.alias.php - FBConnect for MediaWiki
+ *
+ * Special Page alias file... for when we actually define some special pages ;-)
+ */
+
+
+/*
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+$aliases = array();
+
+/** English */
+$aliases['en'] = array(
+ 'Connect' => array( 'Connect', 'ConnectAccount' ),
+);
77 FBConnect.i18n.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * FBConnect.i18n.php - FBConnect for MediaWiki
+ *
+ * Internationalization file... for when Facebook Connect is internationalized ;-)
+ */
+
+
+/*
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+$messages = array();
+
+if (!defined( 'fb' )) {
+ define( 'fb', 'fbconnect-' );
+}
+
+
+/** English */
+$messages['en'] = array(
+// Extension name
+'fbconnect' => 'Facebook Connect',
+fb.'desc' => 'Enables users to [[Special:Connect|Connect]] with their [http://www.facebook.com Facebook] ' .
+ 'accounts. Offers authentification based on Facebook groups (soon!) and the use of FBML in wiki text.',
+// Group containing Facebook Connect users
+'group-fb-user' => 'Facebook Connect users',
+'group-fb-user-member' => 'Facebook Connect user',
+'grouppage-fb-user' => '{{ns:project}}:Facebook Connect users',
+// Group for Facebook Connect users beloning to the group specified by $fbUserRightsFromGroup
+'group-fb-groupie' => 'Group members',
+'group-fb-groupie-member' => 'Group member',
+'grouppage-fb-groupie' => '{{ns:project}}:Group members',
+// Officers of the Facebook group
+'group-fb-officer' => 'Group officers',
+'group-fb-officer-member' => 'Group officer',
+'grouppage-fb-officer' => '{{ns:project}}:Group officers',
+// Admins of the Facebook group
+'group-fb-admin' => 'Group admins',
+'group-fb-admin-member' => 'Group administrator',
+'grouppage-fb-admin' => '{{ns:project}}:Group admins',
+// Incredibly good looking people
+'right-goodlooking' => 'Really, really, ridiculously good looking',
+// Personal toolbar
+fb.'connect' => 'Connect this account with Facebook',
+fb.'logout' => 'Logout of Facebook',
+fb.'link' => 'Back to facebook.com',
+// Special:Connect
+fb.'title' => 'Connect account with Facebook',
+fb.'intro' => 'This wiki is enabled with Facebook Connect, the next evolution of Facebook Platform. This means ' .
+ 'that when you are Connected, in addition to the normal [[Wikipedia:Help:Logging in#Why log in?' .
+ '|benefits]] you see when logging in, you will be able to take advantage of some extra features...',
+fb.'conv' => 'Convenience',
+fb.'convdesc' => 'Connected users are automatically logged you in. If permission is given, then this wiki can even ' .
+ 'use Facebook as an email proxy so you can continue to receive important notifications without ' .
+ 'revealing your email address.',
+fb.'fbml' => 'Facebook Markup Language',
+fb.'fbmldesc' => 'Facebook has provided a bunch of built-in tags that will render dynamic data. Many of these tags ' .
+ 'can be included in wiki text, and will be rendered differently depending on which Connected user ' .
+ 'they are being viewed by.',
+fb.'comm' => 'Communication',
+fb.'commdesc' => 'Facebook Connect ushers in a whole new level of networking. See which of your friends are using ' .
+ 'the wiki, and optionally share your actions with your friends through the Facebook News Feed.',
+fb.'welcome' => 'Welcome, Facebook Connect user!',
+fb.'loginbox' => "Or '''login''' with Facebook:\n\n$1",
+fb.'merge' => 'Merge your wiki account with your Facebook ID',
+fb.'mergebox' => 'This feature has not yet been implemented. Accounts can be merged manually with [[Special:' .
+ 'Renameuser]] if it is installed. For more information, please visit [[MediaWikiWiki:Extension:' .
+ "Renameuser|Extension:Renameuser]].\n\n\n$1\n\n\nNote: This can be undone by a sysop.",
+fb.'logoutbox'=> "$1\n\nThis will also log you out of Facebook and all Connected sites, including this wiki.",
+fb.'listusers-header'
+ => '$1 and $2 privileges are automatically transfered from the Officer and Admin titles of the ' .
+ "Facebook group $3.\n\nFor more info, please contact the group creator $4.",
+);
180 FBConnect.php
@@ -0,0 +1,180 @@
+<?php
+/**
+ * Copyright © 2008 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @author Garrett Bruin
+ * @copyright Copyright © 2008 Garrett Brown
+ * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
+ * @addtogroup Extensions
+ *
+ *
+ * FBConnect plugin. Integrates Facebook Connect into MediaWiki.
+ *
+ * Facebook Connect single sign on (SSO) experience and XFBML are currently available.
+ * Please rename config.sample.php to config.php, follow the instructions inside and
+ * customize variables as you like to set up your Facebook Connect extension.
+ *
+ * Info is available at http://www.mediawiki.org/wiki/Extension:FBConnect
+ * and at http://wiki.developers.facebook.com/index.php/MediaWiki
+ *
+ * Limited support is available at http://www.mediawiki.org/wiki/Extension_talk:FBConnect
+ * and at http://wiki.developers.facebook.com/index.php/Talk:MediaWiki
+ *
+ */
+
+
+/**
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' )) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+/**
+ * FBConnect version. Note: this is not necessarily the most recent SVN revision number.
+ */
+define( 'MEDIAWIKI_FBCONNECT_VERSION', 'r71, January 25, 2009' );
+
+/**
+ * Add information about this extension to Special:Version.
+ */
+$wgExtensionCredits['specialpage'][] = array(
+ 'name' => 'Facebook Connect Plugin',
+ 'author' => 'Garrett Brown',
+ 'url' => 'http://www.mediawiki.org/wiki/Extension:FBConnect',
+ 'descriptionmsg' => 'fbconnect-desc',
+ 'version' => MEDIAWIKI_FBCONNECT_VERSION,
+);
+
+/**
+ * Initialization of the autoloaders and special extension pages.
+ */
+$dir = dirname(__FILE__) . '/';
+require_once $dir . 'config.php';
+require_once $dir . 'facebook-client/facebook.php';
+
+$wgExtensionMessagesFiles['FBConnect'] = $dir . 'FBConnect.i18n.php';
+$wgExtensionAliasesFiles['FBConnect'] = $dir . 'FBConnect.alias.php';
+
+$wgAutoloadClasses['FBConnectAPI'] = $dir . 'FBConnectAPI.php';
+$wgAutoloadClasses['FBConnectAuthPlugin'] = $dir . 'FBConnectAuthPlugin.php';
+$wgAutoloadClasses['FBConnectHooks'] = $dir . 'FBConnectHooks.php';
+$wgAutoloadClasses['FBConnectXFBML'] = $dir . 'FBConnectXFBML.php';
+$wgAutoloadClasses['SpecialConnect'] = $dir . 'SpecialConnect.php';
+
+$wgSpecialPages['Connect'] = 'SpecialConnect';
+#$wgSpecialPages['NewsFeed'] = 'SpecialNewsFeed';
+
+$wgExtensionFunctions[] = 'FBConnect::init';
+
+// If we are configured to pull group info from Facebook, then create the group permissions
+$wgGroupPermissions['fb-user'] = $wgGroupPermissions['user'];
+if( $fbUserRightsFromGroup ) {
+ $wgGroupPermissions['fb-groupie'] = $wgGroupPermissions['user'];
+ $wgGroupPermissions['fb-officer'] = $wgGroupPermissions['bureaucrat'];
+ $wgGroupPermissions['fb-admin'] = $wgGroupPermissions['sysop'];
+ $wgGroupPermissions['fb-officer']['goodlooking'] = true;
+ $wgImplictGroups[] = 'fb-groupie';
+ $wgImplictGroups[] = 'fb-officer';
+ $wgImplictGroups[] = 'fb-admin';
+}
+
+/**/
+// Define new autopromote condition (use quoted text, numbers can cause collisions)
+define( 'APCOND_FB_INGROUP', 'fb*g' );
+define( 'APCOND_FB_ISOFFICER', 'fb*o' );
+define( 'APCOND_FB_ISADMIN', 'fb*a' );
+
+$wgAutopromote['fb-groupie'] = APCOND_FB_INGROUP;
+$wgAutopromote['fb-officer'] = APCOND_FB_ISOFFICER;
+$wgAutopromote['fb-admin'] = APCOND_FB_ISADMIN;
+
+/**
+$wgAutopromote['autoconfirmed'] = array( '&', array( APCOND_EDITCOUNT, &$wgAutoConfirmCount ),
+ array( APCOND_AGE, &$wgAutoConfirmAge ),
+ array( APCOND_FB_INGROUP ));
+/**/
+
+$wgImplicitGroups[] = 'fb-groupie';
+$wgImplicitGroups[] = 'fb-officer';
+$wgImplicitGroups[] = 'fb-admin';
+
+
+/**
+ * Class FBConnect
+ *
+ * This class initializes the extension, and contains the core non-hook,
+ * non-authentification code.
+ */
+class FBConnect {
+ // Instance of our Facebook API class
+ public static $api;
+ // Whether we are rendering the Special:Connect page
+ public static $special_connect;
+
+ /**
+ * Initializes and configures the extension.
+ */
+ public static function init() {
+ global $wgXhtmlNamespaces, $wgAuth, $wgHooks;
+
+ self::$special_connect = false;
+ self::$api = new FBConnectAPI();
+
+ // The xmlns:fb attribute is required for proper rendering on IE
+ $wgXhtmlNamespaces['fb'] = 'http://www.facebook.com/2008/fbml';
+
+ // Set the global variable $wgAuth to our custom authentification plugin.
+ // The AuthPluginSetup hook is called right before init(), so we can't use this hook
+ $wgAuth = new StubObject( 'wgAuth', 'FBConnectAuthPlugin' );
+
+ // Install all public static functions in class FBConnectHooks as MediaWiki hooks
+ $hooks = self::enumMethods('FBConnectHooks');
+ foreach( $hooks as $hookName ) {
+ $wgHooks[$hookName][] = "FBConnectHooks::$hookName";
+ }
+
+ // ParserFirstCallInit was introduced in modern (1.12+) MW versions so as to
+ // avoid unstubbing $wgParser on setHook() too early, as per r35980
+ if (!defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' )) {
+ global $wgParser;
+ wfRunHooks( 'ParserFirstCallInit', $wgParser );
+ }
+ }
+
+ /**
+ * Returns an array with the names of all public static functions
+ * in the specified class.
+ */
+ public static function enumMethods( $className ) {
+ $hooks = array();
+ try {
+ $class = new ReflectionClass( $className );
+ foreach( $class->getMethods( ReflectionMethod::IS_PUBLIC ) as $method ) {
+ if ( $method->isStatic() ) {
+ $hooks[] = $method->getName();
+ }
+ }
+ } catch( Exception $e ) {
+ // If PHP's version doesn't support the Reflection API, then exit
+ die( 'PHP version (' . phpversion() . ') must be great enough to support the Reflection API' );
+ // or...
+ $hooks = array('AuthPluginSetup', 'UserLoadFromSession',
+ 'RenderPreferencesForm', 'PersonalUrls',
+ 'ParserAfterTidy', 'BeforePageDisplay');
+ }
+ return $hooks;
+ }
+}
405 FBConnectAPI.php
@@ -0,0 +1,405 @@
+<?php
+/**
+ * Copyright © 2008 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/**
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+
+/**
+ * Class FBConnectAPI
+ *
+ * This class contains the code used to interface with Facebook via the
+ * Facebook Platform API.
+ */
+class FBConnectAPI {
+ // Stores a list of valid Facebook IDs for adding tooltip info
+ private $ids = array();
+
+ private $pre = '';
+ private $post = '';
+
+ public function __construct() {
+ global $fbUserName, $fbCheckUserNames;
+
+ // If $fbUserName is set, parse it to determine the prefix and suffix
+ if ( isset( $fbUserName ) && $fbUserName !== '' ) {
+ $this->setPrePost( $fbUserName );
+ }
+
+ // Check for user name conflicts if $fbUserNameOK is set to its default value of false
+ if ( isset( $fbCheckUserNames ) && $fbCheckUserNames ) {
+ $this->checkUserNameConflicts();
+ }
+ }
+
+ /**
+ * Sets $pre and $post by parsing $fbUserName.
+ */
+ private function setPrePost( $fbUserName ) {
+ // The first letter of wiki user names must be capitalized
+ global $wgContLang;
+ $fbUserName = $wgContLang->ucfirst( $fbUserName );
+ // Explode $fbUserName around the # if it exists
+ if ( strpos( $fbUserName, '#' ) === false ) {
+ $this->pre = $fbUserName;
+ } else if ( strpos( $fbUserName, '#' ) == 0 ) {
+ $this->post = substr( $fbUserName, 1 );
+ } else {
+ $modifiers = explode( '#', $fbUserName );
+ $this->pre = $modifiers[0];
+ if ( count( $modifiers ) > 1 ) {
+ $this->post = $modifiers[count( $modifiers ) - 1];
+ }
+ }
+ // Make sure $pre and $post are valid (replace '_' with a space)
+ $u = User::newFromName( $this->pre . '18446744073709551614' . $this->post, 'creatable' );
+ if ( is_null( $u ) ) {
+ wfDie( "Bad \$fbUserName: $fbUserName." );
+ } else {
+ $mods = null;
+ if ( preg_match( '/^(.*?)18446744073709551614(.*)$/', $u->getName(), $mods )) {
+ $this->pre = $mods[1];
+ $this->post = $mods[2];
+ }
+ }
+ }
+
+ /**
+ * Checks existing users in the database for user name conflicts with Facebook Connect users.
+ */
+ public function checkUserNameConflicts() {
+ // Query the database for all user names starting with $pre and ending with $post
+ $dbr = wfGetDB( DB_SLAVE );
+ $result = $dbr->select (
+ // SELECT user
+ wfGetDB( DB_SLAVE )->tableNamesN( 'user' ),
+ // FROM user_name
+ 'user_name',
+ // WHERE user_name LIKE pre%post
+ array( "user_name LIKE '$this->pre%$this->post'" ),
+ __METHOD__ . ' (' . get_class( $this ) . ')',
+ array(),
+ array()
+ )->result;
+ $res = new ResultWrapper( $dbr, $result );
+ echo $res->numRows();
+ // Look for offending user names
+ for( $i = 0; $i < $res->numRows(); $i++ ) {
+ $row = $res->fetchRow();
+ if( $this->idFromName( $row['user_name'] ) ) {
+ wfDie( 'User name conflict found: "' . $row['user_name'] . '". ' .
+ 'Rename offending user or set $fbUserName = true.' );
+ }
+ }
+ }
+
+ /**
+ * Retrieves the application's callback url as specified in the app's
+ * developer settings. Eventually, this function may be replaced by code
+ * that queries Facebook for this info, but for now it pulls the URL out
+ * of the variable $fbCallbackURL specified in config.php.
+ */
+ public function getCallbackUrl() {
+ global $fbCallbackURL;
+ return isset( $fbCallbackURL ) ? $fbCallbackURL : '';
+ }
+
+ /**
+ * Returns the root of the Facebook site we'll be hitting.
+ */
+ public function getBaseUrl() {
+ global $fbBaseURL;
+ return isset( $fbBaseURL ) ? $fbBaseURL : '';
+ }
+
+ /**
+ * Turns the base URL into a fully qualified, usable URL.
+ */
+ public function getStaticRoot() {
+ return 'http://static.ak.' . $this->getBaseUrl();
+ }
+
+ /**
+ * Checks to see if the string is a valid (64-bit) Facebook ID.
+ */
+ public function isIdValid( $id ) {
+ if (!is_numeric( $id ))
+ $id = intval( $id );
+ return 0 < $id && $id < hexdec( 'FFFFFFFFFFFFFFFF' );
+ }
+
+ /**
+ * Returns true if the name of the global user $wgUser is a valid Facebook ID.
+ */
+ public function isConnected() {
+ global $wgUser;
+ return $wgUser->isLoggedIn() && $this->isIdValid( $wgUser->getName() );
+ }
+
+ /*
+ * Get the Facebook client object for easy access.
+ */
+ public function getClient() {
+ global $fbApiKey, $fbApiSecret;
+
+ static $facebook = null;
+
+ if ( $facebook === null && $this->isConfigSetup() ) {
+ $facebook = new Facebook( $fbApiKey, $fbApiSecret, false, $this->getBaseUrl() );
+ if (!$facebook) {
+ error_log('Could not create facebook client.');
+ }
+ /**
+ // Create a Facebook session
+ if (isset($_SESSION)) {
+ echo 'Error: isset($_SESSION)';
+ }
+ $session_key = md5(self::getClient()->api_client->session_key);
+ session_id( $session_key );
+ session_start();
+ /**/
+ }
+ return $facebook;
+ }
+
+ /**
+ * Check to make sure config.sample.php was properly renamed to config.php
+ * and the instructions to fill out the first three important variables were
+ * followed correctly.
+ */
+ public function isConfigSetup() {
+ global $fbApiKey, $fbApiSecret;
+ $isSetup = isset($fbApiKey) && $fbApiKey != 'YOUR_API_KEY' &&
+ isset($fbApiSecret) && $fbApiSecret != 'YOUR_API_SECRET' &&
+ $this->getCallbackUrl() != null;
+ if( !$isSetup )
+ error_log( 'Please update the $fbApiKey in config.php' );
+ return $isSetup;
+ }
+
+ /**
+ * A simple question of whether the site is "connected" or not.
+ */
+ public function isEnabled() {
+ if (!$this->isConfigSetup()) {
+ return false;
+ }
+ // Changing this disables Facebook Connect
+ return true;
+ }
+
+ /**
+ * Retrieves the ID of the current logged-in user. If no user is logged in,
+ * then an ID of 0 is returned (I think).
+ */
+ public function user() {
+ return $this->getClient()->get_loggedin_user();
+ }
+
+ /**
+ * Retrieves the wiki user name constructed from the Facebook ID of the current logged-
+ * in user and the postfix/suffix specified in $fbUserName. If user() is 0, then this
+ * returns an empty string.
+ */
+ public function userName() {
+ $uid = $this->user();
+ return $uid ? $this->pre . $uid . $this->post : '';
+ }
+
+ /**
+ * Extracts the Facebook ID from a username with a prefix and / or suffix in $fbUserName.
+ */
+ public function idFromName( $username ) {
+ // If nothing is added onto the ID, then return it
+ if ( $this->pre == '' && $this->post == '' ) {
+ return intval( $username );
+ }
+ $id = null;
+ $found = preg_match( '/^' . $this->pre . '(\d{1,20})' . $this->post . '$/', $username, $id );
+ return $found && $this->isIdValid( $id[1] ) ? intval( $id[1] ) : 0;
+ }
+
+ /**
+ * Requests the user's full name from Facebook.
+ */
+ public function getRealName( $user ) {
+ $name = $this->getFields( $user->getName(), array( 'name' ));
+ return $name[0]['name'];
+ }
+
+ /**
+ * Batches up user IDs so we can get their details with a single Platform query.
+ */
+ public function addPersonById( $id ) {
+ if ($this->isIdValid( $id ) && !in_array( $id, $this->ids )) {
+ $this->ids[] = $id;
+ }
+ }
+
+ /**
+ * Performs that queries requested by addPersonById().
+ */
+ public function getPersons() {
+ // If possible, switch to http://wiki.developers.facebook.com/index.php/Users.getStandardInfo
+ //$user_details = $this->getClient()->api_client->users_getInfo($this->ids, array('last_name', 'first_name', 'pic', 'pic_big', 'pic_small', 'pic_square'));
+ $user_details = $this->getFields( $this->ids, array('last_name', 'first_name', 'pic_square') );
+ if (!is_null( $user_details )) {
+ foreach ( $user_details as $user ) {
+ $fbuid = "fb" . $user['uid'];
+ $users->$fbuid->name = $user['first_name'] . ' ' . $user['last_name'];
+ // If the user's picture is hidden, use a default generic picture
+ $users->$fbuid->pic = ($user['pic_square'] != "" ? $user['pic_square'] :
+ 'http://static.ak.connect.facebook.com/pics/q_silhouette.gif');
+ }
+ return $users;
+ }
+ return array();
+ }
+
+ /**
+ * Returns the name of the group specified by $fbUserRightsFromGroup, or null if it is false.
+ */
+ public function groupInfo() {
+ global $fbUserRightsFromGroup;
+ if ( !$fbUserRightsFromGroup ) {
+ return null;
+ }
+ $group = FBConnect::$api->getClient()->api_client->groups_get( null, $fbUserRightsFromGroup );
+ $info = null;
+ if ( is_array( $group ) && is_array( $group[0] )) {
+ $info['name'] = $group[0]['name'];
+ $info['creator'] = $group[0]['creator'];
+ $info['picture'] = $group[0]['pic'];
+ }
+ return $info;
+ }
+
+ /**
+ * Retrieves group membership data from Facebook as specified by $fbUserRightsFromGroup.
+ */
+ public function getGroupRights( $user = null ) {
+ global $fbUserRightsFromGroup;
+
+ // Groupies can be members, officers or admins (the latter two infer the former)
+ $rights = array('member' => false,
+ 'officer' => false,
+ 'admin' => false);
+
+ // If no group ID is specified by $fbUserRightsFromGroup, then there's no group to belong to
+ $gid = $fbUserRightsFromGroup;
+ if( !$gid || !$this->user() ) {
+ return $rights;
+ }
+ if( $user == null ) {
+ $user = $this->user();
+ } else if ( $user instanceof User ) {
+ $user = $user->getName();
+ }
+ if( !FBConnect::$api->isIdValid( $user )) {
+ return $rights;
+ }
+ if( is_int( $user )) {
+ $user = "$user";
+ }
+
+ // Cache the rights for an individual user to prevent hitting Facebook for duplicate info
+ static $rights_cache = array();
+ if ( array_key_exists( $user, $rights_cache )) {
+ // Retrieve the rights from the cache
+ return $rights_cache[$user];
+ }
+
+ // This can contain up to 500 ids, avoid requesting this info twice
+ static $members = false;
+ // Get a random 500 group members, along with officers, admins and not_replied's
+ if ( $members === false )
+ $members = FBConnect::$api->getClient()->api_client->groups_getMembers( $gid );
+ if ( $members ) {
+ if( array_key_exists( 'officers', $members ) && in_array( $user, $members['officers'] )) {
+ $rights['member'] = $rights['officer'] = true;
+ }
+ if( array_key_exists( 'admins', $members ) && in_array( $user, $members['admins'] )) {
+ $rights['member'] = $rights['admin'] = true;
+ }
+ // Because the latter two rights infer the former, this step isn't always necessary
+ if( !$rights['member'] ) {
+ // Check to see if we are one of the (up to 500) random users
+ if (( array_key_exists( 'not_replied', $members ) && is_array( $members['not_replied'] ) &&
+ in_array( $user, $members['not_replied'] )) || in_array( $user, $members['members'] )) {
+ $rights['member'] = true;
+ } else {
+ // For groups of over 500ish, we must use this extra API call
+ // Notice that it occurs last, because we can hopefully avoid having to call it
+ $group = FBConnect::$api->getClient()->api_client->groups_get( intval( $user ), $gid );
+ if( is_array( $group ) && is_array( $group[0] ) && $group[0]['gid'] == "$gid" ) {
+ $rights['member'] = true;
+ }
+ }
+ }
+ }
+ // Cache the rights
+ $rights_cache[$user] = $rights;
+ return $rights;
+ }
+
+ /*
+ * Fetch fields about a user from Facebook.
+ *
+ * If performance is an issue, then you may want to implement caching on top of this
+ * function. The cache would have to be cleared every 24 hours.
+ */
+ private function getFields( $fb_uids, $fields ) {
+ try {
+ $user_details = $this->getClient()->api_client->users_getInfo( $fb_uids, $fields );
+ } catch( Exception $e ) {
+ error_log( 'Failure in the api when requesting ' . join( ',', $fields ) .
+ ' on uid ' . $fb_uids . ' : ' . $e->getMessage() );
+ return null;
+ }
+ return $user_details ? ( !empty( $user_details ) ? $user_details : null ) : null;
+ }
+
+ /**
+ * Retrieves the proxied email address of a particular user. This function is untested.
+ * See: http://wiki.developers.facebook.com/index.php/Proxied_Email
+ *
+ * @Unused
+ *
+ * @TODO: Test this function!
+ */
+ public function getProxiedEmail( $ids ) {
+ return self::getClient()->api_client->users_getInfo($ids, 'proxied_email');
+ }
+
+ /**
+ * Returns the "public" hash of the email address (i.e., the one Facebook
+ * gives out via their API). The hash is of the form crc32($email)_md5($email).
+ *
+ * @Unused
+ */
+ public function hashEmail($email) {
+ if ($email == null)
+ return '';
+ $email = trim( strtolower( $email ));
+ return crc32( $email ) . '_' . md5( $email );
+ }
+}
152 FBConnectAuthPlugin.php
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Copyright © 2008 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/**
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' )) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+/**
+ * Class AuthPlugin must be defined before we can extend it!
+ */
+if ( !isset( $IP ))
+ $IP = defined( 'MW_INSTALL_PATH' ) ? MW_INSTALL_PATH : dirname( __FILE__ ) . '/../..';
+require_once( "$IP/includes/AuthPlugin.php" );
+
+
+/**
+ * Class FBConnectAuthPlugin extends AuthPlugin
+ *
+ * Custom implementation of user authentification as it pertains to
+ * Facebook Connect. This plugin bypasses the typical username-password
+ * system currently in place in MediaWiki. Authentification takes place
+ * against the cookies set by Facebook; if the user is signed into
+ * Facebook Connect and has authorized the application, then they will
+ * by logged into MediaWiki by their Facebook ID.
+ */
+class FBConnectAuthPlugin extends AuthPlugin {
+ /**
+ * Returns whether $username is a valid username.
+ */
+ public function userExists( $username ) {
+ return FBConnect::$api->isIdValid( FBConnect::$api->idFromName( $username ));
+ }
+
+ /**
+ * Whether the given username and password authenticate as a valid login. We should only
+ * let people login if they are first connected through Facebook Connect.
+ */
+ public function authenticate( $username, $password = '' ) {
+ return FBConnect::$api->idFromName( $username ) == FBConnect::$api->user();
+ }
+
+ /**
+ * The authorization is external via Facebook Connect, so we would normally autocreate
+ * accounts as necessary. However, we set this to false because automatic account
+ * creation is handled internally by the FBConnect extension.
+ */
+ public function autoCreate() {
+ return true;
+ }
+
+ /**
+ * Only nonconnected users can change their passwords.
+ */
+ public function allowPasswordChange() {
+ return !$this->strict() && !FBConnect::$api->user();
+ }
+
+ /**
+ * Check to see if external accounts can be created on Facebook. Returns false
+ * because they obviously can't be.
+ */
+ public function canCreateAccounts() {
+ return false;
+ }
+
+ /**
+ * Do not look in the local database for user authentication because our
+ * authentication method is all that counts. Returns true to prevent logins
+ * that don't authenticate here from being checked against the local database's
+ * password fields.
+ */
+ public function strict() {
+ global $fbAllowOldAccounts;
+ return !$fbAllowOldAccounts;
+ }
+
+ /**
+ * This function gets called when the user is created. $user is an instance of
+ * the User class (see includes/User.php). Note the passed-by-reference nature.
+ */
+ public function initUser( &$user, $autocreate = false ) {
+ if ( $autocreate && $this->userExists( $user->getName() )) {
+ $user->mEmailAuthenticated = wfTimestampNow();
+ // Turn on e-mail notifications by default
+ $user->setOption( 'enotifwatchlistpages', 1 );
+ $user->setOption( 'enotifusertalkpages', 1 );
+ $user->setOption( 'enotifminoredits', 1 );
+ $user->setOption( 'enotifrevealaddr', 1 );
+ // No password to remember
+ $user->setOption( 'rememberpassword', 0 );
+ // Mark the user as a Facebook Connect user
+ $user->addGroup( 'fb-user' );
+ }
+ $this->updateUser( $user );
+ return true;
+ }
+
+ /**
+ * When a user logs in, attempt to fill in preferences and such. Here, we query
+ * for the user's real name.
+ */
+ public function updateUser( &$user ) {
+ if ( FBConnect::$api->isIdValid( FBConnect::$api->idFromName( $user->getName() ))) {
+ /**/
+ // Temporary fix for my personal wiki
+ if ( !in_array( 'fb-user', $user->getGroups() )) {
+ $user->addGroup( 'fb-user' );
+ }
+ /**/
+ $name = FBConnect::$api->getRealName( $user );
+ if ( $name != '' && $name != $user->getRealName() ) {
+ $user->setRealName( $name );
+ $user->saveSettings();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Modify options in the login template.
+ */
+ public function modifyUITemplate( &$template ) {
+ if( FBConnect::$api->user() ) {
+ // Disable the mail new password box
+ $template->set( "useemail", false );
+ // Disable 'remember me' box
+ $template->set( "remember", false );
+ // What happens if a Connected user creates an account while logged in?
+ $template->set( "create", false );
+ }
+ // Don't use domains
+ $template->set( 'usedomain', false );
+ }
+}
448 FBConnectHooks.php
@@ -0,0 +1,448 @@
+<?php
+/**
+ * Copyright © 2008 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/**
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+
+/**
+ * Class FBConnectHooks
+ *
+ * This class contains all the hooks used in this extension. HOOKS DO NOT NEED
+ * TO BE EXPLICITLY ADDED TO $wgHooks. Simply write a function with the same
+ * name as the hook that provokes it, place it inside this class and let
+ * FBConnect::init() do its magic. Helper functions should be private, because
+ * only public static methods with an initial capital letter are added as hooks.
+ */
+class FBConnectHooks {
+ /**
+ * Checks the autopromote condition for a user.
+ */
+ static function AutopromoteCondition( $cond_type, $args, $user, &$result ) {
+ $types = array(APCOND_FB_INGROUP => 'member',
+ APCOND_FB_ISOFFICER => 'officer',
+ APCOND_FB_ISADMIN => 'admin');
+ $type = $types[$cond_type];
+ switch( $type ) {
+ case 'member':
+ case 'officer':
+ case 'admin':
+ $rights = FBConnect::$api->getGroupRights( $user );
+ $result = $rights[$type];
+ }
+ return true;
+ }
+
+ /**
+ * Check against stricter requirements (if any) for Facebook Connect users. Counterintuitively,
+ * we do the requirement checks first. This is to prevent unnecessary API group-related queries.
+ *
+ * $promote contains the groups that will be added. If the user isn't entitled to these groups,
+ * then we flush this array down the toilet.
+ */
+ static function GetAutoPromoteGroups( &$user, &$promote ) {
+ //echo "\nGetAutoPromoteGroups\n";
+ //var_dump( $promote );
+ /**/
+ // If there isn't any groups to promote to anyway
+ if( !count($promote) ) {
+ return true;
+ }
+ /**
+ // Requirement checks would go here to prevent unnecessary API group queries
+ // E.g. if there was a seperate AutoConfirmAge or AutoConfirmCount check for Facebook users
+ global $fbAutoConfirmAge, $fbAutoConfirmCount;
+ if( !isset( $fbAutoConfirmAge ))
+ $fbAutoConfirmAge = 0;
+ if( !isset( $fbAutoConfirmCount ))
+ $fbAutoConfirmCount = 0;
+ $age = time() - wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
+ if( $age >= $fbAutoConfirmAge && $user->getEditCount() >= $fbAutoConfirmCount ) {
+ // Matches requirements, don't bother checking if we're in a group
+ return true;
+ }
+ /**
+ // If user is not in Facebook group, empty the $promote array
+ $inGroup = true;
+ if( !$inGroup ) {
+ $promote = array();
+ }
+ /**/
+ return true;
+ }
+
+ /**
+ * Add a permissions error when permissions errors are checked for.
+ *
+ * The difference between getUserPermissionsErrors and getUserPermissionsErrorsExpensive:
+ *
+ * Typically, both hooks are run when checking for proper permissions in Title.php. When
+ * it is desireable to skip potentially expensive cascading permission checks, only
+ * getUserPermissionsErrors is run. This behavior is suitable for nonessential UI
+ * controls in common cases, but _not_ for functional access control. This behavior
+ * may provide false positives, but should never provide a false negative.
+ */
+ static function XXXgetUserPermissionsErrorsExpensive( $title, $user, $action, &$result ) {
+ //echo "getUserPermissionsErrorsExpensive\n";
+ return true;
+ }
+
+ /**
+ * Adds several Facebook Connect variables to the page:
+ *
+ * fbAPIKey The application's API key (see $fbAPIKey in config.php)
+ * fbLoggedIn Whether the PHP client reports the user being Connected
+ * fbLogoutURL The URL to be redirected to on a disconnect
+ *
+ * This hook was added in MediaWiki version 1.14. See:
+ * http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Skin.php?view=log&pathrev=38397
+ * If we are not at revision 38397 or later, this function is called from BeforePageDisplay
+ * to retain backward compatability.
+ */
+ static function MakeGlobalVariablesScript( &$vars ) {
+ global $wgTitle, $fbApiKey;
+ $thisurl = $wgTitle->getPrefixedURL();
+ $vars['fbApiKey'] = $fbApiKey;
+ $vars['fbLoggedIn'] = FBConnect::$api->user() ? true : false;
+ $vars['fbLogoutURL'] = Skin::makeSpecialUrl('Userlogout',
+ $wgTitle->isSpecial('Preferences') ? '' : "returnto={$thisurl}");
+ $vars['fbNames'] = FBConnect::$api->getPersons();
+ return true;
+ }
+
+ /**
+ * Hack: run MakeGlobalVariablesScript for backwards compatability.
+ * The MakeGlobalVariablesScript hook was added to MediaWiki 1.14 in revision 38397:
+ * http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Skin.php?view=log&pathrev=38397
+ */
+ private static function MGVS_hack( &$script ) {
+ global $wgVersion, $IP;
+ if (version_compare($wgVersion, '1.14', '<')) {
+ $svn = SpecialVersion::getSvnRevision($IP);
+ // if !$svn, then we must be using 1.13.x (as opposed to 1.14alpha+)
+ if (!$svn || $svn < 38397)
+ {
+ $script = "";
+ $vars = array();
+ wfRunHooks('MakeGlobalVariablesScript', array(&$vars));
+ foreach( $vars as $name => $value ) {
+ $script .= "\t\tvar $name = " . json_encode($value) . ";\n";
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Injects some important CSS and Javascript into the <head> of the page.
+ */
+ static function BeforePageDisplay( &$out, &$sk ) {
+ global $fbLogo, $wgScriptPath, $wgJsMimeType;
+
+ // Parse page output for Facebook IDs
+ $html = $out->getHTML();
+ preg_match_all('/User:([^"\'&#]+)/', $html, $usernames);
+ foreach( $usernames[1] as $name ) {
+ $id = FBConnect::$api->idFromName( $name );
+ if( $id )
+ FBConnect::$api->addPersonById( $id );
+ }
+
+ // Add a pretty Facebook logo in front of userpage links if $fbLogo is set
+ $style = '<style type="text/css">
+ @import url("' . $wgScriptPath . '/extensions/FBConnect/fbconnect.css");' . ($fbLogo ? '
+
+ /* Add a pretty Facebook logo to links of Connected users */
+ .mw-fbconnectuser {
+ background: url(' . $fbLogo . ') top right no-repeat;
+ padding-right: 17px;
+ }
+
+ li#pt-fblink' . (FBConnect::$api->isConnected() ? ', li#pt-userpage' : '') . ' {
+ background: url(' . $fbLogo . ') top left no-repeat;
+ padding-left: 17px;
+ }' : '') . (FBConnect::$special_connect ? '
+
+ /* Modify the style of #userloginForm for Special:Connect */
+ #userloginForm {
+ float: right;
+ }
+
+ #userloginForm form {
+ margin: 0 !important;
+ }' : '') . '
+ </style>';
+
+ // Styles and Scripts have been built, so add them to the page
+ if (self::MGVS_hack( $mgvs_script ))
+ // Inserts list of global JavaScript variables
+ $out->addInlineScript( $mgvs_script );
+ // Enables DHTML tooltips
+ $out->addScript("<script type=\"$wgJsMimeType\" " .
+ "src=\"$wgScriptPath/extensions/FBConnect/wz_tooltip/wz_tooltip.js\"></script>\n");
+ // Required Facebook Connect JavaScript code
+ $out->addScript("<script type=\"$wgJsMimeType\" " .
+ "src=\"$wgScriptPath/extensions/FBConnect/fbconnect.js\"></script>\n");
+ // Styles DHTML tooltips, adds pretty Facebook logos to userpage links
+ $out->addScript( $style );
+
+ return true;
+ }
+
+ /**
+ * Adds Facebook tooltip info to the rows of Connected users in Special:ListUsers.
+ */
+ static function SpecialListusersFormatRow( &$item, $row ) {
+ // Only add DHTML tooltips to Facebook Connect users
+ if (!FBConnect::$api->isIdValid( $row->user_name )) # || $row->edits == 0) {
+ return true;
+
+ // Look to see if class="..." appears in the link
+ $regs = array();
+ preg_match( '/^([^>]*?)class=(["\'])([^"]*)\2(.*)/', $item, $regs );
+ if (count( $regs )) {
+ // If so, append " mw-userlink" to the end of the class list
+ $item = $regs[1] . "class=$regs[2]$regs[3] mw-userlink$regs[2]" . $regs[4];
+ } else {
+ // Otherwise, stick class="mw-userlink" into the link just before the '>'
+ preg_match( '/^([^>]*)(.*)/', $item, $regs );
+ $item = $regs[1] . ' class="mw-userlink"' . $regs[2];
+ }
+ return true;
+ }
+
+ /**
+ * This script is necessary for Facebook Connect because it refers the browser to the
+ * Facebook JavaScript Feature Loader file. This script should be referenced in the
+ * BODY not in the HEAD, as low as possible before FB.init() is called.
+ */
+ static function SkinAfterBottomScripts( $skin, &$text ) {
+ $text = "\n\t\t<script type=\"text/javascript\" " .
+ "src=\"http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php\">" .
+ "</script>$text";
+ return true;
+ }
+
+ /**
+ * Installs a parser hook for every tag reported by FBConnectXFBML::availableTags().
+ * Accomplishes this by asking FBConnectXFBML to create a hook function that then
+ * redirects to FBConnectXFBML::parserHook().
+ */
+ static function ParserFirstCallInit( &$parser ) {
+ $pHooks = FBConnectXFBML::availableTags();
+ foreach( $pHooks as $tag ) {
+ $parser->setHook( $tag, FBConnectXFBML::createParserHook( $tag ));
+ }
+ return true;
+ }
+
+ /**
+ * Modify the user's persinal toolbar (in the upper right)
+ */
+ static function PersonalUrls( &$personal_urls, &$wgTitle ) {
+ global $wgUser, $fbAllowOldAccounts, $fbRemoveUserTalkLink;
+
+ wfLoadExtensionMessages( 'FBConnect' );
+
+ if ( $wgUser->isLoggedIn() ) {
+ if( FBConnect::$api->isConnected() ) {
+
+ // Replace ugly Facebook ID numbers with the user's real name
+ if( $wgUser->getRealName() != "" )
+ $personal_urls['userpage']['text'] = $wgUser->getRealName();
+
+ // Replace logout link with a button to disconnect from Facebook Connect
+ unset( $personal_urls['logout'] );
+ $personal_urls['fblogout'] = array(
+ 'text' => wfMsg( 'fbconnect-logout' ),
+ 'href' => '#',
+ 'active' => false );
+
+ // Add a convenient link back to facebook.com
+ // This helps enforce the idea that this wiki is "in front" of Facebook
+ $personal_urls['fblink'] = array(
+ 'text' => wfMsg( 'fbconnect-link' ),
+ 'href' => 'http://www.facebook.com/profile.php?id=' . $wgUser->getName(),
+ 'active' => false );
+
+ } else { # User is logged in but not Connected
+
+ // Link to a special page that connects the user's account with their Facebook ID
+ $personal_urls['fblink'] = array(
+ 'text' => wfMsg( 'fbconnect-connect' ),
+ 'href' => Skin::makeSpecialUrl( 'Connect', '' ),
+ 'active' => false );
+ }
+ } else { # User is not logged in
+
+ // Add an option to connect via Facebook Connect
+ $personal_urls['fbconnect'] = array(
+ 'text' => wfMsg( 'fbconnectlogin' ),
+ 'href' => SpecialPage::getTitleFor( 'Connect' )->
+ getLocalUrl( 'returnto=' . $wgTitle->getPrefixedURL() ),
+ // 'href' => '#', # TODO: Update JavaScript and then use this href
+ 'active' => $wgTitle->isSpecial( 'Connect' ) );
+
+ // Remove other personal toolbar links
+ if( !$fbAllowOldAccounts ) {
+ foreach( array( 'login', 'anonlogin' ) as $k ) {
+ if( array_key_exists( $k, $personal_urls ) )
+ unset( $personal_urls[$k] );
+ }
+ }
+ }
+
+ // Unset user talk page links
+ if ( $fbRemoveUserTalkLink && array_key_exists( 'mytalk', $personal_urls ))
+ unset( $personal_urls['mytalk'] );
+
+ return true;
+ }
+
+ /**
+ * Modify the preferences form. At the moment, we simply turn the user name
+ * into a link to the user's facebook profile.
+ */
+ public static function RenderPreferencesForm( $form, $output ) {
+ global $wgUser;
+
+ // If the user name is a valid Facebook ID, link to the Facebook profile
+ if( FBConnect::$api->isConnected() ) {
+ $html = $output->getHTML();
+ $name = $wgUser->getName();
+ $i = strpos( $html, $name );
+ if ($i !== FALSE) {
+ // Replace the old output with the new output
+ $html = substr( $html, 0, $i ) . preg_replace( "/$name/",
+ "<a href=\"http://www.facebook.com/profile.php?id=$name\" " .
+ "class='mw-userlink mw-fbconnectuser'>$name</a>", substr( $html, $i ), 1 );
+ $output->clearHTML();
+ $output->addHTML( $html );
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Adds some info about the governing Facebook group to the header form of Special:ListUsers.
+ */
+ static function SpecialListusersHeaderForm( &$pager, &$out ) {
+ global $fbUserRightsFromGroup, $fbLogo;
+ if ( $gid = $fbUserRightsFromGroup ) {
+ $group = FBConnect::$api->groupInfo();
+ $groupName = $group['name'];
+ $cid = $group['creator'];
+ $pic = $group['picture'];
+ $out .= '
+ <table style="border-collapse: collapse;">
+ <tr>
+ <td>
+ ' . wfMsgWikiHtml( 'fbconnect-listusers-header',
+ wfMsg( 'group-bureaucrat-member' ), wfMsg( 'group-sysop-member' ),
+ "<a href=\"http://www.facebook.com/group.php?gid=$gid\">$groupName</a>",
+ "<a href=\"http://www.facebook.com/profile.php?id=$cid#User:$cid\" " .
+ "class=\"mw-userlink\">$cid</a>") . '
+ </td>
+ <td>
+ <img src="' . "$pic\" title=\"$groupName\" alt=\"$groupName" . '">
+ </td>
+ </tr>
+ </table>';
+ }
+ return true;
+ }
+
+ /**
+ * Removes the 'createaccount' right from users if $fbConnectOnly is true.
+ */
+ static function UserGetRights( &$user, &$aRights ) {
+ global $fbConnectOnly;
+ if ( $fbConnectOnly ) {
+ foreach ( $aRights as $i => $right ) {
+ if ( $right == 'createaccount' ) {
+ unset( $aRights[$i] );
+ break;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * If the user isn't logged in, try to auto-authenticate via Facebook Connect.
+ */
+ static function UserLoadFromSession( $user, &$result ) {
+ global $wgAuth, $wgUser;
+ // Check to see if we have a connection with Facebook
+ if ( !FBConnect::$api->user() ) {
+ // No connection with facebook, so use local sessions only if FBConnectAuthPlugin allows it
+ if( $wgAuth->strict() ) {
+ $result = false;
+ }
+ return true;
+ }
+ $localId = User::idFromName( FBConnect::$api->userName() );
+
+ // If the user exists, then log them in
+ if ( $localId ) {
+ $user->setID( $localId );
+ $user->loadFromId();
+ // Updates the user's info from Facebook if no real name is set
+ $wgAuth->updateUser( $user );
+ } else {
+ // User has not visited the wiki before, so create a new user from their Facebook ID
+ $userName = FBConnect::$api->userName();
+
+ // Test to see if we are denied by FBConnectAuthPlugin or the user can't create an account
+ if ( !$wgAuth->autoCreate() || !$wgAuth->userExists( $userName ) ||
+ !$wgAuth->authenticate( $userName )) {
+ #if( $wgAuth->strict() ) {
+ $result = false;
+ #}
+ return true;
+ }
+
+ // Checks passed, create the user
+ $user->loadDefaults( $userName );
+ $user->addToDatabase();
+
+ $wgAuth->initUser( $user, true );
+ $wgUser = $user;
+
+ // Update the user count
+ $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
+ $ssUpdate->doUpdate();
+
+ // Notify hooks (e.g. Newuserlog)
+ wfRunHooks( 'AuthPluginAutoCreate', array( $wgUser ));
+
+ // Which MediaWiki versions can we call this function in?
+ $user->addNewUserLogEntryAutoCreate();
+ }
+
+ // Authentification okay
+ wfSetupSession();
+ $result = true;
+ return true;
+ }
+}
175 FBConnectXFBML.php
@@ -0,0 +1,175 @@
+<?php
+/**
+ * Copyright © 2008 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/**
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+
+/**
+ * Class FBConnectXFBML
+ *
+ * This class allows FBML (Facebook Markup Language, an extension to HTML) to
+ * be incorporated into the wiki through XFBML.
+ */
+class FBConnectXFBML {
+ /**
+ * This function is the callback that the ParserFirstCallInit hook assigns
+ * to the parser whenever a FBML tag is encountered (like <fb:name>).
+ * Function createParserHook($tag) creates an anonymous (lambda-style)
+ * function that simply redirects to parserHook(), filling in the missing
+ * $tag argument with the $tag provided to createParserHook.
+ */
+ static function parserHook($text, $args, &$parser, $tag = '' ) {
+ global $fbAllowFacebookImages;
+ switch ($tag) {
+ case '':
+ break; // Error: We shouldn't be here!
+
+ // To implement a custom XFBML tag handler, simply case it here like so
+ #case 'fb:login-button':
+ case 'fb:prompt-permission':
+ // Disable these tags by returning an empty string
+ break;
+ case 'fb:serverfbml':
+ $attrs = self::implodeAttrs( $args );
+ return "<fb:serverfbml{$attrs}>$text</fb:serverfbml>";
+ case 'fb:profile-pic':
+ case 'fb:photo':
+ case 'fb:video':
+ if (!$fbAllowFacebookImages)
+ break;
+ // Careful - no "break" if $fbAllowFacebookImages is true
+ default:
+ $attrs = self::implodeAttrs( $args );
+ return "<{$tag}{$attrs}>" . $parser->recursiveTagParse( $text ) . "</$tag>";
+ }
+ // Strip the tag entirely
+ return '';
+ }
+
+ /**
+ * Helper function to create name-value pairs from the list of attributes passed to the
+ * parser hook.
+ */
+ private static function implodeAttrs( $args ) {
+ $attrs = "";
+ // The default action is to strip all event handlers and allow the tag
+ foreach( $args as $name => $value ) {
+ // Disable all event handlers (e.g. onClick, onlogin)
+ if ( substr( $name, 0, 2 ) == "on" )
+ continue;
+ // Otherwise, pass the attribute through htmlspecialchars unmodified
+ $attrs .= " $name=\"" . htmlspecialchars( $value ) . '"';
+ }
+ return $attrs;
+ }
+
+ /**
+ * Helper function for parserHook. Originally, all tags were directed to
+ * that function, but I had no way of knowing whick tag provoked the function.
+ */
+ static function createParserHook($tag) {
+ $args = '$text,$args,&$parser';
+ $code = 'return FBConnectXFBML::parserHook($text,$args,$parser,\''.$tag.'\');';
+ return create_function($args, $code);
+ }
+
+ /**
+ * Returns true if XFBML is enabled (i.e. $fbUseMarkup is not false).
+ * Defaults to true if $fbUseMarkup is not set.
+ */
+ static function isEnabled() {
+ global $fbUseMarkup;
+ return !isset($fbUseMarkup) || $fbUseMarkup;
+ }
+
+ /**
+ * Returns the availabe XFBML tags. For now, this is just an array copied from
+ * <http://wiki.developers.facebook.com/index.php/XFBML> and the second-to-last line in
+ * <http://static.ak.fbcdn.net/rsrc.php/zAE5U/lpkg/2netpns0/en_US/141/136684/js/connect.js.pkg.php>.
+ * Eventually, this method can be replaced with one that gathers these tags
+ * from the internet (e.g. by downloading an xml or js file) in real time.
+ *
+ * This is necessary because the Facebook Platform is so new, and major updates
+ * are being pushed out every week. With such turmoil regarding the available tags
+ * and the features they offer, our list of tags should not be hardcoded into this file.
+ *
+ * But for now... HELP! Where does Firefox pull the XFBML tags in from??
+ */
+ static function availableTags() {
+ if (!self::isEnabled()) {
+ // If XFBML isn't enabled, then don't report any tags
+ return array( );
+ }
+
+ // These are DEFINITELY valid tags (sarcasm intended -- see method doc comment)
+ $validTags = array('fb:container',
+ 'fb:eventLink',
+ 'fb:groupLink',
+ 'fb:login-button',
+ 'fb:name',
+ 'fb:photo',
+ 'fb:profile-pic',
+ 'fb:prompt-permission',
+ 'fb:pronoun',
+ 'fb:serverfbml',
+ 'fb:unconnected-friends-count',
+ 'fb:user-status');
+ // This tag is listed in the facebook dev wiki's documentation, but I couldn't
+ // find any mention of it in the JavaScript package connect.js.pkg.php
+ // Is it valid? What's going on in this JavaScript file?
+ $wikiTags = array('fb:connect-form');
+ // I found these in the JavaScript package connect.js.pkg.php, but the wiki
+ // makes no mention of them. Are they valid? This article might have more info:
+ // http://wiki.developers.facebook.com/index.php/JS_API_N_FB.XFBML
+ $jsTags = array('fb:add-section-button',
+ 'fb:share-button',
+ 'fb:userlink',
+ 'fb:video');
+ // Oh well, include them anyway
+ $tags = array_merge( $validTags, $wikiTags, $jsTags );
+
+ // Reject discarded tags (that return an empty string) from Special:Version
+ $tempParser = new DummyParser();
+ foreach( $tags as $i => $tag ) {
+ if ( self::parserHook('', array(), $tempParser, $tag) == '' ) {
+ unset( $tags[$i] );
+ }
+ }
+ // Allow other functions to modify the available XFBML tags
+ wfRunHooks( 'FbmlAvailableTags', array( &$tags ));
+ return $tags;
+ }
+}
+
+
+/**
+ * Class DummyParser
+ *
+ * Allows FBConnectXML::availableTags() to pre-sanatize the list of tags reported to
+ * MediaWiki, excluding any tags that result in the tag breing replaced by an empty
+ * string. Sorry for the confusing summary here, its really late. =)
+ */
+class DummyParser {
+ // We don't pass any text in our testing, so this must return an empty string
+ function recursiveTagParse() { return ''; }
+}
219 SpecialConnect.php
@@ -0,0 +1,219 @@
+<?php
+/**
+ * Copyright © 2008 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin>
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+/**
+ * Not a valid entry point, skip unless MEDIAWIKI is defined.
+ */
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
+}
+
+
+/**
+ * Body class for the special page Special:Connect.
+ */
+class SpecialConnect extends SpecialPage {
+ /**
+ * Constructor
+ */
+ function __construct() {
+ global $wgSpecialPageGroups;
+ // Initiate SpecialPage's constructor
+ parent::__construct( 'Connect' );
+ // Add this special page to the "login" group of special pages
+ $wgSpecialPageGroups['Connect'] = 'login';
+ }
+
+ /**
+ * Overrides getDescription() in SpecialPage. Looks in a different wiki message
+ * for this extension's description.
+ */
+ function getDescription() {
+ return wfMsg( 'fbconnect-title' );
+ }
+
+ /**
+ * Performs any necessary execution and outputs the resulting Special page.
+ */
+ function execute( $par ) {
+ global $wgOut;
+ // Let the extension (specifically, FBConnectHooks::BeforePageDisplay()) know this page is being rendered
+ FBConnect::$special_connect = true;
+
+ // Setup the page for rendering
+ wfLoadExtensionMessages( 'FBConnect' );
+ $this->setHeaders();
+ $wgOut->disallowUserJs(); # just in case...
+
+ // Render the heading (general info and propoganda about Facebook Connect)
+ $this->renderHeading();
+
+ // Render the Login and Connect/Merge forms
+ $this->renderForms();
+ }
+
+ /**
+ * Creates a header outlining the benefits of using Facebook Connect.
+ */
+ function renderHeading() {
+ global $wgOut;
+ $heading = '
+ <div id="specialconnect_info">
+ ' . wfMsg( 'fbconnect-intro' ) . '
+ <table>
+ <tr>
+ <th>' . wfMsg( 'fbconnect-conv' ) . '</th>
+ <th>' . wfMsg( 'fbconnect-fbml' ) . '</th>
+ <th>' . wfMsg( 'fbconnect-comm' ) . '</th>
+ </tr>
+ <tr>
+ <td>' . wfMsg( 'fbconnect-convdesc' ) . '</td>
+ <td>' . wfMsg( 'fbconnect-fbmldesc' ) . '</td>
+ <td>' . wfMsg( 'fbconnect-commdesc' ) . '</td>
+ </tr>
+ </table>
+ </div>';
+ $wgOut->addWikiText( $heading );
+ }
+
+ /**
+ * Renders two side-by-side boxes that differ based on who is logged in.
+ *
+ * Anonymous user: Draws the Special:Userlogin box and a Connect button.
+ * Non-connected user: Draws the Special:Userlogin and a Merge box.
+ * Connected user: Draws some info about the user and a Logout box.
+ */
+ function renderForms() {
+ global $wgOut, $wgAuth, $wgUser;
+ $wgOut->addHTML('
+ <table id="specialconnect_boxarea">
+ <tr>
+ <td class="box_left">');
+ if( FBConnect::$api->isConnected() ) {
+ // If the user is Connected, display info about them instead of a login form
+ $content = '<b><fb:name uid="loggedinuser" useyou="false" linked="false"></fb:name></b> ' .
+ // @TODO: (UCLA) should be replaced by the user's primary network
+ '(UCLA)<br/><a href="/wiki/User:#">my user page</a> | <a href="#" ' .
+ 'onclick="return popupFacebookInvite();">invite friends</a>';
+ $this->drawBox( 'fbconnect-welcome', '', $content );
+ } else {
+ $template = $this->createLoginForm();
+ // Give authentication and captcha plugins a chance to modify the form
+ $wgAuth->modifyUITemplate( $template );
+ wfRunHooks( 'UserLoginForm', array( &$template ));
+ $wgOut->addTemplate( $template );
+ }
+ $wgOut->addHTML('
+ </td>
+ <td class="box_right">');
+ if( !$wgUser->isLoggedIn() ) {
+ $this->drawBox( 'fbconnect', 'fbconnect-loginbox' );
+ } else if( !FBConnect::$api->isConnected() ) {
+ $this->drawBox( 'fbconnect-merge', 'fbconnect-mergebox' );
+ } else {
+ $this->drawBox( 'fbconnect-logout', 'fbconnect-logoutbox' );
+ }
+ $wgOut->addHTML('
+ </td>
+ </tr>
+ </table>');
+ }
+
+ /**
+ * Draws a Facebook-style info box.
+ *
+ * @param string $h1 The name of the message for the title of the box
+ * @param string $msg The name of the message for the content, or blank to use $html
+ * @param string $html The HTML to use if $msg is blank, or the $1 argument of the given message
+ */
+ function drawBox( $h1, $msg, $html = '' ) {
+ global $wgOut;
+ $wgOut->addHTML('
+ <div id="specialconnect_box">
+ <div>');
+ if( $html !== '' ) {
+ $wgOut->addHTML( '<fb:profile-pic uid="loggedinuser" size="small" ' .
+ 'facebook-logo="true"></fb:profile-pic>' );
+ }
+ $wgOut->addHTML('
+ </div>
+ <h1>');
+ $wgOut->addWikiText( wfMsg( $h1 ));
+ $wgOut->addHTML('
+ </h1>
+ <div class="box_content">');
+ $button = '<fb:login-button size="large" background="white" length="long" autologoutlink="true">';
+ if( $msg !== '' ) {
+ $wgOut->addWikiText( wfMsg( $msg, $button ));
+ } else {
+ if( $html == '' )
+ $html = $button;
+ $wgOut->addHTML( '<p>' . $html . '</p>' );
+ }
+ $wgOut->addHTML('
+ </div>
+ </div>');
+ }
+
+ /**
+ * Creates a Login Form template object and propogates it with parameters.
+ */
+ function createLoginForm() {
+ global $wgUser, $wgEnableEmail, $wgEmailConfirmToEdit,
+ $wgCookiePrefix, $wgCookieExpiration, $wgAuth;
+
+ $template = new UserloginTemplate();
+
+ // Pull the name from $wgUser or cookies
+ if( $wgUser->isLoggedIn() )
+ $name = $wgUser->getName();
+ else if( isset( $_COOKIE[$wgCookiePrefix . 'UserName'] ))
+ $name = $_COOKIE[$wgCookiePrefix . 'UserName'];
+ else
+ $name = null;
+ // Alias some common URLs for $action and $link
+ $loginTitle = self::getTitleFor( 'Userlogin' );
+ $this_href = wfUrlencode( $this->getTitle() );
+ // Action URL that gets posted to
+ $action = $loginTitle->getLocalUrl( 'action=submitlogin&type=login&returnto=' . $this_href );
+ // Don't show a "create account" link if the user is not allowed to create an account
+ if ($wgUser->isAllowed( 'createaccount' )) {
+ $link_href = htmlspecialchars( $loginTitle->getLocalUrl( 'type=signup&returnto=' . $this_href ));
+ $link_text = wfMsgHtml( 'nologinlink' );
+ $link = wfMsgHtml( 'nologin', "<a href=\"$link_href\">$link_text</a>" );
+ } else
+ $link = '';
+
+ // Set the appropriate options for this template
+ $template->set( 'header', '' );
+ $template->set( 'name', $name );
+ $template->set( 'action', $action );
+ $template->set( 'link', $link );
+ $template->set( 'message', '' );
+ $template->set( 'messagetype', 'none' );
+ $template->set( 'useemail', $wgEnableEmail );
+ $template->set( 'emailrequired', $wgEmailConfirmToEdit );
+ $template->set( 'canreset', $wgAuth->allowPasswordChange() );
+ $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
+ $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) );
+ // Look this setting up in SpecialUserLogin.php
+ $template->set( 'usedomain', false );
+ // Spit out the form we just made
+ return $template;
+ }
+}
112 config.sample.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * To install:
+ * 1. Copy this file to config.php (remove the .sample part)
+ * 2. Follow the instructions below to make the extension work.
+ */
+
+### FBCONNECT CONFIGURATION VARIABLES ###
+
+/*
+ * To use Facebook Connect you will first need to get a Facebook API Key:
+ * 1. Visit the Facebook application creation page:
+ * http://www.facebook.com/developers/createapp.php
+ * 2. Enter a descriptive name for your wiki in the Application Name field.
+ * This will be seen by users when they sign up for your site.
+ * 3. Accept the Facebook Terms of Service.
+ * 4. Upload icon and logo images. The icon appears in News Feed stories and the
+ * logo appears in the Connect dialog when the user connects with your application.
+ * 5. Click Submit.
+ * 6. Copy the displayed API key and application secret into this config file.
+ */
+$fbApiKey = 'YOUR_API_KEY';
+$fbApiSecret = 'YOUR_SECRET';
+
+/*
+ * Enter your callback URL here. That's the location where index.php resides.
+ * Make sure it's your exact root - facebook.com and www.facebook.com are different.
+ *
+ * Set the callback URL in your developer app to match the one you specify here.
+ * This is important so that the Javascript cross-domain library works correctly.
+ *
+ * Note that each callback URL needs its own app id.
+ */
+$fbCallbackURL = 'http://www.example.com/callback/w/';
+
+/*
+ * This is the root of the Facebook site you'll be hitting. In production,
+ * this will be facebook.com
+ */
+$fbBaseURL = 'connect.facebook.com';
+
+/*
+ * The feed story template needs to be registered with your app_key, and then just passed
+ * at run time. To register the feed bundle for your app, visit:
+ *
+ * www.yourwiki.com/path/to/extensions/FBConnect/register_feed_forms.php
+ *
+ * Then copy/paste the resulting feed bundle ID here.
+ *
+ * NOTE: This feature is currently unimplemented.
+ */
+$fbFeedBundleId = 99999999;
+
+// The Facebook icon. You can copy this to your server if you want, or set to false to disable.
+$fbLogo = 'http://static.ak.fbcdn.net/images/icons/favicon.gif';
+
+// This will be the form of the Facebook user's name when the user Connects and an account is
+// automatically created. The first letter will of course be capitalized, and '_' converted to
+// spaces. The user's Facebook ID takes place of the #. If no # is used, then the Facebook ID
+// will be appended onto this string to form the user name. Unless your wiki is a new installation
+// with a small user base, I recommend 'FB ' or '#-fb', e.g.
+//
+// To check if $fbUserName meshes with your current wiki setup, set $fbCheckUserNames to true.
+//
+// NOTE: As of r87, anything other than '' may currently cause tooltips have minor display
+// problems, like tooltips and the Facebook logo failing to show up on the page.
+$fbUserName = ''; # 'FB ' or '#-fb', for example
+
+// Uncomment this line to check for user name conflicts between existing user names in the database
+// and user names that could be generated for Facebook Connect users.
+#$fbCheckUserNames = true;
+
+// Allow non-Connected user accounts to login. Set this to true to allow users to continue logging
+// into your site with old-style user names.
+$fbAllowOldAccounts = true;
+
+// Disable new account creation (accounts can only be created by a successful Connection)
+$fbConnectOnly = false;
+
+// Allow the use of XFBML in wiki text
+// For more info, see http://wiki.developers.facebook.com/index.php/XFBML
+$fbUseMarkup = true;
+
+// If XFBML is enabled, then <fb:photo> maybe be used as a replacement for $wgAllowExternalImages
+// with the added benefit that all photos are screened against Facebook's Code of Conduct
+// <http://www.facebook.com/codeofconduct.php> and subject to dynamic privacy. To disable just
+// <fb:photo> tags, set this to false.
+$fbAllowFacebookImages = true;
+
+// For easier wiki rights management, create a group on Facebook and place the group ID here.
+// Three new implicit groups will be created:
+// fb-groupie, a member of the specified group
+// fb-officer, a group member with an officer title
+// fb-admin, an administrator of the Facebook group
+// By default, they map to User, Bureaucrat and Sysop privileges, respectively. Users will
+// automatically be promoted or demoted when their membership, title or admin status is modified
+// from the group page within Facebook.
+$fbUserRightsFromGroup = false; # Or a group ID
+
+// Not used (yet...)
+$fbRestrictToGroup = true;
+$fbRestrictToNotReplied = false;
+
+
+### GLOBAL CONFIGURATION VARIABLES ###
+
+// Remove link to user's talk page in the personal toolbar (upper right)
+$fbRemoveUserTalkLink = true;
+
+// Don't show IP or its talk link in the personal header.
+// See http://www.mediawiki.org/wiki/Manual:$wgShowIPinHeader
+$wgShowIPinHeader = false;
498 facebook-client/facebook.php
@@ -0,0 +1,498 @@
+<?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
+ public function expire_session() {
+ if ($this->api_client->auth_expireSession()) {
+ 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;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ 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) {
+ return self::get_facebook_url().'/add.php?api_key='.$this->api_key .
+ ($next ? '&next=' . urlencode($next) : '');
+ }
+
+ public function get_login_url($next, $canvas) {
+ return self::get_facebook_url().'/login.php?v=1.0&api_key=' . $this->api_key .
+ ($next ? '&next=' . urlencode($next) : '') .
+ ($canvas ? '&canvas' : '');
+ }
+
+ 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, ''