Index: trunk/extensions/FBConnect/FBConnect.alias.php |
— | — | @@ -0,0 +1,21 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * FBConnect.alias.php - FBConnect for MediaWiki |
| 5 | + * |
| 6 | + * Special Page alias file... for when we actually define some special pages ;-) |
| 7 | + */ |
| 8 | + |
| 9 | + |
| 10 | +/* |
| 11 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 12 | + */ |
| 13 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 14 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 15 | +} |
| 16 | + |
| 17 | +$aliases = array(); |
| 18 | + |
| 19 | +/** English */ |
| 20 | +$aliases['en'] = array( |
| 21 | + 'Connect' => array( 'Connect', 'ConnectAccount' ), |
| 22 | +); |
Index: trunk/extensions/FBConnect/FBConnectXFBML.php |
— | — | @@ -0,0 +1,202 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2008-2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Class FBConnectXFBML |
| 30 | + * |
| 31 | + * This class allows FBML (Facebook Markup Language, an extension to HTML) to |
| 32 | + * be incorporated into the wiki through XFBML. |
| 33 | + * |
| 34 | + * See: <http://wiki.developers.facebook.com/index.php/XFBML> |
| 35 | + */ |
| 36 | +class FBConnectXFBML { |
| 37 | + /** |
| 38 | + * This function is the callback that the ParserFirstCallInit hook assigns |
| 39 | + * to the parser whenever a FBML tag is encountered (like <fb:name>). |
| 40 | + * Function createParserHook($tag) creates an anonymous (lambda-style) |
| 41 | + * function that simply redirects to parserHook(), filling in the missing |
| 42 | + * $tag argument with the $tag provided to createParserHook. |
| 43 | + */ |
| 44 | + static function parserHook($innertext, $args, &$parser, $tag = '' ) { |
| 45 | + global $fbAllowFacebookImages; |
| 46 | + |
| 47 | + // Run hook to allow modifying the default behavior. If $override is |
| 48 | + // set, it is used instead. Return false to disable the tag. |
| 49 | + $override = ''; |
| 50 | + if ( !wfRunHooks('XFBMLParseTag', |
| 51 | + array( $tag, &$args, &$innertext, &$override )) ) { |
| 52 | + return ''; |
| 53 | + } |
| 54 | + if ( $override != '' ) { |
| 55 | + return $override; |
| 56 | + } |
| 57 | + |
| 58 | + switch ($tag) { |
| 59 | + case '': |
| 60 | + break; // Error: We shouldn't be here! |
| 61 | + |
| 62 | + // To implement a custom XFBML tag handler, simply case it here like so |
| 63 | + case 'fb:add-profile-tab': |
| 64 | + // Disable these tags by returning an empty string |
| 65 | + break; |
| 66 | + case 'fb:serverfbml': |
| 67 | + // TODO: Is this safe? Does it respect $fbAllowFacebookImages? |
| 68 | + $attrs = self::implodeAttrs( $args ); |
| 69 | + // Don't recursively parse $innertext |
| 70 | + return "<fb:serverfbml{$attrs}>$innertext</fb:serverfbml>"; |
| 71 | + case 'fb:profile-pic': |
| 72 | + #case 'fb:photo': // Dropped in new JavaScript SDK |
| 73 | + #case 'fb:video': // Dropped in new JavaScript SDK |
| 74 | + if (!$fbAllowFacebookImages) { |
| 75 | + break; |
| 76 | + } |
| 77 | + // Careful - no "break;" if $fbAllowFacebookImages is true |
| 78 | + default: |
| 79 | + // Allow other tags by default |
| 80 | + $attrs = self::implodeAttrs( $args ); |
| 81 | + return "<{$tag}{$attrs}>" . $parser->recursiveTagParse( $innertext ) . "</$tag>"; |
| 82 | + } |
| 83 | + // Strip the tag entirely |
| 84 | + return ''; |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * Helper function to create name-value pairs from the list of attributes passed to the |
| 89 | + * parser hook. |
| 90 | + */ |
| 91 | + static function implodeAttrs( $args ) { |
| 92 | + $attrs = ""; |
| 93 | + // The default action is to strip all event handlers and allow the tag |
| 94 | + foreach( $args as $name => $value ) { |
| 95 | + // Disable all event handlers (e.g. onClick, onligin) |
| 96 | + if ( substr( $name, 0, 2 ) == "on" ) |
| 97 | + continue; |
| 98 | + // Otherwise, pass the attribute through htmlspecialchars unmodified |
| 99 | + $attrs .= " $name=\"" . htmlspecialchars( $value ) . '"'; |
| 100 | + } |
| 101 | + return $attrs; |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Helper function for parserHook. Originally, all tags were directed to |
| 106 | + * that function, but I had no way of knowing which tag provoked the function. |
| 107 | + */ |
| 108 | + static function createParserHook($tag) { |
| 109 | + $args = '$text,$args,&$parser'; |
| 110 | + $code = 'return FBConnectXFBML::parserHook($text,$args,$parser,\''.$tag.'\');'; |
| 111 | + return create_function($args, $code); |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Returns true if XFBML is enabled (i.e. $fbUseMarkup is not false). |
| 116 | + * Defaults to true if $fbUseMarkup is not set. |
| 117 | + */ |
| 118 | + static function isEnabled() { |
| 119 | + global $fbUseMarkup; |
| 120 | + return !isset($fbUseMarkup) || $fbUseMarkup; |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * Returns the availabe XFBML tags. For help on including these in your |
| 125 | + * site, please see: <http://developers.facebook.com/plugins>. |
| 126 | + * |
| 127 | + * If Facebook adds a new tag (or you create your own!) then this list can |
| 128 | + * be updated with the XFBMLAvailableTags hook. |
| 129 | + */ |
| 130 | + static function availableTags() { |
| 131 | + if (!self::isEnabled()) { |
| 132 | + // If XFBML isn't enabled, then don't report any tags |
| 133 | + return array( ); |
| 134 | + } |
| 135 | + // http://github.com/facebook/connect-js/blob/master/src/xfbml/xfbml.js#L237 |
| 136 | + $tags = array('fb:activity', |
| 137 | + 'fb:add-profile-tab', |
| 138 | + 'fb:bookmark', |
| 139 | + 'fb:comments', |
| 140 | + 'fb:connect-bar', |
| 141 | + 'fb:fan', |
| 142 | + 'fb:like', |
| 143 | + 'fb:like-box', |
| 144 | + 'fb:live-stream', |
| 145 | + 'fb:login', |
| 146 | + 'fb:login-button', |
| 147 | + 'fb:facepile', |
| 148 | + 'fb:name', |
| 149 | + 'fb:profile-pic', |
| 150 | + 'fb:recommendations', |
| 151 | + 'fb:serverfbml', |
| 152 | + 'fb:share-button', |
| 153 | + 'fb:social-bar', |
| 154 | + /* |
| 155 | + * From the Facebook Developer's Wiki under the old JS library. |
| 156 | + * These may still be possible with a <fb:serverFbml> tag. |
| 157 | + * <http://wiki.developers.facebook.com/index.php/XFBML> |
| 158 | + */ |
| 159 | + #'fb:connect-form', |
| 160 | + #'fb:container', |
| 161 | + #'fb:eventlink', |
| 162 | + #'fb:grouplink', |
| 163 | + #'fb:photo', |
| 164 | + #'fb:prompt-permission', |
| 165 | + #'fb:pronoun', |
| 166 | + #'fb:unconnected-friends-count', |
| 167 | + #'fb:user-status' |
| 168 | + /* |
| 169 | + * In 2008 I found these in the deprecated Facebook Connect |
| 170 | + * JavaScript library, connect.js.pkg.php, though no documentation |
| 171 | + * was available for them on the Facebook dev wiki. |
| 172 | + */ |
| 173 | + #'fb:add-section-button', |
| 174 | + #'fb:share-button', |
| 175 | + #'fb:userlink', |
| 176 | + #'fb:video', |
| 177 | + ); |
| 178 | + |
| 179 | + // Reject discarded tags (that return an empty string) from Special:Version |
| 180 | + $tempParser = new DummyParser(); |
| 181 | + foreach( $tags as $i => $tag ) { |
| 182 | + if ( self::parserHook('', array(), $tempParser, $tag) == '' ) { |
| 183 | + unset( $tags[$i] ); |
| 184 | + } |
| 185 | + } |
| 186 | + // Allow other functions to modify the available XFBML tags |
| 187 | + wfRunHooks( 'XFBMLAvailableTags', array( &$tags )); |
| 188 | + return $tags; |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | + |
| 193 | +/** |
| 194 | + * Class DummyParser |
| 195 | + * |
| 196 | + * Allows FBConnectXML::availableTags() to pre-sanatize the list of tags reported to |
| 197 | + * MediaWiki, excluding any tags that result in the tag being replaced by an empty |
| 198 | + * string. Sorry for the confusing summary here, its really late. =) |
| 199 | + */ |
| 200 | +class DummyParser { |
| 201 | + // We don't pass any text in our testing, so this must return an empty string |
| 202 | + function recursiveTagParse() { return ''; } |
| 203 | +} |
Index: trunk/extensions/FBConnect/SpecialConnect.php |
— | — | @@ -0,0 +1,764 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2008-2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Class SpecialConnect |
| 30 | + * |
| 31 | + * This class represents the body class for the page Special:Connect. |
| 32 | + * |
| 33 | + * Currently, this page has one valid subpage at Special:Connect/ChooseName. |
| 34 | + * Visiting the subpage will generate an error; it is only useful when POSTed to. |
| 35 | + */ |
| 36 | +class SpecialConnect extends SpecialPage { |
| 37 | + private $userNamePrefix; |
| 38 | + static private $availableUserUpdateOptions = array('fullname', 'nickname', 'email', 'language', 'timecorrection'); |
| 39 | + |
| 40 | + /** |
| 41 | + * Constructor. |
| 42 | + */ |
| 43 | + function __construct() { |
| 44 | + global $wgSpecialPageGroups; |
| 45 | + // Initiate SpecialPage's constructor |
| 46 | + parent::__construct( 'Connect' ); |
| 47 | + // Add this special page to the "login" group of special pages |
| 48 | + $wgSpecialPageGroups['Connect'] = 'login'; |
| 49 | + |
| 50 | + wfLoadExtensionMessages( 'FBConnect' ); |
| 51 | + $this->userNamePrefix = wfMsg('fbconnect-usernameprefix'); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * Allows the prefix to be changed at runtime. This is useful, for example, |
| 56 | + * to generate a username based off of a facebook name. |
| 57 | + */ |
| 58 | + public function setUserNamePrefix( $prefix ){ |
| 59 | + $this->userNamePrefix = $prefix; |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Returns the list of user options that can be updated by facebook on each login. |
| 64 | + */ |
| 65 | + public function getAvailableUserUpdateOptions(){ |
| 66 | + return self::$availableUserUpdateOptions; |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Overrides getDescription() in SpecialPage. Looks in a different wiki message |
| 71 | + * for this extension's description. |
| 72 | + */ |
| 73 | + function getDescription() { |
| 74 | + return wfMsg( 'fbconnect-title' ); |
| 75 | + } |
| 76 | + |
| 77 | + /** |
| 78 | + * Performs any necessary execution and outputs the resulting Special page. |
| 79 | + */ |
| 80 | + function execute( $par ) { |
| 81 | + global $wgUser, $facebook, $wgRequest; |
| 82 | + |
| 83 | + if ( $wgRequest->getVal("action", "") == "disconnect" ) { |
| 84 | + self::disconnectAction(); |
| 85 | + } |
| 86 | + |
| 87 | + // Check to see if the user is already logged in |
| 88 | + if ( $wgUser->getID() ) { |
| 89 | + $this->sendPage('alreadyLoggedIn'); |
| 90 | + return; |
| 91 | + } |
| 92 | + |
| 93 | + $fbid = $facebook->getUser(); |
| 94 | + /* |
| 95 | + // Connect to the Facebook API |
| 96 | + try { |
| 97 | + $me = $facebook->api('/me'); |
| 98 | + |
| 99 | + } catch (FacebookApiException $e) { |
| 100 | + error_log($e); |
| 101 | + } |
| 102 | + */ |
| 103 | + |
| 104 | + // Look at the subpage name to discover where we are in the login process |
| 105 | + switch ( $par ) { |
| 106 | + case 'ChooseName': |
| 107 | + $choice = $wgRequest->getText('wpNameChoice'); |
| 108 | + if ($wgRequest->getCheck('wpCancel')) { |
| 109 | + $this->sendError('fbconnect-cancel', 'fbconnect-canceltext'); |
| 110 | + } |
| 111 | + else switch ($choice) { |
| 112 | + // Check to see if the user opted to connect an existing account |
| 113 | + case 'existing': |
| 114 | + $this->attachUser($fbid, $wgRequest->getText('wpExistingName'), |
| 115 | + $wgRequest->getText('wpExistingPassword')); |
| 116 | + break; |
| 117 | + // Check to see if the user selected another valid option |
| 118 | + case 'nick': |
| 119 | + case 'first': |
| 120 | + case 'full': |
| 121 | + // Get the username from Facebook (Note: not from the form) |
| 122 | + $username = FBConnectUser::getOptionFromInfo($choice . 'name', |
| 123 | + $facebook->getUserInfo()); |
| 124 | + case 'manual': |
| 125 | + if (!isset($username) || !$this->userNameOK($username)) { |
| 126 | + // Use manual name if no username is set, even if manual wasn't chosen |
| 127 | + $username = $wgRequest->getText('wpName2'); |
| 128 | + } |
| 129 | + // If no valid username was found, something's not right; ask again |
| 130 | + if (!$this->userNameOK($username)) { |
| 131 | + $this->sendPage('chooseNameForm', 'fbconnect-invalidname'); |
| 132 | + } else { |
| 133 | + $this->createUser($fbid, $username); |
| 134 | + } |
| 135 | + break; |
| 136 | + case 'auto': |
| 137 | + // Create a user with a unique generated username |
| 138 | + $this->createUser($fbid, $this->generateUserName()); |
| 139 | + break; |
| 140 | + default: |
| 141 | + $this->sendError('fbconnect-invalid', 'fbconnect-invalidtext'); |
| 142 | + } |
| 143 | + break; |
| 144 | + default: |
| 145 | + // Main entry point |
| 146 | + #if ( $wgRequest->getText( 'returnto' ) ) { |
| 147 | + # $this->setReturnTo( $wgRequest->getText( 'returnto' ), |
| 148 | + # $wgRequest->getVal( 'returntoquery' ) ); |
| 149 | + #} |
| 150 | + if ($fbid) { |
| 151 | + // If the user is connected, log them in |
| 152 | + $this->login($fbid); |
| 153 | + } else { |
| 154 | + // If the user isn't Connected, then show a form with the Connect button |
| 155 | + $this->sendPage('connectForm'); |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + /** |
| 161 | + * Logs in the user by their Facebook ID. If the Facebook user doesn't have |
| 162 | + * an account on the wiki, then they are presented with a form prompting |
| 163 | + * them to choose a wiki username. |
| 164 | + */ |
| 165 | + protected function login($fb_id) { |
| 166 | + global $wgUser; |
| 167 | + |
| 168 | + // Check to see if the Connected user exists in the database |
| 169 | + if ($fb_id) { |
| 170 | + $user = FBConnectDB::getUser($fb_id); |
| 171 | + } |
| 172 | + |
| 173 | + // If the user doesn't exist yet locally, allow hooks to check to see if this is a clustered set up |
| 174 | + if ( !(isset($user) && $user instanceof User ) && |
| 175 | + !wfRunHooks('SpecialConnect::login::notFoundLocally', array(&$this, &$user, $fb_id))) { |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + if ( isset($user) && $user instanceof User ) { |
| 180 | + $fbUser = new FBConnectUser($user); |
| 181 | + // Update user from facebook (see class FBConnectUser) |
| 182 | + $fbUser->updateFromFacebook(); |
| 183 | + |
| 184 | + // Setup the session |
| 185 | + global $wgSessionStarted; |
| 186 | + if (!$wgSessionStarted) { |
| 187 | + wfSetupSession(); |
| 188 | + } |
| 189 | + |
| 190 | + $user->setCookies(); |
| 191 | + $wgUser = $user; |
| 192 | + |
| 193 | + // Similar to what's done in LoginForm::authenticateUserData(). |
| 194 | + // Load $wgUser now. This is necessary because loading $wgUser (say |
| 195 | + // by calling getName()) calls the UserLoadFromSession hook, which |
| 196 | + // potentially creates the user in the local database. |
| 197 | + $sessionUser = User::newFromSession(); |
| 198 | + $sessionUser->load(); |
| 199 | + |
| 200 | + $this->sendPage('displaySuccessLogin'); |
| 201 | + } else if ($fb_id) { |
| 202 | + $this->sendPage('chooseNameForm'); |
| 203 | + } else { |
| 204 | + // TODO: send an error message saying only Connected users can log in |
| 205 | + // or ask them to Connect. |
| 206 | + $this->sendError('fbconnect-cancel', 'fbconnect-canceltext'); |
| 207 | + } |
| 208 | + } |
| 209 | + |
| 210 | + protected function createUser($fb_id, $name) { |
| 211 | + global $wgUser, $wgOut, $fbConnectOnly, $wgAuth, $wgRequest, $wgMemc; |
| 212 | + wfProfileIn(__METHOD__); |
| 213 | + |
| 214 | + // Handle accidental reposts. |
| 215 | + if ( $wgUser->isLoggedIn() ) { |
| 216 | + $this->sendPage('displaySuccessLogin'); |
| 217 | + wfProfileOut(__METHOD__); |
| 218 | + return; |
| 219 | + } |
| 220 | + |
| 221 | + // Make sure we're not stealing an existing user account. |
| 222 | + if (!$name || !$this->userNameOK($name)) { |
| 223 | + // TODO: Provide an error message that explains that they need to pick a name or the name is taken. |
| 224 | + wfDebug("FBConnect: Name not OK: '$name'\n"); |
| 225 | + $this->sendPage('chooseNameForm'); |
| 226 | + return; |
| 227 | + } |
| 228 | + |
| 229 | + /// START OF TYPICAL VALIDATIONS AND RESTRICITONS ON ACCOUNT-CREATION. /// |
| 230 | + |
| 231 | + // Check the restrictions again to make sure that the user can create this account. |
| 232 | + $titleObj = SpecialPage::getTitleFor( 'Connect' ); |
| 233 | + if ( wfReadOnly() ) { |
| 234 | + $wgOut->readOnlyPage(); |
| 235 | + return; |
| 236 | + } elseif ( !isset($fbConnectOnly) || !$fbConnectOnly ) { |
| 237 | + // These two permissions don't apply in $fbConnectOnly mode |
| 238 | + if ( $wgUser->isBlockedFromCreateAccount() ) { |
| 239 | + wfDebug("FBConnect: Blocked user was attempting to create account via Facebook Connect.\n"); |
| 240 | + $wgOut->showErrorPage('fbconnect-error', 'fbconnect-errortext'); |
| 241 | + return; |
| 242 | + } elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) { |
| 243 | + $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' ); |
| 244 | + return; |
| 245 | + } |
| 246 | + } |
| 247 | + |
| 248 | + // If we are not allowing users to login locally, we should be checking |
| 249 | + // to see if the user is actually able to authenticate to the authenti- |
| 250 | + // cation server before they create an account (otherwise, they can |
| 251 | + // create a local account and login as any domain user). We only need |
| 252 | + // to check this for domains that aren't local. |
| 253 | + $mDomain = $wgRequest->getText( 'wpDomain' ); |
| 254 | + if( 'local' != $mDomain && '' != $mDomain ) { |
| 255 | + if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $name ) ) ) { |
| 256 | + $wgOut->showErrorPage('fbconnect-error', 'wrongpassword'); |
| 257 | + return false; |
| 258 | + } |
| 259 | + } |
| 260 | + |
| 261 | + // IP-blocking (and open proxy blocking) protection from SpecialUserLogin |
| 262 | + global $wgEnableSorbs, $wgProxyWhitelist; |
| 263 | + $ip = wfGetIP(); |
| 264 | + if ( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) && |
| 265 | + $wgUser->inSorbsBlacklist( $ip ) ) |
| 266 | + { |
| 267 | + $wgOut->showErrorPage('fbconnect-error', 'sorbs_create_account_reason'); |
| 268 | + return; |
| 269 | + } |
| 270 | + |
| 271 | + /** |
| 272 | + // Test to see if we are denied by $wgAuth or the user can't create an account |
| 273 | + if ( !$wgAuth->autoCreate() || !$wgAuth->userExists( $userName ) || |
| 274 | + !$wgAuth->authenticate( $userName )) { |
| 275 | + $result = false; |
| 276 | + return true; |
| 277 | + } |
| 278 | + /**/ |
| 279 | + |
| 280 | + // Run a hook to let custom forms make sure that it is okay to proceed with processing the form. |
| 281 | + // This hook should only check preconditions and should not store values. Values should be stored using the hook at the bottom of this function. |
| 282 | + // Can use 'this' to call sendPage('chooseNameForm', 'SOME-ERROR-MSG-CODE-HERE') if some of the preconditions are invalid. |
| 283 | + if(! wfRunHooks( 'SpecialConnect::createUser::validateForm', array( &$this ) )){ |
| 284 | + return; |
| 285 | + } |
| 286 | + |
| 287 | + $user = User::newFromName($name); |
| 288 | + if (!$user) { |
| 289 | + wfDebug("FBConnect: Error creating new user.\n"); |
| 290 | + $wgOut->showErrorPage('fbconnect-error', 'fbconnect-error-creating-user'); |
| 291 | + return; |
| 292 | + } |
| 293 | + |
| 294 | + // Let extensions abort the account creation. If you have extensions which are expecting a Real Name or Email, you may need to disable |
| 295 | + // them since these are not requirements of Facebook Connect (so users will not have them). |
| 296 | + // NOTE: Currently this is commented out because it seems that most wikis might have a handful of restrictions that won't be needed on |
| 297 | + // Facebook Connections. For instance, requiring a CAPTCHA or age-verification, etc. Having a Facebook account as a pre-requisitie removes the need for that. |
| 298 | + /* |
| 299 | + $abortError = ''; |
| 300 | + if( !wfRunHooks( 'AbortNewAccount', array( $user, &$abortError ) ) ) { |
| 301 | + // Hook point to add extra creation throttles and blocks |
| 302 | + wfDebug( "SpecialConnect::createUser: a hook blocked creation\n" ); |
| 303 | + $wgOut->showErrorPage('fbconnect-error', 'fbconnect-error-user-creation-hook-aborted', array($abortError)); |
| 304 | + return false; |
| 305 | + } |
| 306 | + /**/ |
| 307 | + |
| 308 | + // Apply account-creation throttles |
| 309 | + global $wgAccountCreationThrottle; |
| 310 | + if ( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) { |
| 311 | + $key = wfMemcKey( 'acctcreate', 'ip', $ip ); |
| 312 | + $value = $wgMemc->get( $key ); |
| 313 | + if ( !$value ) { |
| 314 | + $wgMemc->set( $key, 0, 86400 ); |
| 315 | + } |
| 316 | + if ( $value >= $wgAccountCreationThrottle ) { |
| 317 | + $this->throttleHit( $wgAccountCreationThrottle ); |
| 318 | + return false; |
| 319 | + } |
| 320 | + $wgMemc->incr( $key ); |
| 321 | + } |
| 322 | + |
| 323 | + /// END OF TYPICAL VALIDATIONS AND RESTRICITONS ON ACCOUNT-CREATION. /// |
| 324 | + |
| 325 | + // Create the account (locally on main cluster or via wgAuth on other clusters) |
| 326 | + $email = $realName = ""; // The real values will get filled in outside of the scope of this function. |
| 327 | + $pass = null; |
| 328 | + if( !$wgAuth->addUser( $user, $pass, $email, $realName ) ) { |
| 329 | + wfDebug("FBConnect: Error adding new user to database.\n"); |
| 330 | + $wgOut->showErrorPage('fbconnect-error', 'fbconnect-errortext'); |
| 331 | + return; |
| 332 | + } |
| 333 | + |
| 334 | + // Adds the user to the local database (regardless of whether wgAuth was used) |
| 335 | + $user = $this->initUser( $user, true ); |
| 336 | + |
| 337 | + // Attach the user to their Facebook account in the database |
| 338 | + // This must be done up here so that the data is in the database before copy-to-local is done for sharded setups |
| 339 | + FBConnectDB::addFacebookID($user, $fb_id); |
| 340 | + |
| 341 | + wfRunHooks( 'AddNewAccount', array( $user ) ); |
| 342 | + |
| 343 | + // Mark that the user is a Facebook user |
| 344 | + $user->addGroup('fb-user'); |
| 345 | + |
| 346 | + // Store which fields should be auto-updated from Facebook when the user logs in. |
| 347 | + $updateFormPrefix = "wpUpdateUserInfo"; |
| 348 | + foreach (self::$availableUserUpdateOptions as $option) { |
| 349 | + if($wgRequest->getVal($updateFormPrefix.$option, '') != ""){ |
| 350 | + $user->setOption("fbconnect-update-on-login-$option", 1); |
| 351 | + } else { |
| 352 | + $user->setOption("fbconnect-update-on-login-$option", 0); |
| 353 | + } |
| 354 | + } |
| 355 | + |
| 356 | + // Process the FBConnectPushEvent preference checkboxes if fbConnectPushEvents are enabled. |
| 357 | + global $fbEnablePushToFacebook; |
| 358 | + if($fbEnablePushToFacebook){ |
| 359 | + global $fbPushEventClasses; |
| 360 | + if(!empty($fbPushEventClasses)){ |
| 361 | + foreach($fbPushEventClasses as $pushEventClassName){ |
| 362 | + $pushObj = new $pushEventClassName; |
| 363 | + $className = get_class(); |
| 364 | + $prefName = $pushObj->getUserPreferenceName(); |
| 365 | + |
| 366 | + $user->setOption($prefName, ($wgRequest->getCheck($prefName)?"1":"0")); |
| 367 | + } |
| 368 | + } |
| 369 | + } |
| 370 | + |
| 371 | + // Unfortunately, performs a second database lookup |
| 372 | + $fbUser = new FBConnectUser($user); |
| 373 | + // Update the user with settings from Facebook |
| 374 | + $fbUser->updateFromFacebook(); |
| 375 | + |
| 376 | + // Start the session if it's not already been started |
| 377 | + global $wgSessionStarted; |
| 378 | + if (!$wgSessionStarted) { |
| 379 | + wfSetupSession(); |
| 380 | + } |
| 381 | + |
| 382 | + // Log the user in |
| 383 | + $user->setCookies(); |
| 384 | + // Store the new user as the global user object |
| 385 | + $wgUser = $user; |
| 386 | + |
| 387 | + /* |
| 388 | + * Similar to what's done in LoginForm::authenticateUserData(). Load |
| 389 | + * $wgUser now. This is necessary because loading $wgUser (say by |
| 390 | + * calling getName()) calls the UserLoadFromSession hook, which |
| 391 | + * potentially creates the user in the local database. |
| 392 | + */ |
| 393 | + $sessionUser = User::newFromSession(); |
| 394 | + $sessionUser->load(); |
| 395 | + |
| 396 | + // Allow custom form processing to store values since this form submission was successful. |
| 397 | + // This hook should not fail on invalid input, instead check the input using the SpecialConnect::createUser::validateForm hook above. |
| 398 | + wfRunHooks( 'SpecialConnect::createUser::postProcessForm', array( &$this ) ); |
| 399 | + |
| 400 | + // TODO: Which MediaWiki versions can we call this function in? |
| 401 | + $user->addNewUserLogEntryAutoCreate(); |
| 402 | + #$user->addNewUserLogEntry(); |
| 403 | + |
| 404 | + $this->sendPage('displaySuccessLogin'); |
| 405 | + |
| 406 | + wfProfileOut(__METHOD__); |
| 407 | + } |
| 408 | + |
| 409 | + /** |
| 410 | + * Actually add a user to the database. |
| 411 | + * Give it a User object that has been initialised with a name. |
| 412 | + * |
| 413 | + * This is a custom version of similar code in SpecialUserLogin's LoginForm with differences |
| 414 | + * due to the fact that this code doesn't require a password, etc. |
| 415 | + * |
| 416 | + * @param $u User object. |
| 417 | + * @param $autocreate boolean -- true if this is an autocreation via auth plugin |
| 418 | + * @return User object. |
| 419 | + * @private |
| 420 | + */ |
| 421 | + protected function initUser( $u, $autocreate ) { |
| 422 | + global $wgAuth; |
| 423 | + |
| 424 | + $u->addToDatabase(); |
| 425 | + |
| 426 | + // No passwords for FBConnect accounts. |
| 427 | + /** |
| 428 | + if ( $wgAuth->allowPasswordChange() ) { |
| 429 | + $u->setPassword( $this->mPassword ); |
| 430 | + } |
| 431 | + /** |
| 432 | + $u->setEmail( $this->mEmail ); // emails aren't required by FBConnect extension (some customizations such as Wikia require it on their own). |
| 433 | + $u->setRealName( $this->mRealName ); // real name isn't required for FBConnect |
| 434 | + /**/ |
| 435 | + $u->setToken(); |
| 436 | + |
| 437 | + $wgAuth->initUser( $u, $autocreate ); |
| 438 | + $wgAuth->updateUser($u); |
| 439 | + |
| 440 | + //$u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 ); |
| 441 | + $u->setOption('skinoverwrite', 1); |
| 442 | + $u->saveSettings(); |
| 443 | + |
| 444 | + # Update user count |
| 445 | + $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); |
| 446 | + $ssUpdate->doUpdate(); |
| 447 | + |
| 448 | + return $u; |
| 449 | + } |
| 450 | + |
| 451 | + /** |
| 452 | + * Attaches the Facebook ID to an existing wiki account. If the user does |
| 453 | + * not exist, or the supplied password does not match, then an error page |
| 454 | + * is sent. Otherwise, the accounts are matched in the database and the new |
| 455 | + * user object is logged in. |
| 456 | + */ |
| 457 | + protected function attachUser($fbid, $name, $password) { |
| 458 | + global $wgOut, $wgUser; |
| 459 | + wfProfileIn(__METHOD__); |
| 460 | + |
| 461 | + // The user must be logged into Facebook before choosing a wiki username |
| 462 | + if ( !$fbid ) { |
| 463 | + wfDebug("FBConnect: aborting in attachUser(): no Facebook ID was reported.\n"); |
| 464 | + $wgOut->showErrorPage( 'fbconnect-error', 'fbconnect-errortext' ); |
| 465 | + return; |
| 466 | + } |
| 467 | + // Look up the user by their name |
| 468 | + $user = new FBConnectUser(User::newFromName($name)); |
| 469 | + if (!$user || !$user->checkPassword($password)) { |
| 470 | + $this->sendPage('chooseNameForm', 'wrongpassword'); |
| 471 | + return; |
| 472 | + } |
| 473 | + // Attach the user to their Facebook account in the database |
| 474 | + FBConnectDB::addFacebookID($user, $fbid); |
| 475 | + // Update the user with settings from Facebook |
| 476 | + $user->updateFromFacebook(); |
| 477 | + // Store the user in the global user object |
| 478 | + $wgUser = $user; |
| 479 | + $this->sendPage('displaySuccessLogin'); |
| 480 | + |
| 481 | + wfProfileOut(__METHOD__); |
| 482 | + } |
| 483 | + |
| 484 | + /** |
| 485 | + * Generates a unique username for a wiki account based on the prefix specified |
| 486 | + * in the message 'fbconnect-usernameprefix'. The number appended is equal to |
| 487 | + * the number of Facebook Connect to user ID associations in the user_fbconnect |
| 488 | + * table, so quite a few numbers will be skipped. However, this approach is |
| 489 | + * more scalable. For smaller wiki installations, uncomment the line $i = 1 to |
| 490 | + * have consecutive usernames starting at 1. |
| 491 | + */ |
| 492 | + public function generateUserName() { |
| 493 | + // Because $i is incremented the first time through the while loop |
| 494 | + $i = FBConnectDB::countUsers(); |
| 495 | + #$i = 1; // This is the DUMB WAY to do this for large databases |
| 496 | + while ($i < PHP_INT_MAX) { |
| 497 | + $name = $this->userNamePrefix . $i; |
| 498 | + if ($this->userNameOK($name)) { |
| 499 | + return $name; |
| 500 | + } |
| 501 | + ++$i; |
| 502 | + } |
| 503 | + return $prefix; |
| 504 | + } |
| 505 | + |
| 506 | + /** |
| 507 | + * Tests whether the name is OK to use as a user name. |
| 508 | + */ |
| 509 | + public function userNameOK ($name) { |
| 510 | + global $wgReservedUsernames; |
| 511 | + return ($name && (null == User::idFromName($name)) && !in_array($name, $wgReservedUsernames)); |
| 512 | + } |
| 513 | + |
| 514 | + /** |
| 515 | + * Displays a simple error page. |
| 516 | + */ |
| 517 | + protected function sendError($titleMsg, $textMsg) { |
| 518 | + global $wgOut; |
| 519 | + $wgOut->showErrorPage($titleMsg, $textMsg); |
| 520 | + } |
| 521 | + |
| 522 | + public function sendPage($function, $arg = NULL) { |
| 523 | + global $wgOut; |
| 524 | + // Setup the page for rendering |
| 525 | + wfLoadExtensionMessages( 'FBConnect' ); |
| 526 | + $this->setHeaders(); |
| 527 | + $wgOut->disallowUserJs(); # just in case... |
| 528 | + $wgOut->setRobotPolicy( 'noindex,nofollow' ); |
| 529 | + $wgOut->setArticleRelated( false ); |
| 530 | + // Call the specified function to continue generating the page |
| 531 | + if (is_null($arg)) { |
| 532 | + $this->$function(); |
| 533 | + } else { |
| 534 | + $this->$function($arg); |
| 535 | + } |
| 536 | + } |
| 537 | + |
| 538 | + private function alreadyLoggedIn() { |
| 539 | + global $wgOut, $wgUser, $wgRequest, $wgSitename; |
| 540 | + $wgOut->setPageTitle(wfMsg('fbconnect-error')); |
| 541 | + $wgOut->addWikiMsg('fbconnect-alreadyloggedin', $wgUser->getName()); |
| 542 | + $wgOut->addWikiMsg('fbconnect-click-to-connect-existing', $wgSitename); |
| 543 | + $wgOut->addHTML('<fb:login-button'.FBConnect::getPermissionsAttribute().FBConnect::getOnLoginAttribute().'></fb:login-button>'); |
| 544 | + // Render the "Return to" text retrieved from the URL |
| 545 | + $wgOut->returnToMain(false, $wgRequest->getText('returnto'), $wgRequest->getVal('returntoquery')); |
| 546 | + } |
| 547 | + |
| 548 | + private function displaySuccessLogin() { |
| 549 | + global $wgOut, $wgRequest; |
| 550 | + $wgOut->setPageTitle(wfMsg('fbconnect-success')); |
| 551 | + $wgOut->addWikiMsg('fbconnect-successtext'); |
| 552 | + // Run any hooks for UserLoginComplete |
| 553 | + $inject_html = ''; |
| 554 | + wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$inject_html ) ); |
| 555 | + $wgOut->addHtml( $inject_html ); |
| 556 | + // Render the "return to" text retrieved from the URL |
| 557 | + $wgOut->returnToMain(false, $wgRequest->getText('returnto'), $wgRequest->getVal('returntoquery')); |
| 558 | + } |
| 559 | + |
| 560 | + /** |
| 561 | + * TODO: Add an option to disallow this extension to access your Facebook |
| 562 | + * information. This option could simply point you to your Facebook privacy |
| 563 | + * settings. This is necessary in case the user wants to perpetually browse |
| 564 | + * the wiki anonymously, while still being logged in to Facebook. |
| 565 | + * |
| 566 | + * NOTE: The above might be done now that we have checkboxes for which options |
| 567 | + * to update from fb. Haven't tested it though. |
| 568 | + */ |
| 569 | + private function chooseNameForm($messagekey = 'fbconnect-chooseinstructions') { |
| 570 | + // Permissions restrictions. |
| 571 | + global $wgUser, $facebook, $wgOut, $fbConnectOnly; |
| 572 | + $titleObj = SpecialPage::getTitleFor( 'Connect' ); |
| 573 | + if ( wfReadOnly() ) { |
| 574 | + // The wiki is in read-only mode |
| 575 | + $wgOut->readOnlyPage(); |
| 576 | + return; |
| 577 | + } elseif ( !isset($fbConnectOnly) || !$fbConnectOnly ) { |
| 578 | + // These two permissions don't apply in $fbConnectOnly mode |
| 579 | + if ( $wgUser->isBlockedFromCreateAccount() ) { |
| 580 | + wfDebug("FBConnect: Blocked user was attempting to create account via Facebook Connect.\n"); |
| 581 | + $wgOut->showErrorPage('fbconnect-error', 'fbconnect-errortext'); |
| 582 | + return; |
| 583 | + } elseif ( count( $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) { |
| 584 | + $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' ); |
| 585 | + return; |
| 586 | + } |
| 587 | + } |
| 588 | + |
| 589 | + |
| 590 | + // Allow other code to have a custom form here (so that this extension can be integrated with existing custom login screens). |
| 591 | + if( !wfRunHooks( 'SpecialConnect::chooseNameForm', array( &$this, &$messagekey ) ) ){ |
| 592 | + return; |
| 593 | + } |
| 594 | + |
| 595 | + // Connect to the Facebook API |
| 596 | + $userinfo = $facebook->getUserInfo(); |
| 597 | + |
| 598 | + // Keep track of when the first option visible to the user is checked |
| 599 | + $checked = false; |
| 600 | + |
| 601 | + // Outputs the canonical name of the special page at the top of the page |
| 602 | + $this->outputHeader(); |
| 603 | + // If a different $messagekey was passed (like 'wrongpassword'), use it instead |
| 604 | + $wgOut->addWikiMsg( $messagekey ); |
| 605 | + // TODO: Format the html a little nicer |
| 606 | + $wgOut->addHTML(' |
| 607 | + <form action="' . $this->getTitle('ChooseName')->getLocalUrl() . '" method="POST"> |
| 608 | + <fieldset id="mw-fbconnect-choosename"> |
| 609 | + <legend>' . wfMsg('fbconnect-chooselegend') . '</legend> |
| 610 | + <table>'); |
| 611 | + // Let them attach to an existing user if $fbConnectOnly allows it |
| 612 | + if (!$fbConnectOnly) { |
| 613 | + // Grab the UserName from the cookie if it exists |
| 614 | + global $wgCookiePrefix; |
| 615 | + $name = isset($_COOKIE[$wgCookiePrefix . 'UserName']) ? |
| 616 | + trim($_COOKIE[$wgCookiePrefix . 'UserName']) : ''; |
| 617 | + // Build an array of attributes to update |
| 618 | + $updateOptions = array(); |
| 619 | + foreach (self::$availableUserUpdateOptions as $option) { |
| 620 | + // Translate the MW parameter into a FB parameter |
| 621 | + $value = FBConnectUser::getOptionFromInfo($option, $userinfo); |
| 622 | + // If no corresponding value was received from Facebook, then continue |
| 623 | + if (!$value) { |
| 624 | + continue; |
| 625 | + } |
| 626 | + // Build the list item for the update option |
| 627 | + $updateOptions[] = "<li><input name=\"wpUpdateUserInfo$option\" type=\"checkbox\" " . |
| 628 | + "value=\"1\" id=\"wpUpdateUserInfo$option\" /><label for=\"wpUpdateUserInfo$option\">" . |
| 629 | + wfMsgHtml("fbconnect-$option") . wfMsgExt('colon-separator', array('escapenoentities')) . |
| 630 | + " <i>$value</i></label></li>"; |
| 631 | + } |
| 632 | + // Implode the update options into an unordered list |
| 633 | + $updateChoices = count($updateOptions) > 0 ? "<br />\n" . wfMsgHtml('fbconnect-updateuserinfo') . |
| 634 | + "\n<ul>\n" . implode("\n", $updateOptions) . "\n</ul>\n" : ''; |
| 635 | + // Create the HTML for the "existing account" option |
| 636 | + $html = '<tr><td class="wm-label"><input name="wpNameChoice" type="radio" ' . |
| 637 | + 'value="existing" id="wpNameChoiceExisting"/></td><td class="mw-input">' . |
| 638 | + '<label for="wnNameChoiceExisting">' . wfMsg('fbconnect-chooseexisting') . '<br/>' . |
| 639 | + wfMsgHtml('fbconnect-chooseusername') . '<input name="wpExistingName" size="16" value="' . |
| 640 | + $name . '" id="wpExistingName"/>' . wfMsgHtml('fbconnect-choosepassword') . |
| 641 | + '<input name="wpExistingPassword" ' . 'size="" value="" type="password"/>' . $updateChoices . |
| 642 | + '</td></tr>'; |
| 643 | + $wgOut->addHTML($html); |
| 644 | + } |
| 645 | + |
| 646 | + // Add the options for nick name, first name and full name if we can get them |
| 647 | + // TODO: Wikify the usernames (i.e. Full name should have an _ ) |
| 648 | + foreach (array('nick', 'first', 'full') as $option) { |
| 649 | + $nickname = FBConnectUser::getOptionFromInfo($option . 'name', $userinfo); |
| 650 | + if ($nickname && $this->userNameOK($nickname)) { |
| 651 | + $wgOut->addHTML('<tr><td class="mw-label"><input name="wpNameChoice" type="radio" value="' . |
| 652 | + $option . ($checked ? '' : '" checked="checked') . '" id="wpNameChoice' . $option . |
| 653 | + '"/></td><td class="mw-input"><label for="wpNameChoice' . $option . '">' . |
| 654 | + wfMsg('fbconnect-choose' . $option, $nickname) . '</label></td></tr>'); |
| 655 | + // When the first radio is checked, this flag is set and subsequent options aren't checked |
| 656 | + $checked = true; |
| 657 | + } |
| 658 | + } |
| 659 | + |
| 660 | + // The options for auto and manual usernames are always available |
| 661 | + $wgOut->addHTML('<tr><td class="mw-label"><input name="wpNameChoice" type="radio" value="auto" ' . |
| 662 | + ($checked ? '' : 'checked="checked" ') . 'id="wpNameChoiceAuto"/></td><td class="mw-input">' . |
| 663 | + '<label for="wpNameChoiceAuto">' . wfMsg('fbconnect-chooseauto', $this->generateUserName()) . |
| 664 | + '</label></td></tr><tr><td class="mw-label"><input name="wpNameChoice" type="radio" ' . |
| 665 | + 'value="manual" id="wpNameChoiceManual"/></td><td class="mw-input"><label ' . |
| 666 | + 'for="wpNameChoiceManual">' . wfMsg('fbconnect-choosemanual') . '</label> ' . |
| 667 | + '<input name="wpName2" size="16" value="" id="wpName2"/></td></tr>' . |
| 668 | + // Finish with two options, "Log in" or "Cancel" |
| 669 | + '<tr><td></td><td class="mw-submit"><input type="submit" value="Log in" name="wpOK"/>' . |
| 670 | + '<input type="submit" value="Cancel" name="wpCancel"/></td></tr></table></fieldset></form>' |
| 671 | + ); |
| 672 | + } |
| 673 | + |
| 674 | + /** |
| 675 | + * Displays the main connect form. |
| 676 | + */ |
| 677 | + private function connectForm() { |
| 678 | + global $facebook, $wgOut, $wgUser, $wgSitename; |
| 679 | + //$fbid = $facebook->getUser(); |
| 680 | + |
| 681 | + // Outputs the canonical name of the special page at the top of the page |
| 682 | + $this->outputHeader(); |
| 683 | + |
| 684 | + // Render a humble Facebook Connect button |
| 685 | + $wgOut->addHTML('<h2>' . wfMsg( 'fbconnect' ) . '</h2> |
| 686 | + <div>'.wfMsgExt( 'fbconnect-intro', array('parse', 'content')) . '<br/>' . wfMsg( 'fbconnect-click-to-login', $wgSitename ) .' |
| 687 | + <fb:login-button size="large" background="black" length="long"'.FBConnect::getPermissionsAttribute().FBConnect::getOnLoginAttribute().'></fb:login-button> |
| 688 | + </div>' |
| 689 | + ); |
| 690 | + } |
| 691 | + |
| 692 | + /** |
| 693 | + * Disconnect from Facebook |
| 694 | + */ |
| 695 | + private function disconnectAction() { |
| 696 | + global $wgRequest, $wgOut, $wgUser; |
| 697 | + |
| 698 | + if( $wgRequest->wasPosted() ) { |
| 699 | + $id = FBConnectDB::getFacebookIDs($wgUser); |
| 700 | + if( count($id) > 0 ) { |
| 701 | + FBConnectDB::removeFacebookID( $wgUser, $id['user_fbid'] ); |
| 702 | + } |
| 703 | + } |
| 704 | + |
| 705 | + $title = Title::makeTitle( NS_SPECIAL, "Preferences" ); |
| 706 | + $url = $title->getFullUrl("#prefsection-10"); |
| 707 | + $wgOut->redirect($url); |
| 708 | + return true; |
| 709 | + } |
| 710 | + |
| 711 | + /** |
| 712 | + * Creates a Login Form template object and propogates it with parameters. |
| 713 | + * |
| 714 | + private function createLoginForm() { |
| 715 | + global $wgUser, $wgEnableEmail, $wgEmailConfirmToEdit, |
| 716 | + $wgCookiePrefix, $wgCookieExpiration, $wgAuth; |
| 717 | + |
| 718 | + $template = new UserloginTemplate(); |
| 719 | + |
| 720 | + // Pull the name from $wgUser or cookies |
| 721 | + if( $wgUser->isLoggedIn() ) |
| 722 | + $name = $wgUser->getName(); |
| 723 | + else if( isset( $_COOKIE[$wgCookiePrefix . 'UserName'] )) |
| 724 | + $name = $_COOKIE[$wgCookiePrefix . 'UserName']; |
| 725 | + else |
| 726 | + $name = null; |
| 727 | + // Alias some common URLs for $action and $link |
| 728 | + $loginTitle = self::getTitleFor( 'Userlogin' ); |
| 729 | + $this_href = wfUrlencode( $this->getTitle() ); |
| 730 | + // Action URL that gets posted to |
| 731 | + $action = $loginTitle->getLocalUrl('action=submitlogin&type=login&returnto=' . $this_href); |
| 732 | + // Don't show a "create account" link if the user is not allowed to create an account |
| 733 | + if ($wgUser->isAllowed( 'createaccount' )) { |
| 734 | + $link_href = htmlspecialchars($loginTitle->getLocalUrl('type=signup&returnto=' . $this_href)); |
| 735 | + $link_text = wfMsgHtml( 'nologinlink' ); |
| 736 | + $link = wfMsgWikiHtml( 'nologin', "<a href=\"$link_href\">$link_text</a>" ); |
| 737 | + } else { |
| 738 | + $link = ''; |
| 739 | + } |
| 740 | + |
| 741 | + // Set the appropriate options for this template |
| 742 | + $template->set( 'header', '' ); |
| 743 | + $template->set( 'name', $name ); |
| 744 | + $template->set( 'action', $action ); |
| 745 | + $template->set( 'link', $link ); |
| 746 | + $template->set( 'message', '' ); |
| 747 | + $template->set( 'messagetype', 'none' ); |
| 748 | + $template->set( 'useemail', $wgEnableEmail ); |
| 749 | + $template->set( 'emailrequired', $wgEmailConfirmToEdit ); |
| 750 | + $template->set( 'canreset', $wgAuth->allowPasswordChange() ); |
| 751 | + $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); |
| 752 | + $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) ); |
| 753 | + // Look this setting up in SpecialUserLogin.php |
| 754 | + $template->set( 'usedomain', false ); |
| 755 | + // Give authentication and captcha plugins a chance to modify the form |
| 756 | + $type = 'login'; |
| 757 | + $wgAuth->modifyUITemplate( $template, $type ); |
| 758 | + wfRunHooks( 'UserLoginForm', array( &$template )); |
| 759 | + |
| 760 | + |
| 761 | + // Spit out the form we just made |
| 762 | + return $template; |
| 763 | + } |
| 764 | + /**/ |
| 765 | +} |
Index: trunk/extensions/FBConnect/FBConnectAPI.php |
— | — | @@ -0,0 +1,255 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2008-2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Class FBConnectAPI |
| 30 | + * |
| 31 | + * This class contains the code used to interface with Facebook via the |
| 32 | + * Facebook Platform API. |
| 33 | + */ |
| 34 | +class FBConnectAPI extends Facebook { |
| 35 | + // Constructor |
| 36 | + public function __construct() { |
| 37 | + global $fbAppId, $fbSecret, $fbDomain; |
| 38 | + // Check to make sure config.sample.php was renamed properly |
| 39 | + if ( !$this->isConfigSetup() ) { |
| 40 | + exit( 'Please update $fbAppId and $fbSecret in config.php' ); |
| 41 | + } |
| 42 | + $config = array( |
| 43 | + 'appId' => $fbAppId, |
| 44 | + 'secret' => $fbSecret, |
| 45 | + 'cookie' => true, |
| 46 | + ); |
| 47 | + // Include the optional domain parameter if it has been set |
| 48 | + if ( !empty($fbDomain) && $fbDomain != 'BASE_DOMAIN' ) { |
| 49 | + $config['domain'] = $fbDomain; |
| 50 | + } |
| 51 | + parent::__construct( $config ); |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * Check to make sure config.sample.php was properly renamed to config.php |
| 56 | + * and the instructions to fill out the first two important variables were |
| 57 | + * followed correctly. |
| 58 | + */ |
| 59 | + public function isConfigSetup() { |
| 60 | + global $fbAppId, $fbSecret; |
| 61 | + $isSetup = isset($fbAppId) && $fbAppId != 'YOUR_APP_KEY' && |
| 62 | + isset($fbSecret) && $fbSecret != 'YOUR_SECRET'; |
| 63 | + if(!$isSetup) { |
| 64 | + // Check to see if they are still using the old variables |
| 65 | + global $fbApiKey, $fbApiSecret; |
| 66 | + if ( isset($fbApiKey) ) { |
| 67 | + $fbAppId = $fbApiKey; |
| 68 | + } |
| 69 | + if ( isset($fbApiSecret) ) { |
| 70 | + $fbSecret= $fbApiSecret; |
| 71 | + } |
| 72 | + $isSetup = isset($fbAppId) && $fbAppId != 'YOUR_APP_KEY' && |
| 73 | + isset($fbSecret) && $fbSecret != 'YOUR_SECRET'; |
| 74 | + } |
| 75 | + return $isSetup; |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * Calls users.getInfo. Requests information about the user from Facebook. |
| 80 | + */ |
| 81 | + public function getUserInfo() { |
| 82 | + // First check to see if we have a session (if not, return null) |
| 83 | + $user = $this->getUser(); |
| 84 | + if ( !$user ) { |
| 85 | + return null; |
| 86 | + } |
| 87 | + try { |
| 88 | + // Cache information about users to reduce the number of Facebook hits |
| 89 | + static $userinfo = array(); |
| 90 | + |
| 91 | + if ( !isset($userinfo[$user]) ) { |
| 92 | + // Query the Facebook API with the users.getInfo method |
| 93 | + $query = array( |
| 94 | + 'method' => 'users.getInfo', |
| 95 | + 'uids' => $user, |
| 96 | + 'fields' => join(',', array( |
| 97 | + 'first_name', 'name', 'sex', 'timezone', 'locale', |
| 98 | + /*'profile_url',*/ |
| 99 | + 'username', 'proxied_email', 'contact_email', |
| 100 | + )), |
| 101 | + ); |
| 102 | + $user_details = $this->api( $query ); |
| 103 | + // Cache the data in the $userinfo array |
| 104 | + $userinfo[$user] = $user_details[0]; |
| 105 | + } |
| 106 | + return isset($userinfo[$user]) ? $userinfo[$user] : null; |
| 107 | + } catch (FacebookApiException $e) { |
| 108 | + error_log( 'Failure in the api when requesting users.getInfo ' . |
| 109 | + "on uid $user: " . $e->getMessage()); |
| 110 | + } |
| 111 | + return null; |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Retrieves group membership data from Facebook. |
| 116 | + */ |
| 117 | + public function getGroupRights( $user = null ) { |
| 118 | + global $fbUserRightsFromGroup; |
| 119 | + |
| 120 | + // Groupies can be members, officers or admins (the latter two infer the former) |
| 121 | + $rights = array('member' => false, |
| 122 | + 'officer' => false, |
| 123 | + 'admin' => false); |
| 124 | + |
| 125 | + $gid = !empty($fbUserRightsFromGroup) ? $fbUserRightsFromGroup : false; |
| 126 | + if (// If no group ID is specified, then there's no group to belong to |
| 127 | + !$gid || |
| 128 | + // If $user wasn't specified, set it to the logged in user |
| 129 | + !$user || |
| 130 | + // If there is no logged in user |
| 131 | + !($user = $this->getUser()) |
| 132 | + ) { |
| 133 | + return $rights; |
| 134 | + } |
| 135 | + |
| 136 | + // If a User object was provided, translate it into a Facebook ID |
| 137 | + if ($user instanceof User) { |
| 138 | + // TODO: Does this call for a special api call without access_token? |
| 139 | + $users = FBConnectDB::getFacebookIDs($user); |
| 140 | + if (count($users)) { |
| 141 | + $user = $users[0]; |
| 142 | + } else { |
| 143 | + // Not a Connected user, can't be in a group |
| 144 | + return $rights; |
| 145 | + } |
| 146 | + } |
| 147 | + |
| 148 | + // Cache the rights for an individual user to prevent hitting Facebook for duplicate info |
| 149 | + static $rights_cache = array(); |
| 150 | + if ( array_key_exists( $user, $rights_cache )) { |
| 151 | + // Retrieve the rights from the cache |
| 152 | + return $rights_cache[$user]; |
| 153 | + } |
| 154 | + |
| 155 | + // This can contain up to 500 IDs, avoid requesting this info twice |
| 156 | + static $members = false; |
| 157 | + // Get a random 500 group members, along with officers, admins and not_replied's |
| 158 | + if ($members === false) { |
| 159 | + try { |
| 160 | + // Check to make sure our session is still valid |
| 161 | + $members = $this->api(array( |
| 162 | + 'method' => 'groups.getMembers', |
| 163 | + 'gid' => $gid |
| 164 | + )); |
| 165 | + } catch (FacebookApiException $e) { |
| 166 | + // Invalid session; we're not going to be able to get the rights |
| 167 | + error_log($e); |
| 168 | + $rights_cache[$user] = $rights; |
| 169 | + return $rights; |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + if ( $members ) { |
| 174 | + // Check to see if the user is an officer |
| 175 | + if (array_key_exists('officers', $members) && in_array($user, $members['officers'])) { |
| 176 | + $rights['member'] = $rights['officer'] = true; |
| 177 | + } |
| 178 | + // Check to see if the user is an admin of the group |
| 179 | + if (array_key_exists('admins', $members) && in_array($user, $members['admins'])) { |
| 180 | + $rights['member'] = $rights['admin'] = true; |
| 181 | + } |
| 182 | + // Because the latter two rights infer the former, this step isn't always necessary |
| 183 | + if( !$rights['member'] ) { |
| 184 | + // Check to see if we are one of the (up to 500) random users |
| 185 | + if ((array_key_exists('not_replied', $members) && is_array($members['not_replied']) && |
| 186 | + in_array($user, $members['not_replied'])) || in_array($user, $members['members'])) { |
| 187 | + $rights['member'] = true; |
| 188 | + } else { |
| 189 | + // For groups of over 500ish, we must use this extra API call |
| 190 | + // Notice that it occurs last, because we can hopefully avoid having to call it |
| 191 | + try { |
| 192 | + $group = $this->api(array( |
| 193 | + 'method' => 'groups.get', |
| 194 | + 'uid' => $user, |
| 195 | + 'gids' => $gid |
| 196 | + )); |
| 197 | + } catch (FacebookApiException $e) { |
| 198 | + error_log($e); |
| 199 | + } |
| 200 | + if (is_array($group) && is_array($group[0]) && $group[0]['gid'] == "$gid") { |
| 201 | + $rights['member'] = true; |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + } |
| 206 | + // Cache the rights |
| 207 | + $rights_cache[$user] = $rights; |
| 208 | + return $rights; |
| 209 | + } |
| 210 | + |
| 211 | + /* |
| 212 | + * Publish message on Facebook wall. |
| 213 | + */ |
| 214 | + public function publishStream($message, $link = "", $link_title ) { |
| 215 | + /* |
| 216 | + $attachment = array( |
| 217 | + 'name' => $message, |
| 218 | + 'href' => $link, |
| 219 | + 'caption' => $caption, |
| 220 | + 'description' => $description |
| 221 | + ); |
| 222 | + |
| 223 | + if( count($media) > 0 ) { |
| 224 | + foreach ( $media as $value ) { |
| 225 | + $attachment[ 'media' ][] = $value; |
| 226 | + } |
| 227 | + } |
| 228 | + /**/ |
| 229 | + // $api_error_descriptions |
| 230 | + $attachment = array(); |
| 231 | + |
| 232 | + $query = array( |
| 233 | + 'method' => 'stream.publish', |
| 234 | + 'attachment' => json_encode($attachment), |
| 235 | + 'action_links' => json_encode(array( |
| 236 | + 'text' => $link_title, |
| 237 | + 'href' => $link |
| 238 | + )), |
| 239 | + ); |
| 240 | + |
| 241 | + $result = json_decode( $this->api( $query ) ); |
| 242 | + |
| 243 | + if ( is_array( $result ) ) { |
| 244 | + // Error |
| 245 | + #error_log(FacebookAPIErrorCodes::$api_error_descriptions[$result]); |
| 246 | + error_log("stream.publish returned error code $result->error_code"); |
| 247 | + return false; |
| 248 | + } else if ( is_string( $result ) ) { |
| 249 | + // Success! Return value is "$UserId_$PostId" |
| 250 | + return true; |
| 251 | + } else { |
| 252 | + error_log("stream.publish: Unknown return type: " . gettype($result)); |
| 253 | + return false; |
| 254 | + } |
| 255 | + } |
| 256 | +} |
Index: trunk/extensions/FBConnect/PreferencesExtension.php |
— | — | @@ -0,0 +1,290 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * See: http://www.mediawiki.org/wiki/Extension:PreferencesExtension |
| 6 | + * |
| 7 | + * This allows other extensions to add their own preferences to the default preferences display |
| 8 | + * |
| 9 | + * Author: Austin Che <http://openwetware.org/wiki/User:Austin_J._Che> |
| 10 | + */ |
| 11 | + |
| 12 | +$wgExtensionFunctions[] = "wfPreferencesExtension"; |
| 13 | +$wgExtensionCredits['specialpage'][] = array( |
| 14 | + 'name' => 'PreferencesExtension', |
| 15 | + 'version' => '2008/02/10.2', |
| 16 | + 'author' => 'Austin Che', |
| 17 | + 'url' => 'http://openwetware.org/wiki/User:Austin_J._Che/Extensions/PreferencesExtension', |
| 18 | + 'description' => 'Enables extending user preferences', |
| 19 | +); |
| 20 | + |
| 21 | +// constants for pref types |
| 22 | +define('PREF_USER_T', 1); |
| 23 | +define('PREF_TOGGLE_T', 2); |
| 24 | +define('PREF_TEXT_T', 3); |
| 25 | +define('PREF_PASSWORD_T', 4); |
| 26 | +define('PREF_INT_T', 5); |
| 27 | +define('PREF_DROPDOWN_T', 6); |
| 28 | +define('PREF_CUSTOM_HTML_T', 6); |
| 29 | + |
| 30 | +// each element of the following should be an array that can have keys: |
| 31 | +// name, section, type, size, validate, load, save, html, min, max, default |
| 32 | +if (!isset($wgExtensionPreferences)) |
| 33 | + $wgExtensionPreferences = array(); |
| 34 | + |
| 35 | +function wfPreferencesExtension() |
| 36 | +{ |
| 37 | + wfProfileIn(__METHOD__); |
| 38 | + |
| 39 | +// wfUseMW('1.7'); |
| 40 | + global $wgHooks; |
| 41 | + $wgHooks['SpecialPage_initList'][] = 'wfOverridePreferences'; |
| 42 | + |
| 43 | + wfProfileOut(__METHOD__); |
| 44 | +} |
| 45 | + |
| 46 | +/** |
| 47 | + * Adds an array of prefs to be displayed in the user preferences |
| 48 | + * |
| 49 | + * @param array $prefs |
| 50 | + */ |
| 51 | +function wfAddPreferences($prefs) |
| 52 | +{ |
| 53 | + global $wgExtensionPreferences; |
| 54 | + wfProfileIn(__METHOD__); |
| 55 | + |
| 56 | + foreach ($prefs as $pref) |
| 57 | + { |
| 58 | + $wgExtensionPreferences[] = $pref; |
| 59 | + } |
| 60 | + wfProfileOut(__METHOD__); |
| 61 | +} |
| 62 | + |
| 63 | +function wfOverridePreferences(&$list) |
| 64 | +{ |
| 65 | + wfProfileIn(__METHOD__); |
| 66 | + |
| 67 | + // we 'override' the default preferences special page with our own |
| 68 | + $list["Preferences"] = array("SpecialPage", "Preferences", "", true, "wfSpecialPreferencesExtension"); |
| 69 | + |
| 70 | + wfProfileOut(__METHOD__); |
| 71 | + return true; |
| 72 | +} |
| 73 | + |
| 74 | +function wfSpecialPreferencesExtension() |
| 75 | +{ |
| 76 | + wfProfileIn(__METHOD__); |
| 77 | + |
| 78 | + global $IP; |
| 79 | + require_once($IP . '/includes/specials/SpecialPreferences.php'); |
| 80 | + |
| 81 | + // override the default preferences form |
| 82 | + class SpecialPreferencesExtension extends PreferencesForm |
| 83 | + { |
| 84 | + // overload to add new field by hook |
| 85 | + function __construct( &$request ) { |
| 86 | + global $wgExtensionPreferences, $wgUser; |
| 87 | + parent::__construct($request); |
| 88 | + wfRunHooks( 'initPreferencesExtensionForm', array( $wgUser, &$wgExtensionPreferences ) ); |
| 89 | + } |
| 90 | + // unlike parent, we don't load in posted form values in constructor |
| 91 | + // until savePreferences when we need it |
| 92 | + // we also don't need resetPrefs, instead loading the newest values when displaying the form |
| 93 | + // finally parent's execute function doesn't need overriding |
| 94 | + // this leaves only two functions to override |
| 95 | + // one for displaying the form and one for saving the values |
| 96 | + |
| 97 | + function savePreferences() |
| 98 | + { |
| 99 | + // handle extension prefs first |
| 100 | + global $wgUser, $wgRequest; |
| 101 | + global $wgExtensionPreferences; |
| 102 | + wfProfileIn(__METHOD__); |
| 103 | + |
| 104 | + foreach ($wgExtensionPreferences as $p) |
| 105 | + { |
| 106 | + $name = isset($p['name']) ? $p['name'] : ""; |
| 107 | + if ( !$name ) { |
| 108 | + continue; |
| 109 | + } |
| 110 | + |
| 111 | + $value = $wgRequest->getVal($name); |
| 112 | + $type = isset($p['type']) ? $p['type'] : PREF_USER_T; |
| 113 | + |
| 114 | + if ( !empty($p['int-type']) ) { |
| 115 | + $type = $p['int-type']; |
| 116 | + } |
| 117 | + |
| 118 | + switch ($type) |
| 119 | + { |
| 120 | + case PREF_TOGGLE_T: |
| 121 | + if (isset($p['save'])) |
| 122 | + $p['save']($name, $value); |
| 123 | + else |
| 124 | + $wgUser->setOption($name, $wgRequest->getCheck("wpOp{$name}")); |
| 125 | + break; |
| 126 | + |
| 127 | + case PREF_INT_T: |
| 128 | + $min = isset($p['min']) ? $p['min'] : 0; |
| 129 | + $max = isset($p['max']) ? $p['max'] : 0x7fffffff; |
| 130 | + if (isset($p['save'])) |
| 131 | + $p['save']($name, $value); |
| 132 | + else |
| 133 | + $wgUser->setOption($name, $this->validateIntOrNull($value, $min, $max)); |
| 134 | + break; |
| 135 | + |
| 136 | + case PREF_DROPDOWN_T: |
| 137 | + if (isset($p['save'])) |
| 138 | + $p['save']($name, $value); |
| 139 | + else |
| 140 | + $wgUser->setOption($name, $value); |
| 141 | + break; |
| 142 | + |
| 143 | + case PREF_PASSWORD_T: |
| 144 | + case PREF_TEXT_T: |
| 145 | + case PREF_USER_T: |
| 146 | + default: |
| 147 | + if (isset($p['validate'])) |
| 148 | + $value = $p['validate']($value); |
| 149 | + if (isset($p['save'])) |
| 150 | + $p['save']($name, $value); |
| 151 | + else |
| 152 | + $wgUser->setOption($name, $value); |
| 153 | + break; |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + // call parent's function which saves the normal prefs and writes to the db |
| 158 | + parent::savePreferences(); |
| 159 | + |
| 160 | + wfProfileOut(__METHOD__); |
| 161 | + } |
| 162 | + |
| 163 | + function mainPrefsForm( $status , $message = '' ) |
| 164 | + { |
| 165 | + global $wgOut, $wgRequest, $wgUser; |
| 166 | + global $wgExtensionPreferences; |
| 167 | + wfProfileIn(__METHOD__); |
| 168 | + |
| 169 | + // first get original form, then hack into it new options |
| 170 | + parent::mainPrefsForm($status, $message); |
| 171 | + $html = $wgOut->getHTML(); |
| 172 | + $wgOut->clearHTML(); |
| 173 | + $sections = array(); |
| 174 | + foreach ($wgExtensionPreferences as $p) |
| 175 | + { |
| 176 | + if (! isset($p['section']) || ! $p['section']) |
| 177 | + continue; |
| 178 | + $section = $p['section']; |
| 179 | + |
| 180 | + $name = isset($p['name']) ? $p['name'] : ""; |
| 181 | + |
| 182 | + $value = ""; |
| 183 | + if ($name) |
| 184 | + { |
| 185 | + if (isset($p['load'])) |
| 186 | + $value = $p['load']($name); |
| 187 | + else |
| 188 | + $value = $wgUser->getOption($name); |
| 189 | + } |
| 190 | + if ($value === '' && isset($p['default'])) |
| 191 | + $value = $p['default']; |
| 192 | + |
| 193 | + $sectext = htmlspecialchars(wfMsg($section)); |
| 194 | + $regex = "/(<fieldset>\s*<legend>\s*" . preg_quote($sectext) . |
| 195 | + "\s*<\/legend>.*?)(<\/fieldset>)/s"; |
| 196 | + |
| 197 | + // check if $section exists in prefs yet |
| 198 | + // cache the existence of sections |
| 199 | + if (!isset($sections[$section])) |
| 200 | + { |
| 201 | + $sections[$section] = true; |
| 202 | + |
| 203 | + if (! preg_match($regex, $html, $m)) |
| 204 | + { |
| 205 | + // doesn't exist so add an empty section to end |
| 206 | + $addhtml = "<fieldset><legend>$sectext</legend></fieldset>\n"; |
| 207 | + $html = preg_replace("/(<(div|table) id='prefsubmit'.*)/s", "$addhtml $1", $html); |
| 208 | + } |
| 209 | + |
| 210 | + } |
| 211 | + |
| 212 | + $type = isset($p['type']) ? $p['type'] : PREF_USER_T; |
| 213 | + |
| 214 | + if ( !empty($p['int-type']) ) { |
| 215 | + $type = $p['int-type']; |
| 216 | + } |
| 217 | + $pos = isset($p['pos']) ? $p['pos'] : ''; |
| 218 | + switch ($type) |
| 219 | + { |
| 220 | + case PREF_CUSTOM_HTML_T: |
| 221 | + $addhtml = $p['html']; |
| 222 | + break; |
| 223 | + |
| 224 | + case PREF_TOGGLE_T: |
| 225 | + $addhtml = $this->getToggle($name); |
| 226 | + break; |
| 227 | + |
| 228 | + case PREF_INT_T: |
| 229 | + case PREF_TEXT_T: |
| 230 | + case PREF_PASSWORD_T: |
| 231 | + $size = isset($p['size']) && $p['size'] ? "size=\"{$p['size']}\"" : ""; |
| 232 | + $caption = isset($p['caption']) && $p['caption'] ? $p['caption'] : wfMsg($name); |
| 233 | + if ($type == PREF_PASSWORD_T) |
| 234 | + $type = "password"; |
| 235 | + else |
| 236 | + $type = "text"; |
| 237 | + |
| 238 | + if ($pos == 'first' || $pos == '') |
| 239 | + $addhtml = "\n<table>\n"; |
| 240 | + else |
| 241 | + $addhtml = ''; |
| 242 | + $addhtml .= $this->addRow("<label for=\"{$name}\">$caption</label>", |
| 243 | + "<input type=\"$type\" id=\"{$name}\" name=\"{$name}\" value=\"{$value}\" $size />")."\n"; |
| 244 | + if ($pos == 'last' || $pos == '') |
| 245 | + $addhtml .="</table>\n"; |
| 246 | + break; |
| 247 | + |
| 248 | + case PREF_DROPDOWN_T: |
| 249 | + $caption = isset($p['caption']) && $p['caption'] ? $p['caption'] : wfMsg($name); |
| 250 | + $onchange = isset($p['onchange']) && $p['onchange'] ? (" onchange='" . $p['onchange'] . "'") : ''; |
| 251 | + $row = "\n <select id=\"{$name}\"$onchange name=\"{$name}\">\n"; |
| 252 | + $options = is_array($p['options']) ? $p['options'] : array(); |
| 253 | + foreach($options as $option) |
| 254 | + { |
| 255 | + $row .= " <option>$option</option>\n"; |
| 256 | + } |
| 257 | + $row .= " </select>"; |
| 258 | + |
| 259 | + if ($pos == 'first' || $pos == '') |
| 260 | + $addhtml = "\n<table>\n"; |
| 261 | + else |
| 262 | + $addhtml = ''; |
| 263 | + $addhtml .= $this->addRow("<label for=\"{$name}\">$caption</label>", $row)."\n"; |
| 264 | + if ($pos == 'last' || $pos == '') |
| 265 | + $addhtml .="</table>\n"; |
| 266 | + break; |
| 267 | + |
| 268 | + case PREF_USER_T: |
| 269 | + default: |
| 270 | + $addhtml = preg_replace("/@VALUE@/", $value, isset($p['html']) ? $p['html'] : ""); |
| 271 | + break; |
| 272 | + } |
| 273 | + |
| 274 | + // the section exists |
| 275 | + $html = preg_replace($regex, "$1 $addhtml $2", $html); |
| 276 | + } |
| 277 | + |
| 278 | + $wgOut->addHTML($html); |
| 279 | + |
| 280 | + // debugging |
| 281 | + //$wgOut->addHTML($wgUser->encodeOptions()); |
| 282 | + wfProfileOut(__METHOD__); |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + global $wgRequest; |
| 287 | + $prefs = new SpecialPreferencesExtension($wgRequest); |
| 288 | + $prefs->execute(); |
| 289 | + |
| 290 | + wfProfileOut(__METHOD__); |
| 291 | +} |
Index: trunk/extensions/FBConnect/FBConnectUser.php |
— | — | @@ -0,0 +1,162 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Class FBConnectUser |
| 30 | + * |
| 31 | + * Extends the User class. |
| 32 | + */ |
| 33 | +class FBConnectUser extends User { |
| 34 | + /** |
| 35 | + * Constructor: Create this object from an existing User. Bonus points if |
| 36 | + * the existing User was created form an ID and has not yet been loaded! |
| 37 | + */ |
| 38 | + function __construct($user) { |
| 39 | + $this->mId = $user->getId(); |
| 40 | + $this->mFrom = 'id'; |
| 41 | + } |
| 42 | + |
| 43 | + /** |
| 44 | + * Update a user's settings with the values retrieved from the current |
| 45 | + * logged-in Facebook user. Settings are only updated if a different value |
| 46 | + * is returned from Facebook and the user's settings allow an update on |
| 47 | + * login. |
| 48 | + */ |
| 49 | + function updateFromFacebook() { |
| 50 | + wfProfileIn(__METHOD__); |
| 51 | + global $facebook; |
| 52 | + |
| 53 | + // Keep track of whether any settings were modified |
| 54 | + $mod = false; |
| 55 | + // Connect to the Facebook API and retrieve the user's info |
| 56 | + $userinfo = $facebook->getUserInfo(); |
| 57 | + // Update the following options if the user's settings allow it |
| 58 | + $updateOptions = array('nickname', 'fullname', 'language', |
| 59 | + 'timecorrection', 'email'); |
| 60 | + foreach ($updateOptions as $option) { |
| 61 | + // Translate Facebook parameters into MediaWiki parameters |
| 62 | + $value = self::getOptionFromInfo($option, $userinfo); |
| 63 | + if ($value && ($this->getOption("fbconnect-update-on-login-$option", "0") == "1")) { |
| 64 | + switch ($option) { |
| 65 | + case 'fullname': |
| 66 | + $this->setRealName($value); |
| 67 | + break; |
| 68 | + default: |
| 69 | + $this->setOption($option, $value); |
| 70 | + } |
| 71 | + $mod = true; |
| 72 | + } |
| 73 | + } |
| 74 | + // Only save the updated settings if something was changed |
| 75 | + if ($mod) { |
| 76 | + $this->saveSettings(); |
| 77 | + } |
| 78 | + |
| 79 | + wfProfileOut(__METHOD__); |
| 80 | + } |
| 81 | + |
| 82 | + /** |
| 83 | + * Helper function for updateFromFacebook(). Takes an array of info from |
| 84 | + * Facebook, and looks up the corresponding MediaWiki parameter. |
| 85 | + */ |
| 86 | + static function getOptionFromInfo($option, $userinfo) { |
| 87 | + // Lookup table for the names of the settings |
| 88 | + $params = array('nickname' => 'username', |
| 89 | + 'fullname' => 'name', |
| 90 | + 'firstname' => 'first_name', |
| 91 | + 'gender' => 'sex', |
| 92 | + 'language' => 'locale', |
| 93 | + 'timecorrection' => 'timezone', |
| 94 | + 'email' => 'contact_email'); |
| 95 | + if (empty($userinfo)) { |
| 96 | + return null; |
| 97 | + } |
| 98 | + $value = array_key_exists($params[$option], $userinfo) ? $userinfo[$params[$option]] : ''; |
| 99 | + // Special handling of several settings |
| 100 | + switch ($option) { |
| 101 | + case 'fullname': |
| 102 | + case 'firstname': |
| 103 | + // If real names aren't allowed, then simply ignore the parameter from Facebook |
| 104 | + global $wgAllowRealName; |
| 105 | + if (!$wgAllowRealName) { |
| 106 | + $value = ''; |
| 107 | + } |
| 108 | + break; |
| 109 | + case 'gender': |
| 110 | + // Unfortunately, Facebook genders are localized (but this might change) |
| 111 | + if ($value != 'male' || $value != 'female') { |
| 112 | + $value = ''; |
| 113 | + } |
| 114 | + break; |
| 115 | + case 'language': |
| 116 | + /** |
| 117 | + * Convert Facebook's locale into a MediaWiki language code. |
| 118 | + * For an up-to-date list of Facebook locales, see: |
| 119 | + * <http://wiki.developers.facebook.com/index.php/Facebook_Locales>. |
| 120 | + * For an up-to-date list of MediaWiki languages, see: |
| 121 | + * <http://svn.wikimedia.org/svnroot/mediawiki/trunk/phase3/languages/Names.php>. |
| 122 | + */ |
| 123 | + if ($value == '') { |
| 124 | + break; |
| 125 | + } |
| 126 | + // These regional languages get special treatment |
| 127 | + $locales = array('en_PI' => 'en', # Pirate English |
| 128 | + 'en_GB' => 'en-gb', # British English |
| 129 | + 'en_UD' => 'en', # Upside Down English |
| 130 | + 'fr_CA' => 'fr', # Canadian French |
| 131 | + 'fb_LT' => 'en', # Leet Speak |
| 132 | + 'pt_BR' => 'pt-br', # Brazilian Portuguese |
| 133 | + 'zh_CN' => 'zh-cn', # Simplified Chinese |
| 134 | + 'es_ES' => 'es', # European Spanish |
| 135 | + 'zh_HK' => 'zh-hk', # Traditional Chinese (Hong Kong) |
| 136 | + 'zh_TW' => 'zh-tw'); # Traditional Chinese (Taiwan) |
| 137 | + if (array_key_exists($value, $locales)) { |
| 138 | + $value = $locales[$value]; |
| 139 | + } else { |
| 140 | + // No special regional treatment exists in MW; chop it off |
| 141 | + $value = substr($value, 0, 2); |
| 142 | + } |
| 143 | + break; |
| 144 | + case 'timecorrection': |
| 145 | + // Convert the timezone into a local timezone correction |
| 146 | + // TODO: $value = TimezoneToOffset($value); |
| 147 | + $value = ''; |
| 148 | + break; |
| 149 | + case 'email': |
| 150 | + // If a contact email isn't available, then use a proxied email |
| 151 | + if ($value == '') { |
| 152 | + // TODO: update the user's email from $userinfo['proxied_email'] |
| 153 | + // instead (the address must stay hidden from the user) |
| 154 | + $value = ''; |
| 155 | + } |
| 156 | + // TODO: if the user's email is updated from Facebook, then |
| 157 | + // automatically authenticate the email address |
| 158 | + #$user->mEmailAuthenticated = wfTimestampNow(); |
| 159 | + } |
| 160 | + // If an appropriate value was found, return it |
| 161 | + return $value == '' ? null : $value; |
| 162 | + } |
| 163 | +} |
Index: trunk/extensions/FBConnect/FBConnect.php |
— | — | @@ -0,0 +1,215 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2008-2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | +/** |
| 20 | + * FBConnect plugin. Integrates Facebook Connect into MediaWiki. |
| 21 | + * |
| 22 | + * Facebook Connect single sign on (SSO) experience and XFBML are currently available. |
| 23 | + * Please rename config.sample.php to config.php, follow the instructions inside and |
| 24 | + * customize variables as you like to set up your Facebook Connect extension. |
| 25 | + * |
| 26 | + * Info is available at http://www.mediawiki.org/wiki/Extension:FBConnect |
| 27 | + * and at http://wiki.developers.facebook.com/index.php/MediaWiki |
| 28 | + * |
| 29 | + * Limited support is available at http://www.mediawiki.org/wiki/Extension_talk:FBConnect |
| 30 | + * and at http://wiki.developers.facebook.com/index.php/Talk:MediaWiki |
| 31 | + * |
| 32 | + * @author Garrett Bruin |
| 33 | + * @copyright Copyright � 2008 Garrett Brown |
| 34 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later |
| 35 | + * @addtogroup Extensions |
| 36 | + */ |
| 37 | + |
| 38 | + |
| 39 | +/* |
| 40 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 41 | + */ |
| 42 | +if ( !defined( 'MEDIAWIKI' )) { |
| 43 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 44 | +} |
| 45 | + |
| 46 | +/* |
| 47 | + * FBConnect version. Note: this is not necessarily the most recent SVN revision number. |
| 48 | + */ |
| 49 | +define( 'MEDIAWIKI_FBCONNECT_VERSION', 'r203, May 22, 2010' ); |
| 50 | + |
| 51 | +/* |
| 52 | + * Add information about this extension to Special:Version. |
| 53 | + */ |
| 54 | +$wgExtensionCredits['specialpage'][] = array( |
| 55 | + 'name' => 'Facebook Connect Plugin', |
| 56 | + 'author' => 'Garrett Brown', |
| 57 | + 'url' => 'http://www.mediawiki.org/wiki/Extension:FBConnect', |
| 58 | + 'descriptionmsg' => 'fbconnect-desc', |
| 59 | + 'version' => MEDIAWIKI_FBCONNECT_VERSION, |
| 60 | +); |
| 61 | + |
| 62 | +/* |
| 63 | + * Initialization of the autoloaders and special extension pages. |
| 64 | + */ |
| 65 | +$dir = dirname(__FILE__) . '/'; |
| 66 | +require_once $dir . 'config.php'; |
| 67 | +require_once $dir . 'php-sdk/facebook.php'; |
| 68 | +if (!empty( $fbIncludePreferencesExtension ) && version_compare($wgVersion, '1.16', '<')) { |
| 69 | + require_once $dir . 'PreferencesExtension.php'; |
| 70 | +} |
| 71 | + |
| 72 | +$wgExtensionFunctions[] = 'FBConnect::init'; |
| 73 | + |
| 74 | +if( !empty( $fbEnablePushToFacebook ) ){ |
| 75 | + // Need to include it explicitly instead of autoload since it has initialization code of its own. |
| 76 | + // This should be done after FBConnect::init is added to wgExtensionFunctions so that FBConnect |
| 77 | + // gets fully initialized first. |
| 78 | + require_once $dir . 'FBConnectPushEvent.php'; |
| 79 | +} |
| 80 | + |
| 81 | +$wgExtensionMessagesFiles['FBConnect'] = $dir . 'FBConnect.i18n.php'; |
| 82 | +$wgExtensionAliasesFiles['FBConnect'] = $dir . 'FBConnect.alias.php'; |
| 83 | + |
| 84 | +$wgAutoloadClasses['FBConnectAPI'] = $dir . 'FBConnectAPI.php'; |
| 85 | +$wgAutoloadClasses['FBConnectDB'] = $dir . 'FBConnectDB.php'; |
| 86 | +$wgAutoloadClasses['FBConnectHooks'] = $dir . 'FBConnectHooks.php'; |
| 87 | +$wgAutoloadClasses['FBConnectUser'] = $dir . 'FBConnectUser.php'; |
| 88 | +$wgAutoloadClasses['FBConnectXFBML'] = $dir . 'FBConnectXFBML.php'; |
| 89 | +$wgAutoloadClasses['SpecialConnect'] = $dir . 'SpecialConnect.php'; |
| 90 | + |
| 91 | +$wgSpecialPages['Connect'] = 'SpecialConnect'; |
| 92 | + |
| 93 | +// Define new autopromote condition (use quoted text, numbers can cause collisions) |
| 94 | +define( 'APCOND_FB_INGROUP', 'fb*g' ); |
| 95 | +define( 'APCOND_FB_ISOFFICER', 'fb*o' ); |
| 96 | +define( 'APCOND_FB_ISADMIN', 'fb*a' ); |
| 97 | + |
| 98 | +// Create a new group for Facebook users |
| 99 | +$wgGroupPermissions['fb-user'] = $wgGroupPermissions['user']; |
| 100 | + |
| 101 | +// If we are configured to pull group info from Facebook, then create the group permissions |
| 102 | +if ($fbUserRightsFromGroup) { |
| 103 | + $wgGroupPermissions['fb-groupie'] = $wgGroupPermissions['user']; |
| 104 | + $wgGroupPermissions['fb-officer'] = $wgGroupPermissions['bureaucrat']; |
| 105 | + $wgGroupPermissions['fb-admin'] = $wgGroupPermissions['sysop']; |
| 106 | + $wgImplictGroups[] = 'fb-groupie'; |
| 107 | + $wgImplictGroups[] = 'fb-officer'; |
| 108 | + $wgImplictGroups[] = 'fb-admin'; |
| 109 | + $wgAutopromote['fb-groupie'] = APCOND_FB_INGROUP; |
| 110 | + $wgAutopromote['fb-officer'] = APCOND_FB_ISOFFICER; |
| 111 | + $wgAutopromote['fb-admin'] = APCOND_FB_ISADMIN; |
| 112 | +} |
| 113 | + |
| 114 | +/** |
| 115 | +$wgAutopromote['autoconfirmed'] = array( '&', array( APCOND_EDITCOUNT, &$wgAutoConfirmCount ), |
| 116 | + array( APCOND_AGE, &$wgAutoConfirmAge ), |
| 117 | + array( APCOND_FB_INGROUP )); |
| 118 | +/**/ |
| 119 | + |
| 120 | + |
| 121 | +/** |
| 122 | + * Class FBConnect |
| 123 | + * |
| 124 | + * This class initializes the extension, and contains the core non-hook, |
| 125 | + * non-authentification code. |
| 126 | + */ |
| 127 | +class FBConnect { |
| 128 | + static private $fbOnLoginJs; |
| 129 | + |
| 130 | + /** |
| 131 | + * Initializes and configures the extension. |
| 132 | + */ |
| 133 | + public static function init() { |
| 134 | + global $wgXhtmlNamespaces, $wgSharedTables, $facebook, $wgHooks; |
| 135 | + global $fbOnLoginJsOverride; |
| 136 | + |
| 137 | + // The xmlns:fb attribute is required for proper rendering on IE |
| 138 | + $wgXhtmlNamespaces['fb'] = 'http://www.facebook.com/2008/fbml'; |
| 139 | + |
| 140 | + // Facebook/username associations should be shared when $wgSharedDB is enabled |
| 141 | + $wgSharedTables[] = 'user_fbconnect'; |
| 142 | + |
| 143 | + // Create our Facebook instance and make it available through $facebook |
| 144 | + $facebook = new FBConnectAPI(); |
| 145 | + |
| 146 | + // Install all public static functions in class FBConnectHooks as MediaWiki hooks |
| 147 | + $hooks = self::enumMethods('FBConnectHooks'); |
| 148 | + foreach( $hooks as $hookName ) { |
| 149 | + $wgHooks[$hookName][] = "FBConnectHooks::$hookName"; |
| 150 | + } |
| 151 | + |
| 152 | + // Allow configurable over-riding of the onLogin handler. |
| 153 | + if(!empty($fbOnLoginJsOverride)){ |
| 154 | + self::$fbOnLoginJs = $fbOnLoginJsOverride; |
| 155 | + } else { |
| 156 | + self::$fbOnLoginJs = "window.location.reload(true);"; |
| 157 | + } |
| 158 | + |
| 159 | + // ParserFirstCallInit was introduced in modern (1.12+) MW versions so as to |
| 160 | + // avoid unstubbing $wgParser on setHook() too early, as per r35980 |
| 161 | + if (!defined( 'MW_SUPPORTS_PARSERFIRSTCALLINIT' )) { |
| 162 | + global $wgParser; |
| 163 | + wfRunHooks( 'ParserFirstCallInit', $wgParser ); |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + /** |
| 168 | + * Returns an array with the names of all public static functions |
| 169 | + * in the specified class. |
| 170 | + */ |
| 171 | + public static function enumMethods( $className ) { |
| 172 | + $hooks = array(); |
| 173 | + try { |
| 174 | + $class = new ReflectionClass( $className ); |
| 175 | + foreach( $class->getMethods( ReflectionMethod::IS_PUBLIC ) as $method ) { |
| 176 | + if ( $method->isStatic() ) { |
| 177 | + $hooks[] = $method->getName(); |
| 178 | + } |
| 179 | + } |
| 180 | + } catch( Exception $e ) { |
| 181 | + // If PHP's version doesn't support the Reflection API, then exit |
| 182 | + die( 'PHP version (' . phpversion() . ') must be great enough to support the Reflection API' ); |
| 183 | + // Or list the extensions here manually... |
| 184 | + $hooks = array('AuthPluginSetup', 'UserLoadFromSession', |
| 185 | + 'RenderPreferencesForm', 'PersonalUrls', |
| 186 | + 'ParserAfterTidy', 'BeforePageDisplay', /*...*/ ); |
| 187 | + } |
| 188 | + return $hooks; |
| 189 | + } |
| 190 | + |
| 191 | + /** |
| 192 | + * Return the code for the permissions attribute (with leading space) to use on all fb:login-buttons. |
| 193 | + */ |
| 194 | + public static function getPermissionsAttribute(){ |
| 195 | + global $fbExtendedPermissions; |
| 196 | + $attr = ""; |
| 197 | + if(!empty($fbExtendedPermissions)){ |
| 198 | + $attr = " perms=\"".implode(",", $fbExtendedPermissions)."\""; |
| 199 | + } |
| 200 | + return $attr; |
| 201 | + } // end getPermissionsAttribute() |
| 202 | + |
| 203 | + /** |
| 204 | + * Return the code for the onlogin attribute which should be appended to all fb:login-button's in this |
| 205 | + * extension. |
| 206 | + * |
| 207 | + * TODO: Generate the entire fb:login-button in a function in this class. We have numerous buttons now. |
| 208 | + */ |
| 209 | + public static function getOnLoginAttribute(){ |
| 210 | + $attr = ""; |
| 211 | + if(!empty(self::$fbOnLoginJs)){ |
| 212 | + $attr = " onlogin=\"".self::$fbOnLoginJs."\""; |
| 213 | + } |
| 214 | + return $attr; |
| 215 | + } // end getOnLoginAttribute() |
| 216 | +} |
Index: trunk/extensions/FBConnect/fbconnect.css |
— | — | @@ -0,0 +1,78 @@ |
| 2 | +/* Info about Facebook Connect on page Special:Connect */ |
| 3 | +#specialconnect_info, #specialconnect_box { |
| 4 | + font-family: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif; |
| 5 | +} |
| 6 | + |
| 7 | +#specialconnect_info p { |
| 8 | + font-size: 14px; |
| 9 | + color: #888; |
| 10 | + margin: 16px 0; |
| 11 | +} |
| 12 | + |
| 13 | +#specialconnect_info th, #specialconnect_info td { |
| 14 | + color: #333; |
| 15 | + text-align: left; |
| 16 | + vertical-align: top; |
| 17 | + padding: 0 10px 0 0; |
| 18 | +} |
| 19 | + |
| 20 | +/* Login and Connect boxes */ |
| 21 | +#specialconnect_boxarea { |
| 22 | + border-collapse: collapse; |
| 23 | + width: 92%; |
| 24 | + margin: 28px 0; |
| 25 | +} |
| 26 | + |
| 27 | +#specialconnect_boxarea .box_left, #specialconnect_boxarea .box_right { |
| 28 | + vertical-align: top; |
| 29 | + width: 50%; |
| 30 | + padding: 0 14px; |
| 31 | +} |
| 32 | + |
| 33 | +.box_left #specialconnect_box { |
| 34 | + float: right; |
| 35 | +} |
| 36 | + |
| 37 | +.box_right #specialconnect_box { |
| 38 | + float: left; |
| 39 | +} |
| 40 | + |
| 41 | +/* Facebook-styled box */ |
| 42 | +#specialconnect_box .box_content { |
| 43 | + color: #333; |
| 44 | + font-size: 12px; |
| 45 | + font-weight: normal; |
| 46 | + margin: 0; |
| 47 | + padding: 0; |
| 48 | +} |
| 49 | + |
| 50 | +#specialconnect_box h1 { |
| 51 | + color: #333; |
| 52 | + font-size: 13px; |
| 53 | + font-weight: bold; |
| 54 | + border-top: 1px solid #94A3C4; |
| 55 | + border-bottom: 1px solid #CCCCCC; |
| 56 | + background-color: #D8DFEA; |
| 57 | +} |
| 58 | + |
| 59 | +#specialconnect_box h1 p { |
| 60 | + margin-top: 0; |
| 61 | + margin-bottom: 0; |
| 62 | +} |
| 63 | + |
| 64 | +#specialconnect_box h1, #specialconnect_box .box_content { |
| 65 | + margin-bottom: 2px; |
| 66 | + padding: 1px 5px; |
| 67 | +} |
| 68 | + |
| 69 | +.box_left #specialconnect_box h1, .box_left #specialconnect_box .box_content { |
| 70 | + padding-right: 120px; |
| 71 | +} |
| 72 | + |
| 73 | +.box_left #specialconnect_box img { |
| 74 | + float: right; |
| 75 | + border-collapse: collapse; |
| 76 | + margin: 7px; |
| 77 | + padding: 0; |
| 78 | + width: 100px; |
| 79 | +} |
Index: trunk/extensions/FBConnect/FBConnectPushEvent.php |
— | — | @@ -0,0 +1,184 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @author Sean Colombo |
| 5 | + * |
| 6 | + * This class is an extendable superclass for events to push to a facebook news-feed. |
| 7 | + * |
| 8 | + * To create a push event, override this class, then add it to config.php in the way |
| 9 | + * defined in config.sample.php. |
| 10 | + */ |
| 11 | + |
| 12 | +$wgExtensionFunctions[] = 'FBConnectPushEvent::initExtension'; |
| 13 | + |
| 14 | +// PreferencesExtension is needed up until 1.16, then the needed functionality is built in. |
| 15 | +$wgHooks['GetPreferences'][] = 'FBConnectPushEvent::addPreferencesToggles'; |
| 16 | +$wgHooks['initPreferencesExtensionForm'][] = 'FBConnectPushEvent::addPreferencesToggles'; |
| 17 | + |
| 18 | +class FBConnectPushEvent { |
| 19 | + protected $isAllowedUserPreferenceName = ''; // implementing classes MUST override this with their own value. |
| 20 | + |
| 21 | + // This must correspond to the name of the message for the text on the tab itself. |
| 22 | + static protected $PREFERENCES_TAB_NAME = "fbconnect-prefstext"; |
| 23 | + |
| 24 | + /** |
| 25 | + * Accessor for the user preference to which (if set to 1) allows this type of event |
| 26 | + * to be used. |
| 27 | + */ |
| 28 | + public function getUserPreferenceName(){ |
| 29 | + return $this->isAllowedUserPreferenceName; |
| 30 | + } |
| 31 | + |
| 32 | + /** |
| 33 | + * Initialize the extension itself. This includes creating the user-preferences for |
| 34 | + * the push events. |
| 35 | + */ |
| 36 | + static public function initExtension(){ |
| 37 | + wfProfileIn(__METHOD__); |
| 38 | + |
| 39 | + // The push feature of the extension requires the publish_stream extended permission. |
| 40 | + global $fbExtendedPermissions; |
| 41 | + $PERMISSION_TO_PUBLISH = 'publish_stream'; |
| 42 | + if(empty($fbExtendedPermissions) || !is_array($fbExtendedPermissions)){ |
| 43 | + $fbExtendedPermissions = array($PERMISSION_TO_PUBLISH); |
| 44 | + } else if(!in_array($PERMISSION_TO_PUBLISH, $fbExtendedPermissions)){ |
| 45 | + $fbExtendedPermissions[] = $PERMISSION_TO_PUBLISH; |
| 46 | + } |
| 47 | + |
| 48 | + // Make sure that all of the push events were configured correctly. |
| 49 | + self::initAll(); |
| 50 | + |
| 51 | + // TODO: This initialization should only be run if the user is fb-connected. Otherwise, the same Connect form as Special:Connect should be shown. |
| 52 | + |
| 53 | + // TODO: Can we detect if this is Special:Preferences and only add the checkboxes if that is the case? Can't think of anything else that would break. |
| 54 | + |
| 55 | + wfProfileOut(__METHOD__); |
| 56 | + } // end initExtension() |
| 57 | + |
| 58 | + /** |
| 59 | + * Adds enable/disable toggles to the Preferences form for controlling all push events. |
| 60 | + * |
| 61 | + */ |
| 62 | + static public function addPreferencesToggles( $user, &$preferences ){ |
| 63 | + global $fbPushEventClasses; |
| 64 | + |
| 65 | + // TODO: PREF_TOGGLE_T is not defined in v1.16 |
| 66 | + /** |
| 67 | + if( !defined('PREF_TOGGLE_T') ) { |
| 68 | + return true; |
| 69 | + } |
| 70 | + /**/ |
| 71 | + |
| 72 | + if( !empty($fbPushEventClasses) ){ |
| 73 | + foreach($fbPushEventClasses as $pushEventClassName){ |
| 74 | + $pushObj = new $pushEventClassName; |
| 75 | + $className = get_class(); |
| 76 | + $prefName = $pushObj->getUserPreferenceName(); |
| 77 | + |
| 78 | + $preferences[$prefName] = array( |
| 79 | + 'type' => 'toggle', |
| 80 | + 'label-message' => $prefName, |
| 81 | + 'section' => self::$PREFERENCES_TAB_NAME, |
| 82 | + 'default' => '1', |
| 83 | + ); |
| 84 | + |
| 85 | + // Prior to v1.16 |
| 86 | + if( defined('PREF_TOGGLE_T') ) { |
| 87 | + $preferences[$prefName]['int-type'] = PREF_TOGGLE_T; |
| 88 | + $preferences[$prefName]['name'] = $prefName; |
| 89 | + } |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + return true; |
| 94 | + } // end addPreferencesToggles() |
| 95 | + |
| 96 | + /** |
| 97 | + * This function returns HTML which contains toggles (in a list) for setting the push |
| 98 | + * Preferences. It is designed to be used inside of a form (such as on Special:Connect). |
| 99 | + * |
| 100 | + * This is not used by the code which adds the form to Special:Preferences. |
| 101 | + * |
| 102 | + * If firstTime is set to true, the checkboxes will default to being checked, otherwise |
| 103 | + * they will default to the current user-option setting for the user. |
| 104 | + */ |
| 105 | + static public function createPreferencesToggles($firstTime = false){ |
| 106 | + global $wgUser, $wgLang, $fbPushEventClasses; |
| 107 | + wfProfileIn(__METHOD__); |
| 108 | + |
| 109 | + $html = ""; |
| 110 | + if(!empty($fbPushEventClasses)){ |
| 111 | + foreach($fbPushEventClasses as $pushEventClassName){ |
| 112 | + $pushObj = new $pushEventClassName; |
| 113 | + $className = get_class(); |
| 114 | + $prefName = $pushObj->getUserPreferenceName(); |
| 115 | + |
| 116 | + $prefText = $wgLang->getUserToggle( $prefName ); |
| 117 | + if($firstTime){ |
| 118 | + $checked = ' checked="checked"'; |
| 119 | + } else { |
| 120 | + $checked = $wgUser->getOption( $prefName ) == 1 ? ' checked="checked"' : ''; |
| 121 | + } |
| 122 | + $html .= "<div class='toggle'>"; |
| 123 | + $html .= "<input type='checkbox' value='1' id=\"$prefName\" name=\"$prefName\"$checked />"; |
| 124 | + $html .= "<label for=\"$prefName\">$prefText</label>"; |
| 125 | + $html .= "</div>\n"; |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + wfProfileOut(__METHOD__); |
| 130 | + return $html; |
| 131 | + } // end createPreferencesToggles() |
| 132 | + |
| 133 | + /** |
| 134 | + * This static function is called by the FBConnect extension if push events are enabled. It checks |
| 135 | + * to make sure that the configured push-events are valid and then gives them each a chance to initialize. |
| 136 | + */ |
| 137 | + static public function initAll(){ |
| 138 | + global $fbPushEventClasses, $wgUser; |
| 139 | + wfProfileIn(__METHOD__); |
| 140 | + |
| 141 | + if( !empty( $fbPushEventClasses ) ) { |
| 142 | + // Fail fast (and hard) if a push event was coded incorrectly. |
| 143 | + foreach($fbPushEventClasses as $pushEventClassName){ |
| 144 | + $pushObj = new $pushEventClassName; |
| 145 | + $className = get_class(); |
| 146 | + $prefName = $pushObj->getUserPreferenceName(); |
| 147 | + if(empty($prefName)){ |
| 148 | + $dirName = dir( __FILE__ ); |
| 149 | + $msg = "FATAL ERROR: The push event class <strong>\"$pushEventClassName\"</strong> does not return a valid user preference name! "; |
| 150 | + $msg.= " It was probably written incorrectly. Either fix the class or remove it from being used in <strong>$dirName/config.php</strong>"; |
| 151 | + die($msg); |
| 152 | + } else if(!is_subclass_of($pushObj, $className)){ |
| 153 | + $msg = "FATAL ERROR: The push event class <strong>\"$pushEventClassName\"</strong> is not a subclass of <strong>$className</strong>! "; |
| 154 | + $msg.= " It was probably written incorrectly. Either fix the class or remove it from being used in <strong>$dirName/config.php</strong>"; |
| 155 | + die($msg); |
| 156 | + } |
| 157 | + |
| 158 | + // The push event is valid, let it initialize itself if needed. |
| 159 | + if( $wgUser->getOption($prefName) ) { |
| 160 | + $pushObj->init(); |
| 161 | + } |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + wfProfileOut(__METHOD__); |
| 166 | + } |
| 167 | + |
| 168 | + /** |
| 169 | + * Overridable function to do any initialization needed by the push event. |
| 170 | + * |
| 171 | + * This is only called if this particular push-event is enabled in config.php |
| 172 | + * and the getUserPreferenceName() call checks out (the result must be non-empty). |
| 173 | + */ |
| 174 | + public function init(){} |
| 175 | + |
| 176 | + /** |
| 177 | + * Put Facebook message. |
| 178 | + */ |
| 179 | + public function pushEvent($message, $link = null, $link_title = null) { |
| 180 | + global $facebook; |
| 181 | + |
| 182 | + return $facebook->publishStream($message, $link, $link_title); |
| 183 | + } |
| 184 | + |
| 185 | +} // end FBConnectPushEvent class |
Index: trunk/extensions/FBConnect/FBConnectHooks.php |
— | — | @@ -0,0 +1,596 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2008-2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Class FBConnectHooks |
| 30 | + * |
| 31 | + * This class contains all the hooks used in this extension. HOOKS DO NOT NEED |
| 32 | + * TO BE EXPLICITLY ADDED TO $wgHooks. Simply write a public static function |
| 33 | + * with the same name as the hook that provokes it, place it inside this class |
| 34 | + * and let FBConnect::init() do its magic. Helper functions should be private, |
| 35 | + * because only public static methods are added as hooks. |
| 36 | + */ |
| 37 | +class FBConnectHooks { |
| 38 | + /** |
| 39 | + * Hook is called whenever an article is being viewed... Currently, figures |
| 40 | + * out the Facebook ID of the user that the userpage belongs to. |
| 41 | + */ |
| 42 | + public static function ArticleViewHeader( &$article, &$outputDone, &$pcache ) { |
| 43 | + global $wgOut; |
| 44 | + // Get the article title |
| 45 | + $nt = $article->getTitle(); |
| 46 | + // If the page being viewed is a user page |
| 47 | + if ($nt && $nt->getNamespace() == NS_USER && strpos($nt->getText(), '/') === false) { |
| 48 | + $user = User::newFromName($nt->getText()); |
| 49 | + if (!$user || $user->getID() == 0) { |
| 50 | + return true; |
| 51 | + } |
| 52 | + $fb_id = FBConnectDB::getFacebookIDs($user->getId()); |
| 53 | + if (!count($fb_id) || !($fb_id = $fb_id[0])) { |
| 54 | + return true; |
| 55 | + } |
| 56 | + // TODO: Something with the Facebook ID stored in $fb_id |
| 57 | + return true; |
| 58 | + } |
| 59 | + return true; |
| 60 | + } |
| 61 | + |
| 62 | + /** |
| 63 | + * Checks the autopromote condition for a user. |
| 64 | + */ |
| 65 | + static function AutopromoteCondition( $cond_type, $args, $user, &$result ) { |
| 66 | + global $fbUserRightsFromGroup; |
| 67 | + |
| 68 | + // Probably a redundant check, but with PHP you can never be too sure... |
| 69 | + if (!$fbUserRightsFromGroup) { |
| 70 | + // No group to pull rights from, so the user can't be a member |
| 71 | + $result = false; |
| 72 | + return true; |
| 73 | + } |
| 74 | + $types = array(APCOND_FB_INGROUP => 'member', |
| 75 | + APCOND_FB_ISOFFICER => 'officer', |
| 76 | + APCOND_FB_ISADMIN => 'admin'); |
| 77 | + $type = $types[$cond_type]; |
| 78 | + switch( $type ) { |
| 79 | + case 'member': |
| 80 | + case 'officer': |
| 81 | + case 'admin': |
| 82 | + global $facebook; |
| 83 | + // Connect to the Facebook API and ask if the user is in the group |
| 84 | + $rights = $facebook->getGroupRights($user); |
| 85 | + $result = $rights[$type]; |
| 86 | + } |
| 87 | + |
| 88 | + return true; |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Injects some important CSS and Javascript into the <head> of the page. |
| 93 | + */ |
| 94 | + public static function BeforePageDisplay( &$out, &$sk ) { |
| 95 | + global $wgVersion, $fbLogo, $fbScript, $fbExtensionScript, $fbIncludeJquery, |
| 96 | + $wgScriptPath, $wgJsMimeType, $wgStyleVersion; |
| 97 | + |
| 98 | + // Asynchronously load the Facebook Connect JavaScript SDK before the page's content |
| 99 | + if(!empty($fbScript)){ |
| 100 | + $out->prependHTML(' |
| 101 | + <div id="fb-root"></div> |
| 102 | + <script> |
| 103 | + (function(){var e=document.createElement("script");e.type="' . |
| 104 | + $wgJsMimeType . '";e.src="' . $fbScript . |
| 105 | + '";e.async=true;document.getElementById("fb-root").appendChild(e)})(); |
| 106 | + </script>' . "\n" |
| 107 | + ); |
| 108 | + } |
| 109 | + |
| 110 | + // Inserts list of global JavaScript variables if necessary |
| 111 | + if (self::MGVS_hack( $mgvs_script )) { |
| 112 | + $out->addInlineScript( $mgvs_script ); |
| 113 | + } |
| 114 | + |
| 115 | + // Add a Facebook logo to the class .mw-fblink |
| 116 | + $style = empty($fbLogo) ? '' : <<<STYLE |
| 117 | + /* Add a pretty logo to Facebook links */ |
| 118 | + .mw-fblink { |
| 119 | + background: url($fbLogo) top left no-repeat !important; |
| 120 | + padding-left: 17px !important; |
| 121 | + } |
| 122 | +STYLE; |
| 123 | + |
| 124 | + // Things get a little simpler in 1.16... |
| 125 | + if (version_compare($wgVersion, '1.16', '>=')) { |
| 126 | + // Add a pretty Facebook logo if $fbLogo is set |
| 127 | + if ( !empty( $fbLogo) ) { |
| 128 | + $out->addInlineStyle($style); |
| 129 | + } |
| 130 | + |
| 131 | + // Don't include jQuery if it's already in use on the site |
| 132 | + #$out->includeJQuery(); |
| 133 | + // Temporary workaround until until MW is bundled with jQuery 1.4.2: |
| 134 | + $out->addScriptFile('http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js'); |
| 135 | + |
| 136 | + // Add the script file specified by $url |
| 137 | + if(!empty($fbExtensionScript)){ |
| 138 | + $out->addScriptFile($fbExtensionScript); |
| 139 | + } |
| 140 | + } else { |
| 141 | + // Add a pretty Facebook logo if $fbLogo is set |
| 142 | + if ( !empty( $fbLogo) ) { |
| 143 | + $out->addScript('<style type="text/css">' . $style . '</style>'); |
| 144 | + } |
| 145 | + |
| 146 | + // Don't include jQuery if it's already in use on the site |
| 147 | + if (!empty($fbIncludeJquery)){ |
| 148 | + $out->addScriptFile("http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"); |
| 149 | + } |
| 150 | + |
| 151 | + // Add the script file specified by $url |
| 152 | + if(!empty($fbExtensionScript)){ |
| 153 | + $out->addScript("<script type=\"$wgJsMimeType\" src=\"$fbExtensionScript?$wgStyleVersion\"></script>\n"); |
| 154 | + } |
| 155 | + } |
| 156 | + return true; |
| 157 | + } |
| 158 | + |
| 159 | + /** |
| 160 | + * Fired when MediaWiki is updated to allow FBConnect to update the database. |
| 161 | + * If the database type is supported, then a new tabled named 'user_fbconnect' |
| 162 | + * is created. For the table's layout, see fbconnect_table.sql. If $wgDBprefix |
| 163 | + * is set, then the table 'user_fbconnect' will be prefixed accordingly. Make |
| 164 | + * sure that fbconnect_table.sql is updated with the database prefix beforehand. |
| 165 | + */ |
| 166 | + static function LoadExtensionSchemaUpdates() { |
| 167 | + global $wgDBtype, $wgDBprefix, $wgExtNewTables; |
| 168 | + $base = dirname( __FILE__ ); |
| 169 | + if ( $wgDBtype == 'mysql' ) { |
| 170 | + $wgExtNewTables[] = array("{$wgDBprefix}user_fbconnect", "$base/fbconnect_table.sql"); |
| 171 | + } else if ( $wgDBtype == 'postgres' ) { |
| 172 | + $wgExtNewTables[] = array("{$wgDBprefix}user_fbconnect", "$base/fbconnect_table.pg.sql"); |
| 173 | + } |
| 174 | + return true; |
| 175 | + } |
| 176 | + |
| 177 | + /** |
| 178 | + * Adds several Facebook Connect variables to the page: |
| 179 | + * |
| 180 | + * fbAppId The application ID (see $fbAppId in config.php) |
| 181 | + * fbSession Assist the JavaScript SDK with loading the session |
| 182 | + * fbUseMarkup Should XFBML tags be rendered (see $fbUseMarkup in config.php) |
| 183 | + * fbLogo Facebook logo (see $fbLogo in config.php) |
| 184 | + * fbLogoutURL The URL to be redirected to on a disconnect |
| 185 | + * |
| 186 | + * This hook was added in MediaWiki version 1.14. See: |
| 187 | + * http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Skin.php?view=log&pathrev=38397 |
| 188 | + * If we are not at revision 38397 or later, this function is called from BeforePageDisplay |
| 189 | + * to retain backward compatability. |
| 190 | + */ |
| 191 | + public static function MakeGlobalVariablesScript( &$vars ) { |
| 192 | + global $fbAppId, $facebook, $fbUseMarkup, $fbLogo, $wgTitle, $wgRequest; |
| 193 | + $vars['fbAppId'] = $fbAppId; |
| 194 | + $vars['fbSession'] = $facebook->getSession(); |
| 195 | + $vars['fbUseMarkup'] = $fbUseMarkup; |
| 196 | + $vars['fbLogo'] = $fbLogo ? true : false; |
| 197 | + $vars['fbLogoutURL'] = Skin::makeSpecialUrl('Userlogout', |
| 198 | + $wgTitle->isSpecial('Preferences') ? '' : |
| 199 | + "returnto={$wgTitle->getPrefixedURL()}"); |
| 200 | + $query = $wgRequest->getValues(); |
| 201 | + $vars['wgPagequery'] = wfUrlencode( wfArrayToCGI( $query ) ); |
| 202 | + return true; |
| 203 | + } |
| 204 | + |
| 205 | + /** |
| 206 | + * Hack: Run MakeGlobalVariablesScript for backwards compatability. |
| 207 | + * The MakeGlobalVariablesScript hook was added to MediaWiki 1.14 in revision 38397: |
| 208 | + * http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Skin.php?view=log&pathrev=38397 |
| 209 | + */ |
| 210 | + private static function MGVS_hack( &$script ) { |
| 211 | + global $wgVersion, $IP; |
| 212 | + if (version_compare($wgVersion, '1.14', '<')) { |
| 213 | + $svn = SpecialVersion::getSvnRevision($IP); |
| 214 | + // if !$svn, then we must be using 1.13.x (as opposed to 1.14alpha+) |
| 215 | + if (!$svn || $svn < 38397) |
| 216 | + { |
| 217 | + $script = ""; |
| 218 | + $vars = array(); |
| 219 | + wfRunHooks('MakeGlobalVariablesScript', array(&$vars)); |
| 220 | + foreach( $vars as $name => $value ) { |
| 221 | + $script .= "\t\tvar $name = " . json_encode($value) . ";\n"; |
| 222 | + } |
| 223 | + return true; |
| 224 | + } |
| 225 | + } |
| 226 | + return false; |
| 227 | + } |
| 228 | + |
| 229 | + /** |
| 230 | + * Installs a parser hook for every tag reported by FBConnectXFBML::availableTags(). |
| 231 | + * Accomplishes this by asking FBConnectXFBML to create a hook function that then |
| 232 | + * redirects to FBConnectXFBML::parserHook(). |
| 233 | + */ |
| 234 | + public static function ParserFirstCallInit( &$parser ) { |
| 235 | + $pHooks = FBConnectXFBML::availableTags(); |
| 236 | + foreach( $pHooks as $tag ) { |
| 237 | + $parser->setHook( $tag, FBConnectXFBML::createParserHook( $tag )); |
| 238 | + } |
| 239 | + return true; |
| 240 | + } |
| 241 | + |
| 242 | + /** |
| 243 | + * Modify the user's persinal toolbar (in the upper right). |
| 244 | + * |
| 245 | + * TODO: Better 'returnto' code |
| 246 | + */ |
| 247 | + public static function PersonalUrls( &$personal_urls, &$wgTitle ) { |
| 248 | + global $facebook, $wgUser, $wgLang, $wgShowIPinHeader; |
| 249 | + global $fbPersonalUrls, $fbConnectOnly; |
| 250 | + wfLoadExtensionMessages('FBConnect'); |
| 251 | + |
| 252 | + /* |
| 253 | + * Personal URLs option: remove_user_talk_link |
| 254 | + */ |
| 255 | + if ($fbPersonalUrls['remove_user_talk_link'] && |
| 256 | + array_key_exists('mytalk', $personal_urls)) { |
| 257 | + unset($personal_urls['mytalk']); |
| 258 | + } |
| 259 | + |
| 260 | + // If the user is logged in and connected |
| 261 | + if ( $wgUser->isLoggedIn() && $facebook->getSession() ) { |
| 262 | + /* |
| 263 | + * Personal URLs option: use_real_name_from_fb |
| 264 | + */ |
| 265 | + if ( !empty( $fbPersonalUrls['use_real_name_from_fb'] ) ) { |
| 266 | + // Start with the real name in the database |
| 267 | + $name = $wgUser->getRealName(); |
| 268 | + // Ask Facebook for the real name |
| 269 | + if (!$name || $name == '') { |
| 270 | + try { |
| 271 | + // This might fail if we load a stale session from cookies |
| 272 | + $fbUser = $facebook->api('/me'); |
| 273 | + $name = $fbUser['name']; |
| 274 | + } catch (FacebookApiException $e) { |
| 275 | + error_log($e); |
| 276 | + } |
| 277 | + } |
| 278 | + // Make sure we were able to get a name from the database or Facebook |
| 279 | + if ($name && $name != '') { |
| 280 | + $personal_urls['userpage']['text'] = $name; |
| 281 | + } |
| 282 | + } |
| 283 | + // Replace logout link with a button to disconnect from Facebook Connect |
| 284 | + if( empty( $fbPersonalUrls['hide_logout_of_fb'] ) ){ |
| 285 | + unset( $personal_urls['logout'] ); |
| 286 | + $personal_urls['fblogout'] = array( |
| 287 | + 'text' => wfMsg( 'fbconnect-logout' ), |
| 288 | + 'href' => '#', |
| 289 | + 'active' => false ); |
| 290 | + } |
| 291 | + |
| 292 | + /* |
| 293 | + * Personal URLs option: link_back_to_facebook |
| 294 | + */ |
| 295 | + if ($fbPersonalUrls['link_back_to_facebook']) { |
| 296 | + try { |
| 297 | + $fbUser = $facebook->api('/me'); |
| 298 | + $link = $fbUser['link']; |
| 299 | + } catch (FacebookApiException $e) { |
| 300 | + $link = 'http://www.facebook.com/profile.php?id=' . |
| 301 | + $facebook->getUser(); |
| 302 | + } |
| 303 | + $personal_urls['fblink'] = array( |
| 304 | + 'text' => wfMsg( 'fbconnect-link' ), |
| 305 | + 'href' => $link, |
| 306 | + 'active' => false |
| 307 | + ); |
| 308 | + } |
| 309 | + } |
| 310 | + // User is logged in but not Connected |
| 311 | + else if ($wgUser->isLoggedIn()) { |
| 312 | + /* |
| 313 | + * Personal URLs option: hide_convert_button |
| 314 | + */ |
| 315 | + if (!$fbPersonalUrls['hide_convert_button']) { |
| 316 | + $personal_urls['fbconvert'] = array( |
| 317 | + 'text' => wfMsg( 'fbconnect-convert' ), |
| 318 | + 'href' => SpecialConnect::getTitleFor('Connect', 'Convert')->getLocalURL( |
| 319 | + 'returnto=' . $wgTitle->getPrefixedURL()), |
| 320 | + 'active' => $wgTitle->isSpecial( 'Connect' ) |
| 321 | + ); |
| 322 | + } |
| 323 | + } |
| 324 | + // User is not logged in |
| 325 | + else { |
| 326 | + /* |
| 327 | + * Personal URLs option: hide_connect_button |
| 328 | + */ |
| 329 | + if (!$fbPersonalUrls['hide_connect_button']) { |
| 330 | + // Add an option to connect via Facebook Connect |
| 331 | + $personal_urls['fbconnect'] = array( |
| 332 | + 'text' => wfMsg( 'fbconnect-connect' ), |
| 333 | + 'href' => SpecialPage::getTitleFor( 'Connect' )-> |
| 334 | + getLocalUrl( 'returnto=' . $wgTitle->getPrefixedURL() ), |
| 335 | + 'active' => $wgTitle->isSpecial('Connect') |
| 336 | + ); |
| 337 | + } |
| 338 | + |
| 339 | + // Remove other personal toolbar links |
| 340 | + if ( !empty( $fbConnectOnly ) ) { |
| 341 | + foreach (array('login', 'anonlogin') as $k) { |
| 342 | + if (array_key_exists($k, $personal_urls)) { |
| 343 | + unset($personal_urls[$k]); |
| 344 | + } |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + return true; |
| 349 | + } |
| 350 | + |
| 351 | + /** |
| 352 | + * Modify the preferences form. At the moment, we simply turn the user name |
| 353 | + * into a link to the user's facebook profile. |
| 354 | + * |
| 355 | + * TODO! |
| 356 | + */ |
| 357 | + public static function RenderPreferencesForm( $form, $output ) { |
| 358 | + global $facebook, $wgUser; |
| 359 | + |
| 360 | + // This hook no longer seems to work... |
| 361 | + return true; |
| 362 | + |
| 363 | + if( $facebook->getSession() ) { |
| 364 | + $html = $output->getHTML(); |
| 365 | + $name = $wgUser->getName(); |
| 366 | + $i = strpos( $html, $name ); |
| 367 | + if ($i !== FALSE) { |
| 368 | + // If the user has a valid Facebook ID, link to the Facebook profile |
| 369 | + try { |
| 370 | + $fbUser = $facebook->api('/me'); |
| 371 | + // Replace the old output with the new output |
| 372 | + $html = substr( $html, 0, $i ) . |
| 373 | + preg_replace("/$name/", "$name (<a href=\"$fbUser[link]\" " . |
| 374 | + "class='mw-userlink mw-fbconnectuser'>" . |
| 375 | + wfMsg('fbconnect-link-to-profile') . "</a>", |
| 376 | + substr( $html, $i ), 1); |
| 377 | + $output->clearHTML(); |
| 378 | + $output->addHTML( $html ); |
| 379 | + } catch (FacebookApiException $e) { |
| 380 | + error_log($e); |
| 381 | + } |
| 382 | + } |
| 383 | + } |
| 384 | + return true; |
| 385 | + } |
| 386 | + |
| 387 | + /** |
| 388 | + * Adds the class "mw-userlink" to links belonging to Connect accounts on |
| 389 | + * the page Special:ListUsers. |
| 390 | + */ |
| 391 | + static function SpecialListusersFormatRow( &$item, $row ) { |
| 392 | + global $fbSpecialUsers; |
| 393 | + |
| 394 | + // Only modify Facebook Connect users |
| 395 | + if (!$fbSpecialUsers || |
| 396 | + !count(FBConnectDB::getFacebookIDs(User::newFromName($row->user_name)))) { |
| 397 | + return true; |
| 398 | + } |
| 399 | + |
| 400 | + // Look to see if class="..." appears in the link |
| 401 | + $regs = array(); |
| 402 | + preg_match( '/^([^>]*?)class=(["\'])([^"]*)\2(.*)/', $item, $regs ); |
| 403 | + if (count( $regs )) { |
| 404 | + // If so, append " mw-userlink" to the end of the class list |
| 405 | + $item = $regs[1] . "class=$regs[2]$regs[3] mw-userlink$regs[2]" . $regs[4]; |
| 406 | + } else { |
| 407 | + // Otherwise, stick class="mw-userlink" into the link just before the '>' |
| 408 | + preg_match( '/^([^>]*)(.*)/', $item, $regs ); |
| 409 | + $item = $regs[1] . ' class="mw-userlink"' . $regs[2]; |
| 410 | + } |
| 411 | + return true; |
| 412 | + } |
| 413 | + |
| 414 | + /** |
| 415 | + * Adds some info about the governing Facebook group to the header form of |
| 416 | + * Special:ListUsers. |
| 417 | + */ |
| 418 | + static function SpecialListusersHeaderForm( &$pager, &$out ) { |
| 419 | + global $fbUserRightsFromGroup, $facebook, $fbLogo; |
| 420 | + if ( empty($fbUserRightsFromGroup) ) { |
| 421 | + return true; |
| 422 | + } |
| 423 | + |
| 424 | + // TODO: Do we need to verify the Facebook session here? |
| 425 | + |
| 426 | + $gid = $fbUserRightsFromGroup; |
| 427 | + // Connect to the API and get some info about the group |
| 428 | + try { |
| 429 | + $group = $facebook->api('/' . $gid); |
| 430 | + } catch (FacebookApiException $e) { |
| 431 | + error_log($e); |
| 432 | + } |
| 433 | + $out .= ' |
| 434 | + <table style="border-collapse: collapse;"> |
| 435 | + <tr> |
| 436 | + <td> |
| 437 | + ' . wfMsgWikiHtml( 'fbconnect-listusers-header', |
| 438 | + wfMsg( 'group-bureaucrat-member' ), wfMsg( 'group-sysop-member' ), |
| 439 | + "<a href=\"http://www.facebook.com/group.php?gid=$gid\">$group[name]</a>", |
| 440 | + "<a href=\"http://www.facebook.com/profile.php?id={$group[owner][id]}\" " . |
| 441 | + "class=\"mw-userlink\">{$group[owner][name]}</a>") . " |
| 442 | + </td> |
| 443 | + <td> |
| 444 | + <img src=\"https://graph.facebook.com/$gid/picture?type=large\" title=\"$group[name]\" alt=\"$group[name]\"> |
| 445 | + </td> |
| 446 | + </tr> |
| 447 | + </table>"; |
| 448 | + return true; |
| 449 | + } |
| 450 | + |
| 451 | + /** |
| 452 | + * Removes Special:UserLogin and Special:CreateAccount from the list of |
| 453 | + * special pages if $fbConnectOnly is set to true. |
| 454 | + */ |
| 455 | + static function SpecialPage_initList( &$aSpecialPages ) { |
| 456 | + global $fbConnectOnly; |
| 457 | + if ( !empty( $fbConnectOnly) ) { |
| 458 | + $aSpecialPages['Userlogin'] = array('SpecialRedirectToSpecial', 'UserLogin', |
| 459 | + 'Connect', false, array('returnto', 'returntoquery')); |
| 460 | + // Used in 1.12.x and above |
| 461 | + $aSpecialPages['CreateAccount'] = array('SpecialRedirectToSpecial', |
| 462 | + 'CreateAccount', 'Connect'); |
| 463 | + } |
| 464 | + return true; |
| 465 | + } |
| 466 | + |
| 467 | + /** |
| 468 | + * HACK: Please someone fix me or explain why this is necessary! |
| 469 | + * |
| 470 | + * Unstub $wgUser to avoid race conditions and stop returning stupid false |
| 471 | + * negatives! |
| 472 | + * |
| 473 | + * This might be due to a bug in User::getRights() [called from |
| 474 | + * User::isAllowed('read'), called from Title::userCanRead()], where mRights |
| 475 | + * is retrieved from an uninitialized user. From my probing, it seems that |
| 476 | + * the user is uninitialized with almost all members blank except for mFrom, |
| 477 | + * equal to 'session'. The second time around, $user seems to point to the |
| 478 | + * User object after being loaded from the session. After the user is loaded |
| 479 | + * it has all the appropriate groups. However, before being loaded it seems |
| 480 | + * that instead of being null, mRights is equal to the array |
| 481 | + * (createaccount, createpage, createtalk, writeapi). |
| 482 | + */ |
| 483 | + static function userCan (&$title, &$user, $action, &$result) { |
| 484 | + // Unstub $wgUser (is there a more succinct way to do this?) |
| 485 | + $user->getId(); |
| 486 | + return true; |
| 487 | + } |
| 488 | + |
| 489 | + /** |
| 490 | + * Removes the 'createaccount' right from users if $fbConnectOnly is true. |
| 491 | + */ |
| 492 | + static function UserGetRights( &$user, &$aRights ) { |
| 493 | + global $fbConnectOnly; |
| 494 | + if ( !empty( $fbConnectOnly ) ) { |
| 495 | + foreach ( $aRights as $i => $right ) { |
| 496 | + if ( $right == 'createaccount' ) { |
| 497 | + unset( $aRights[$i] ); |
| 498 | + break; |
| 499 | + } |
| 500 | + } |
| 501 | + } |
| 502 | + return true; |
| 503 | + } |
| 504 | + |
| 505 | + /** |
| 506 | + * If the user isn't logged in, try to auto-authenticate via Facebook |
| 507 | + * Connect. The Single Sign On magic of FBConnect happens in this function. |
| 508 | + */ |
| 509 | + static function UserLoadFromSession( $user, &$result ) { |
| 510 | + global $facebook, $wgCookiePrefix, $wgTitle, $wgOut, $wgUser; |
| 511 | + |
| 512 | + // Check to see if the user can be logged in from Facebook |
| 513 | + $fbId = $facebook->getSession() ? $facebook->getUser() : 0; |
| 514 | + // Check to see if the user can be loaded from the session |
| 515 | + $localId = isset($_COOKIE["{$wgCookiePrefix}UserID"]) ? |
| 516 | + intval($_COOKIE["{$wgCookiePrefix}UserID"]) : |
| 517 | + (isset($_SESSION['wsUserID']) ? $_SESSION['wsUserID'] : 0); |
| 518 | + |
| 519 | + // Case: Not logged into Facebook, but logged into the wiki |
| 520 | + if (!$fbId && $localId) { |
| 521 | + $mwUser = User::newFromId($localId); |
| 522 | + // If the user was Connected, the JS should have logged them out... |
| 523 | + // TODO: test to see if they logged in normally (with a password) |
| 524 | + #if (FBConnectDB::userLoggedInWithPassword($mwUser)) return true; |
| 525 | + if (count(FBConnectDB::getFacebookIDs($mwUser))) { |
| 526 | + // Oh well, they shouldn't be here anyways; silently log them out |
| 527 | + $mwUser->logout(); |
| 528 | + // Defaults have just been loaded, so save some time |
| 529 | + $result = false; |
| 530 | + } |
| 531 | + } |
| 532 | + // Case: Logged into Facebook, not logged into the wiki |
| 533 | + else if ($fbId && !$localId) { |
| 534 | + // Look up the MW ID of the Facebook user |
| 535 | + $mwUser = FBConnectDB::getUser($fbId); |
| 536 | + $id = $mwUser ? $mwUser->getId() : 0; |
| 537 | + // If the user doesn't exist, ask them to name their new account |
| 538 | + if (!$id) { |
| 539 | + // TODO: $wgTitle was empty for some strange reason... |
| 540 | + if (!empty( $wgTitle )) { |
| 541 | + $returnto = $wgTitle->isSpecial('Userlogout') || $wgTitle->isSpecial('Connect') ? |
| 542 | + '' : 'returnto=' . $wgTitle->getPrefixedURL(); |
| 543 | + } else { |
| 544 | + $returnto = ''; |
| 545 | + } |
| 546 | + // Don't redirect if we're on certain special pages |
| 547 | + if ($returnto != '') { |
| 548 | + // Redirect to Special:Connect so the Facebook user can choose a nickname |
| 549 | + $wgOut->redirect($wgUser->getSkin()->makeSpecialUrl('Connect', $returnto)); |
| 550 | + } |
| 551 | + } else { |
| 552 | + // TODO: To complete the SSO experience, this should log the user on |
| 553 | + /* |
| 554 | + // Load the user from their ID |
| 555 | + $user->mId = $id; |
| 556 | + $user->mFrom = 'id'; |
| 557 | + $user->load(); |
| 558 | + // Update user's info from Facebook |
| 559 | + $fbUser = new FBConnectUser($mwUser); |
| 560 | + $fbUser->updateFromFacebook(); |
| 561 | + // Authentification okay, no need to continue with User::loadFromSession() |
| 562 | + $result = true; |
| 563 | + /**/ |
| 564 | + } |
| 565 | + } |
| 566 | + // Case: Not logged into Facebook or the wiki |
| 567 | + // Case: Logged into Facebook, logged into the wiki |
| 568 | + return true; |
| 569 | + } |
| 570 | + |
| 571 | + /** |
| 572 | + * |
| 573 | + * |
| 574 | + */ |
| 575 | + static function initPreferencesExtensionForm($user, &$wgExtensionPreferences) { |
| 576 | + $id = FBConnectDB::getFacebookIDs($user); |
| 577 | + if( count($id) > 0 ) { |
| 578 | + //action="/index.php?title=TechTeam_QA_8_Wiki&action=submit" method="post" |
| 579 | + $action = Title::makeTitle(NS_SPECIAL,"Connect"); |
| 580 | + $action = $action->getFullURL("action=disconnect"); |
| 581 | + $html = Xml::openElement("div"); |
| 582 | + $html .= Xml::openElement( "form", array("submit" => "post", "action" => $action) ); |
| 583 | + $html .= Xml::element( "input", array("type" => "submit", "value" => "Disconent"), '', true ); |
| 584 | + $html .= Xml::closeElement( "form" ); |
| 585 | + $html .= Xml::closeElement( "div" ); |
| 586 | + $wgExtensionPreferences = array_merge( |
| 587 | + array( |
| 588 | + array( |
| 589 | + 'name' => 'ssasasas', |
| 590 | + 'section' => 'fbconnect-prefstext', |
| 591 | + 'html' => $html, |
| 592 | + 'type' => PREF_CUSTOM_HTML_T) ), $wgExtensionPreferences); |
| 593 | + } |
| 594 | + |
| 595 | + return true; |
| 596 | + } |
| 597 | +} |
Index: trunk/extensions/FBConnect/FBConnectDB.php |
— | — | @@ -0,0 +1,162 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Class FBConnectDB |
| 30 | + * |
| 31 | + * This class abstracts the manipulation of the custom table used by this |
| 32 | + * extension. If $wgDBprefix is set, this class will pull from the translated |
| 33 | + * tables. If the table 'users_fbconnect' does not exist in your database |
| 34 | + * you will receive errors like this: |
| 35 | + * |
| 36 | + * Database error from within function "FBConnectDB::getUser". Database |
| 37 | + * returned error "Table 'user_fbconnect' doesn't exist". |
| 38 | + * |
| 39 | + * In this case, you will need to fix this by running the MW updater: |
| 40 | + * >php maintenance/update.php |
| 41 | + */ |
| 42 | +class FBConnectDB { |
| 43 | + /** |
| 44 | + * Find the Facebook IDs of the given user, if any, using the database connection provided. |
| 45 | + * |
| 46 | + */ |
| 47 | + public static function getFacebookIDs( $user ) { |
| 48 | + $dbr = wfGetDB( DB_SLAVE, array(), self::sharedDB() ); |
| 49 | + $fbid = array(); |
| 50 | + if ( $user instanceof User && $user->getId() != 0 ) { |
| 51 | + $prefix = self::getPrefix(); |
| 52 | + $res = $dbr->select( |
| 53 | + array( "{$prefix}user_fbconnect" ), |
| 54 | + array( 'user_fbid' ), |
| 55 | + array( 'user_id' => $user->getId() ), |
| 56 | + __METHOD__ |
| 57 | + ); |
| 58 | + foreach( $res as $row ) { |
| 59 | + $fbid[] = $row->user_fbid; |
| 60 | + } |
| 61 | + $res->free(); |
| 62 | + } |
| 63 | + return $fbid; |
| 64 | + } |
| 65 | + |
| 66 | + /** |
| 67 | + * Find the user by their Facebook ID. |
| 68 | + * If there is no user found for the given id, returns null. |
| 69 | + */ |
| 70 | + public static function getUser( $fbid ) { |
| 71 | + $prefix = self::getPrefix(); |
| 72 | + // NOTE: Do not just pass this dbr into getUserByDB since that function prevents |
| 73 | + // rewriting of the database name for shared tables. |
| 74 | + $dbr = & wfGetDB( DB_SLAVE, array(), self::sharedDB() ); |
| 75 | + $id = $dbr->selectField( |
| 76 | + array( "{$prefix}user_fbconnect" ), |
| 77 | + array( 'user_id' ), |
| 78 | + array( 'user_fbid' => $fbid ), |
| 79 | + __METHOD__ |
| 80 | + ); |
| 81 | + |
| 82 | + if ( $id ) { |
| 83 | + return User::newFromId( $id ); |
| 84 | + } else { |
| 85 | + return null; |
| 86 | + } |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Add a User <-> Facebook ID association to the database. |
| 91 | + */ |
| 92 | + public static function addFacebookID( $user, $fbid ) { |
| 93 | + wfProfileIn(__METHOD__); |
| 94 | + |
| 95 | + $prefix = self::getPrefix(); |
| 96 | + $dbw = wfGetDB( DB_MASTER, array(), self::sharedDB() ); |
| 97 | + $dbw->insert( |
| 98 | + "{$prefix}user_fbconnect", |
| 99 | + array( |
| 100 | + 'user_id' => $user->getId(), |
| 101 | + 'user_fbid' => $fbid |
| 102 | + ), |
| 103 | + __METHOD__ |
| 104 | + ); |
| 105 | + wfProfileOut(__METHOD__); |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * Remove a User <-> Facebook ID association from the database. |
| 110 | + */ |
| 111 | + public static function removeFacebookID( $user, $fbid ) { |
| 112 | + $prefix = self::getPrefix(); |
| 113 | + $dbw = wfGetDB( DB_MASTER, array(), self::sharedDB() ); |
| 114 | + $dbw->delete( |
| 115 | + "{$prefix}user_fbconnect", |
| 116 | + array( |
| 117 | + 'user_id' => $user->getId(), |
| 118 | + 'user_fbid' => $fbid |
| 119 | + ), |
| 120 | + __METHOD__ |
| 121 | + ); |
| 122 | + return (bool) $dbw->affectedRows(); |
| 123 | + } |
| 124 | + |
| 125 | + /** |
| 126 | + * Estimates the total number of User <-> Facebook ID associations in the |
| 127 | + * database. If there are no users, then the estimate will probably be 1. |
| 128 | + */ |
| 129 | + public static function countUsers() { |
| 130 | + $prefix = self::getPrefix(); |
| 131 | + $dbr = wfGetDB( DB_SLAVE, array(), self::sharedDB() ); |
| 132 | + // An estimate is good enough for choosing a unique nickname |
| 133 | + $count = $dbr->estimateRowCount("{$prefix}user_fbconnect"); |
| 134 | + // Avoid returning 0 or -1 |
| 135 | + return $count >= 1 ? $count : 1; |
| 136 | + } |
| 137 | + |
| 138 | + /** |
| 139 | + * Returns the name of the shared database, if one is in use for the Facebook |
| 140 | + * Connect users table. Note that 'user_fbconnect' (without respecting |
| 141 | + * $wgSharedPrefix) is added to $wgSharedTables in FBConnect::init by default. |
| 142 | + * This function can also be used as a test for whether a shared database for |
| 143 | + * Facebook Connect users is in use. |
| 144 | + * |
| 145 | + * See also: <http://www.mediawiki.org/wiki/Manual:Shared_database> |
| 146 | + */ |
| 147 | + private static function sharedDB() { |
| 148 | + global $wgExternalSharedDB; |
| 149 | + if (!empty($wgExternalSharedDB)) { |
| 150 | + return $wgExternalSharedDB; |
| 151 | + } |
| 152 | + return false; |
| 153 | + } |
| 154 | + |
| 155 | + /** |
| 156 | + * Returns the table prefix name, either $wgDBprefix, $wgSharedPrefix |
| 157 | + * depending on whether a shared database is in use. |
| 158 | + */ |
| 159 | + private static function getPrefix() { |
| 160 | + global $wgDBprefix, $wgSharedPrefix; |
| 161 | + return self::sharedDB() ? $wgSharedPrefix : $wgDBprefix; |
| 162 | + } |
| 163 | +} |
Index: trunk/extensions/FBConnect/fbconnect.min.js |
— | — | @@ -0,0 +1,2 @@ |
| 2 | +window.fbAsyncInit=function(){FB.init({appId:window.fbAppId,session:window.fbSession,status:true,cookie:true,xfbml:window.fbUseMarkup});FB.Event.subscribe("auth.logout",function(){if(confirm("Not logged in.\n\nYou have been loggout out of Facebook. Press OK to log in via Facebook Connect again, or press Cancel to stay on the current page."))window.location=window.wgArticlePath.replace(/\$1/,"Special:Connect")})}; |
| 3 | +$(document).ready(function(){$("#pt-fbconnect,#pt-fblink,#pt-fbconvert").addClass("mw-fblink");$("#pt-fblogout").click(function(){confirm("You are logging out of both this site and Facebook.")&&FB.logout(function(){window.location=window.fbLogoutURL})})});function sendToConnectOnLogin(){window.location.href=wgServer+wgScript+"?title=Special:Connect&returnto="+wgPageName+"&returntoquery="+wgPagequery}; |
\ No newline at end of file |
Index: trunk/extensions/FBConnect/pushEvents/FBPush_OnWatchArticle.i18n.php |
— | — | @@ -0,0 +1,5 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +$messages['en'] = array( |
| 5 | + 'tog-fbconnect-push-allow-OnWatchArticle' => "Post to my Facebook News Feed when I follow (watchlist) an article.", |
| 6 | +); |
Index: trunk/extensions/FBConnect/pushEvents/FBPush_OnAddImage.i18n.php |
— | — | @@ -0,0 +1,5 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +$messages['en'] = array( |
| 5 | + 'tog-fbconnect-push-allow-OnAddImage' => "Post to my Facebook News Feed when I add an image.", |
| 6 | +); |
Index: trunk/extensions/FBConnect/pushEvents/FBPush_OnWatchArticle.php |
— | — | @@ -0,0 +1,35 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @author Sean Colombo |
| 5 | + * |
| 6 | + * Pushes an item to Facebook News Feed when the user adds an article to their watchlist. |
| 7 | + */ |
| 8 | + |
| 9 | +global $wgExtensionMessagesFiles; |
| 10 | +$pushDir = dirname(__FILE__) . '/'; |
| 11 | +$wgExtensionMessagesFiles['FBPush_OnWatchArticle'] = $pushDir . "FBPush_OnWatchArticle.i18n.php"; |
| 12 | + |
| 13 | +class FBPush_OnWatchArticle extends FBConnectPushEvent { |
| 14 | + protected $isAllowedUserPreferenceName = 'fbconnect-push-allow-OnWatchArticle'; // must correspond to an i18n message that is 'tog-[the value of the string on this line]'. |
| 15 | + |
| 16 | + public function init(){ |
| 17 | + global $wgHooks; |
| 18 | + wfProfileIn(__METHOD__); |
| 19 | + |
| 20 | + $wgHooks['ArticleSaveComplete'][] = 'FBPush_OnWatchArticle::articleCountPagesAddedInLastHour'; |
| 21 | + wfLoadExtensionMessages('FBPush_OnWatchArticle'); |
| 22 | + |
| 23 | + wfProfileOut(__METHOD__); |
| 24 | + } |
| 25 | + |
| 26 | + |
| 27 | + public static function articleCountPagesAddedInLastHour(&$article, &$user, $text, $summary,$flag, $fake1, $fake2, &$flags, $revision, &$status, $baseRevId){ |
| 28 | + global $wgContentNamespaces; |
| 29 | + wfProfileIn(__METHOD__); |
| 30 | + if( in_array($article->getTitle()->getNamespace(), $wgContentNamespaces) ) { |
| 31 | + self::pushEvent($article->getTitle()->getText(), $article->getTitle()->getFullURL(), "Read more"); |
| 32 | + } |
| 33 | + wfProfileOut(__METHOD__); |
| 34 | + return true; |
| 35 | + } |
| 36 | +} |
Index: trunk/extensions/FBConnect/pushEvents/FBPush_OnAddImage.php |
— | — | @@ -0,0 +1,22 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @author Sean Colombo |
| 5 | + * |
| 6 | + * Pushes an item to Facebook News Feed when the user adds an Image to the site. |
| 7 | + */ |
| 8 | + |
| 9 | +global $wgExtensionMessagesFiles; |
| 10 | +$pushDir = dirname(__FILE__) . '/'; |
| 11 | +$wgExtensionMessagesFiles['FBPush_OnAddImage'] = $pushDir . "FBPush_OnAddImage.i18n.php"; |
| 12 | + |
| 13 | +class FBPush_OnAddImage extends FBConnectPushEvent { |
| 14 | + protected $isAllowedUserPreferenceName = 'fbconnect-push-allow-OnAddImage'; // must correspond to an i18n message that is 'tog-[the value of the string on this line]'. |
| 15 | + |
| 16 | + public function init(){ |
| 17 | + wfProfileIn(__METHOD__); |
| 18 | + |
| 19 | + wfLoadExtensionMessages('FBPush_OnAddImage'); |
| 20 | + |
| 21 | + wfProfileOut(__METHOD__); |
| 22 | + } |
| 23 | +} |
Index: trunk/extensions/FBConnect/pushEvents/FBPush_OnLargeEdit.i18n.php |
— | — | @@ -0,0 +1,5 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +$messages['en'] = array( |
| 5 | + 'tog-fbconnect-push-allow-OnLargeEdit' => "Post to my Facebook News Feed when I make an edit over ".FBPush_OnLargeEdit::getMinCharsToPush()." characters.", |
| 6 | +); |
Index: trunk/extensions/FBConnect/pushEvents/FBPush_OnLargeEdit.php |
— | — | @@ -0,0 +1,27 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @author Sean Colombo |
| 5 | + * |
| 6 | + * Pushes an item to Facebook News Feed in the event of a large edit. |
| 7 | + */ |
| 8 | + |
| 9 | +global $wgExtensionMessagesFiles; |
| 10 | +$pushDir = dirname(__FILE__) . '/'; |
| 11 | +$wgExtensionMessagesFiles['FBPush_OnLargeEdit'] = $pushDir . "FBPush_OnLargeEdit.i18n.php"; |
| 12 | + |
| 13 | +class FBPush_OnLargeEdit extends FBConnectPushEvent { |
| 14 | + protected $isAllowedUserPreferenceName = 'fbconnect-push-allow-OnLargeEdit'; // must correspond to an i18n message that is 'tog-[the value of the string on this line]'. |
| 15 | + static private $MIN_CHARS_TO_PUSH = 300; // number of chars that need to be changed |
| 16 | + |
| 17 | + static public function getMinCharsToPush(){ |
| 18 | + return self::$MIN_CHARS_TO_PUSH; |
| 19 | + } |
| 20 | + |
| 21 | + public function init(){ |
| 22 | + wfProfileIn(__METHOD__); |
| 23 | + |
| 24 | + wfLoadExtensionMessages('FBPush_OnLargeEdit'); |
| 25 | + |
| 26 | + wfProfileOut(__METHOD__); |
| 27 | + } |
| 28 | +} |
Index: trunk/extensions/FBConnect/FBConnect.i18n.php |
— | — | @@ -0,0 +1,176 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * Copyright � 2008-2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 5 | + * This program is free software; you can redistribute it and/or modify |
| 6 | + * it under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation; either version 2 of the License, or |
| 8 | + * (at your option) any later version. |
| 9 | + * |
| 10 | + * This program is distributed in the hope that it will be useful, |
| 11 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | + * GNU General Public License for more details. |
| 14 | + * |
| 15 | + * You should have received a copy of the GNU General Public License along |
| 16 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 17 | + */ |
| 18 | + |
| 19 | + |
| 20 | +/* |
| 21 | + * Not a valid entry point, skip unless MEDIAWIKI is defined. |
| 22 | + */ |
| 23 | +if ( !defined( 'MEDIAWIKI' ) ) { |
| 24 | + die( 'This file is a MediaWiki extension, it is not a valid entry point' ); |
| 25 | +} |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * FBConnect.i18n.php |
| 30 | + * |
| 31 | + * Internationalization file for FBConnect. |
| 32 | + */ |
| 33 | + |
| 34 | + |
| 35 | +$messages = array(); |
| 36 | + |
| 37 | +// Shorthand to make my life more sane |
| 38 | +if (!defined( 'fb' )) { |
| 39 | + define( 'fb', 'fbconnect-' ); |
| 40 | +} |
| 41 | + |
| 42 | +/** English */ |
| 43 | +$messages['en'] = array( |
| 44 | +// Extension name |
| 45 | +'fbconnect' => 'Facebook Connect', |
| 46 | +fb.'desc' => 'Enables users to [[Special:Connect|Connect]] with their [http://www.facebook.com Facebook] ' . |
| 47 | + 'accounts. Offers authentification based on Facebook groups and the use of FBML in wiki text.', |
| 48 | +// Group containing Facebook Connect users |
| 49 | +'group-fb-user' => 'Facebook Connect users', |
| 50 | +'group-fb-user-member' => 'Facebook Connect user', |
| 51 | +'grouppage-fb-user' => '{{ns:project}}:Facebook Connect users', |
| 52 | +// Group for Facebook Connect users beloning to the group specified by $fbUserRightsFromGroup |
| 53 | +'group-fb-groupie' => 'Group members', |
| 54 | +'group-fb-groupie-member' => 'Group member', |
| 55 | +'grouppage-fb-groupie' => '{{ns:project}}:Group members', |
| 56 | +// Officers of the Facebook group |
| 57 | +'group-fb-officer' => 'Group officers', |
| 58 | +'group-fb-officer-member' => 'Group officer', |
| 59 | +'grouppage-fb-officer' => '{{ns:project}}:Group officers', |
| 60 | +// Admins of the Facebook group |
| 61 | +'group-fb-admin' => 'Group admins', |
| 62 | +'group-fb-admin-member' => 'Group administrator', |
| 63 | +'grouppage-fb-admin' => '{{ns:project}}:Group admins', |
| 64 | +// Personal toolbar |
| 65 | +fb.'connect' => 'Log in with Facebook Connect', |
| 66 | +fb.'convert' => 'Connect this account with Facebook', |
| 67 | +fb.'logout' => 'Logout of Facebook', |
| 68 | +fb.'link' => 'Back to facebook.com', |
| 69 | + |
| 70 | +// Special:Connect |
| 71 | +fb.'title' => 'Connect account with Facebook', |
| 72 | +fb.'intro' => 'This wiki is enabled with Facebook Connect, the next evolution of Facebook Platform. This means ' . |
| 73 | + 'that when you are Connected, in addition to the normal [[Wikipedia:Help:Logging in#Why log in?' . |
| 74 | + '|benefits]] you see when logging in, you will be able to take advantage of some extra features...', |
| 75 | +fb.'click-to-login' => 'Click this button to login to this site via facebook', |
| 76 | +fb.'click-to-connect-existing' => 'Click this button to connect your facebook account to $1', |
| 77 | +fb.'conv' => 'Convenience', |
| 78 | +fb.'convdesc' => 'Connected users are automatically logged you in. If permission is given, then this wiki can even ' . |
| 79 | + 'use Facebook as an email proxy so you can continue to receive important notifications without ' . |
| 80 | + 'revealing your email address.', |
| 81 | +fb.'fbml' => 'Facebook Markup Language', |
| 82 | +fb.'fbmldesc' => 'Facebook has provided a bunch of built-in tags that will render dynamic data. Many of these tags ' . |
| 83 | + 'can be included in wiki text, and will be rendered differently depending on which Connected user ' . |
| 84 | + 'they are being viewed by.', |
| 85 | +fb.'comm' => 'Communication', |
| 86 | +fb.'commdesc' => 'Facebook Connect ushers in a whole new level of networking. See which of your friends are using ' . |
| 87 | + 'the wiki, and optionally share your actions with your friends through the Facebook News Feed.', |
| 88 | +fb.'welcome' => 'Welcome, Facebook Connect user!', |
| 89 | +fb.'loginbox' => "Or '''login''' with Facebook:\n\n$1", |
| 90 | +fb.'merge' => 'Merge your wiki account with your Facebook ID', |
| 91 | +fb.'mergebox' => 'This feature has not yet been implemented. Accounts can be merged manually with [[Special:' . |
| 92 | + 'Renameuser]] if it is installed. For more information, please visit [[MediaWikiWiki:Extension:' . |
| 93 | + "Renameuser|Extension:Renameuser]].\n\n\n$1\n\n\nNote: This can be undone by a sysop.", |
| 94 | +fb.'logoutbox'=> "$1\n\nThis will also log you out of Facebook and all Connected sites, including this wiki.", |
| 95 | +fb.'listusers-header' |
| 96 | + => '$1 and $2 privileges are automatically transfered from the Officer and Admin titles of the ' . |
| 97 | + "Facebook group $3.\n\nFor more info, please contact the group creator $4.", |
| 98 | +// Prefix to use for automatically-generated usernames |
| 99 | +fb.'usernameprefix' => 'FacebookUser', |
| 100 | +// Special:Connect |
| 101 | +fb.'error' => 'Verification error', |
| 102 | +fb.'errortext' => 'An error occured during verification with Facebook Connect.', |
| 103 | +fb.'cancel' => 'Action cancelled', |
| 104 | +fb.'canceltext' => 'The previous action was cancelled by the user.', |
| 105 | +fb.'invalid' => 'Invalid option', |
| 106 | +fb.'invalidtext' => 'The selection made on the previous page was invalid.', |
| 107 | +fb.'success' => 'Facebook verification succeeded', |
| 108 | +fb.'successtext' => 'You have been successfully logged in with Facebook Connect.', |
| 109 | +#fb.'optional' => 'Optional', |
| 110 | +#fb.'required' => 'Required', |
| 111 | +fb.'nickname' => 'Nickname', |
| 112 | +fb.'fullname' => 'Fullname', |
| 113 | +fb.'email' => 'E-mail address', |
| 114 | +fb.'language' => 'Language', |
| 115 | +fb.'timecorrection' => 'Time zone correction (hours)', |
| 116 | +fb.'chooselegend' => 'Username choice', |
| 117 | +fb.'chooseinstructions' => 'All users need a nickname; you can choose one from the options below.', |
| 118 | +fb.'invalidname' => 'The nickname you chose is already taken or not a valid nickname. Please chose a different one.', |
| 119 | +fb.'choosenick' => 'Your Facebook profile name ($1)', |
| 120 | +fb.'choosefirst' => 'Your first name ($1)', |
| 121 | +fb.'choosefull' => 'Your full name ($1)', |
| 122 | +fb.'chooseauto' => 'An auto-generated name ($1)', |
| 123 | +fb.'choosemanual' => 'A name of your choice:', |
| 124 | +fb.'chooseexisting' => 'An existing account on this wiki', |
| 125 | +fb.'chooseusername' => 'Username:', |
| 126 | +fb.'choosepassword' => 'Password:', |
| 127 | +fb.'updateuserinfo' => 'Update the following personal information:', |
| 128 | +fb.'alreadyloggedin' => "'''You are already logged in, $1!'''\n\nIf you want to use Facebook " . |
| 129 | + 'Connect to log in in the future, you can [[Special:Connect/Convert|' . |
| 130 | + 'convert your account to use Facebook Connect]].', |
| 131 | +/* |
| 132 | +fb.'convertinstructions' => 'This form lets you change your user account to use an OpenID URL or add more OpenID URLs', |
| 133 | +fb.'convertoraddmoreids' => 'Convert to OpenID or add another OpenID URL', |
| 134 | +fb.'convertsuccess' => 'Successfully converted to OpenID', |
| 135 | +fb.'convertsuccesstext' => 'You have successfully converted your OpenID to $1.', |
| 136 | +fb.'convertyourstext' => 'That is already your OpenID.', |
| 137 | +fb.'convertothertext' => 'That is someone else\'s OpenID.', |
| 138 | +*/ |
| 139 | + |
| 140 | +fb.'error-creating-user' => "Error creating the user in the local database.", |
| 141 | +fb.'error-user-creation-hook-aborted' => "A hook (extension) aborted the account creation with the message: $1", |
| 142 | + |
| 143 | + |
| 144 | + 'fbconnect-prefstext' => 'Facebook Connect', |
| 145 | + 'fbconnect-link-to-profile' => 'Facebook profile', |
| 146 | + 'fbconnect-prefsheader' => "To control which events will push an item to your Facebook News Feed, |
| 147 | + <a id='fbConnectPushEventBar_show' href='#'>show preferences</a> |
| 148 | + <a id='fbConnectPushEventBar_hide' href='#' style='display:none'>hide preferences</a>", |
| 149 | + 'fbconnect-prefs-can-be-updated' => "You can update these any time by visiting the '$1' tab of your Preferences page.", |
| 150 | +); |
| 151 | + |
| 152 | +/** |
| 153 | + * Message documentation (Message documentation) |
| 154 | + * What is this used for? Answer: This is shown to translators to help them know what the string is for. |
| 155 | + * @author Garrett Brown |
| 156 | + */ |
| 157 | +$messages['qqq'] = array( |
| 158 | + 'fbconnect-desc' => 'Short description of the FBConnect extension, shown in [[Special:Version]]. Do not translate or change links.', |
| 159 | + 'fbconnect-trustinstructions' => '* $1 is a trust root. A trust root looks much like a normal URL, but is used to describe a set of URLs. Trust roots are used by OpenID to verify that a user has approved the OpenID enabled website.', |
| 160 | + 'fbconnect-optional' => '{{Identical|Optional}}', |
| 161 | + 'fbconnect-email' => '{{Identical|E-mail address}}', |
| 162 | + 'fbconnect-language' => '{{Identical|Language}}', |
| 163 | + 'fbconnect-timezone' => '{{Identical|Time zone}}', |
| 164 | + 'fbconnect-choosepassword' => '{{Identical|Password}}', |
| 165 | + 'fbconnect-alreadyloggedin' => '$1 is a user name.', |
| 166 | + 'fbconnect-autosubmit' => '{{doc-important|"Continue" will never be localised. It is hardcoded in a PHP extension. Translations could be made like ""Continue" (translation)"}}', |
| 167 | + 'fbconnect-delete-button' => '{{Identical|Confirm}}', |
| 168 | + 'prefs-fbconnect' => '{{optional}} |
| 169 | +OpenID preferences tab title', |
| 170 | + 'fbconnect-prefstext' => 'FBConnect preferences tab text above the list of preferences', |
| 171 | + 'fbconnect-pref-hide' => 'FBConnect preference label (Hide your FBConnect URL on your user page, if you log in with FBConnect)', |
| 172 | + 'fbconnect-pref-update-userinfo-on-login' => 'FBConnect preference label for updating from Facebook account upon login', |
| 173 | + 'fbconnect-urls-action' => '{{Identical|Action}}', |
| 174 | + 'fbconnect-urls-delete' => '{{identical|Delete}}', |
| 175 | + 'fbconnect-link-to-profile' => 'Appears next to the user\s name in their Preferences page and this text is made into link to the profile of that user if they are connected.', |
| 176 | +); |
| 177 | +/**/ |
Index: trunk/extensions/FBConnect/php-sdk/facebook.php |
— | — | @@ -0,0 +1,775 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +if (!function_exists('curl_init')) { |
| 5 | + throw new Exception('Facebook needs the CURL PHP extension.'); |
| 6 | +} |
| 7 | +if (!function_exists('json_decode')) { |
| 8 | + throw new Exception('Facebook needs the JSON PHP extension.'); |
| 9 | +} |
| 10 | + |
| 11 | +/** |
| 12 | + * Thrown when an API call returns an exception. |
| 13 | + * |
| 14 | + * @author Naitik Shah <naitik@facebook.com> |
| 15 | + */ |
| 16 | +class FacebookApiException extends Exception |
| 17 | +{ |
| 18 | + /** |
| 19 | + * The result from the API server that represents the exception information. |
| 20 | + */ |
| 21 | + protected $result; |
| 22 | + |
| 23 | + /** |
| 24 | + * Make a new API Exception with the given result. |
| 25 | + * |
| 26 | + * @param Array $result the result from the API server |
| 27 | + */ |
| 28 | + public function __construct($result) { |
| 29 | + $this->result = $result; |
| 30 | + |
| 31 | + $code = isset($result['error_code']) ? $result['error_code'] : 0; |
| 32 | + $msg = isset($result['error']) |
| 33 | + ? $result['error']['message'] : $result['error_msg']; |
| 34 | + parent::__construct($msg, $code); |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * Return the associated result object returned by the API server. |
| 39 | + * |
| 40 | + * @returns Array the result from the API server |
| 41 | + */ |
| 42 | + public function getResult() { |
| 43 | + return $this->result; |
| 44 | + } |
| 45 | + |
| 46 | + /** |
| 47 | + * Returns the associated type for the error. This will default to |
| 48 | + * 'Exception' when a type is not available. |
| 49 | + * |
| 50 | + * @return String |
| 51 | + */ |
| 52 | + public function getType() { |
| 53 | + return |
| 54 | + isset($this->result['error']) && isset($this->result['error']['type']) |
| 55 | + ? $this->result['error']['type'] |
| 56 | + : 'Exception'; |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * To make debugging easier. |
| 61 | + * |
| 62 | + * @returns String the string representation of the error |
| 63 | + */ |
| 64 | + public function __toString() { |
| 65 | + $str = $this->getType() . ': '; |
| 66 | + if ($this->code != 0) { |
| 67 | + $str .= $this->code . ': '; |
| 68 | + } |
| 69 | + return $str . $this->message; |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +/** |
| 74 | + * Provides access to the Facebook Platform. |
| 75 | + * |
| 76 | + * @author Naitik Shah <naitik@facebook.com> |
| 77 | + */ |
| 78 | +class Facebook |
| 79 | +{ |
| 80 | + /** |
| 81 | + * Version. |
| 82 | + */ |
| 83 | + const VERSION = '2.0.3'; |
| 84 | + |
| 85 | + /** |
| 86 | + * Default options for curl. |
| 87 | + */ |
| 88 | + public static $CURL_OPTS = array( |
| 89 | + CURLOPT_CONNECTTIMEOUT => 10, |
| 90 | + CURLOPT_RETURNTRANSFER => true, |
| 91 | + CURLOPT_TIMEOUT => 60, |
| 92 | + CURLOPT_USERAGENT => 'facebook-php-2.0', |
| 93 | + ); |
| 94 | + |
| 95 | + /** |
| 96 | + * List of query parameters that get automatically dropped when rebuilding |
| 97 | + * the current URL. |
| 98 | + */ |
| 99 | + protected static $DROP_QUERY_PARAMS = array( |
| 100 | + 'session', |
| 101 | + ); |
| 102 | + |
| 103 | + /** |
| 104 | + * Maps aliases to Facebook domains. |
| 105 | + */ |
| 106 | + public static $DOMAIN_MAP = array( |
| 107 | + 'api' => 'https://api.facebook.com/', |
| 108 | + 'api_read' => 'https://api-read.facebook.com/', |
| 109 | + 'graph' => 'https://graph.facebook.com/', |
| 110 | + 'www' => 'https://www.facebook.com/', |
| 111 | + ); |
| 112 | + |
| 113 | + /** |
| 114 | + * The Application ID. |
| 115 | + */ |
| 116 | + protected $appId; |
| 117 | + |
| 118 | + /** |
| 119 | + * The Application API Secret. |
| 120 | + */ |
| 121 | + protected $apiSecret; |
| 122 | + |
| 123 | + /** |
| 124 | + * The active user session, if one is available. |
| 125 | + */ |
| 126 | + protected $session; |
| 127 | + |
| 128 | + /** |
| 129 | + * Indicates that we already loaded the session as best as we could. |
| 130 | + */ |
| 131 | + protected $sessionLoaded = false; |
| 132 | + |
| 133 | + /** |
| 134 | + * Indicates if Cookie support should be enabled. |
| 135 | + */ |
| 136 | + protected $cookieSupport = false; |
| 137 | + |
| 138 | + /** |
| 139 | + * Base domain for the Cookie. |
| 140 | + */ |
| 141 | + protected $baseDomain = ''; |
| 142 | + |
| 143 | + /** |
| 144 | + * Initialize a Facebook Application. |
| 145 | + * |
| 146 | + * The configuration: |
| 147 | + * - appId: the application ID |
| 148 | + * - secret: the application secret |
| 149 | + * - cookie: (optional) boolean true to enable cookie support |
| 150 | + * - domain: (optional) domain for the cookie |
| 151 | + * |
| 152 | + * @param Array $config the application configuration |
| 153 | + */ |
| 154 | + public function __construct($config) { |
| 155 | + $this->setAppId($config['appId']); |
| 156 | + $this->setApiSecret($config['secret']); |
| 157 | + if (isset($config['cookie'])) { |
| 158 | + $this->setCookieSupport($config['cookie']); |
| 159 | + } |
| 160 | + if (isset($config['domain'])) { |
| 161 | + $this->setBaseDomain($config['domain']); |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * Set the Application ID. |
| 167 | + * |
| 168 | + * @param String $appId the Application ID |
| 169 | + */ |
| 170 | + public function setAppId($appId) { |
| 171 | + $this->appId = $appId; |
| 172 | + return $this; |
| 173 | + } |
| 174 | + |
| 175 | + /** |
| 176 | + * Get the Application ID. |
| 177 | + * |
| 178 | + * @return String the Application ID |
| 179 | + */ |
| 180 | + public function getAppId() { |
| 181 | + return $this->appId; |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * Set the API Secret. |
| 186 | + * |
| 187 | + * @param String $appId the API Secret |
| 188 | + */ |
| 189 | + public function setApiSecret($apiSecret) { |
| 190 | + $this->apiSecret = $apiSecret; |
| 191 | + return $this; |
| 192 | + } |
| 193 | + |
| 194 | + /** |
| 195 | + * Get the API Secret. |
| 196 | + * |
| 197 | + * @return String the API Secret |
| 198 | + */ |
| 199 | + public function getApiSecret() { |
| 200 | + return $this->apiSecret; |
| 201 | + } |
| 202 | + |
| 203 | + /** |
| 204 | + * Set the Cookie Support status. |
| 205 | + * |
| 206 | + * @param Boolean $cookieSupport the Cookie Support status |
| 207 | + */ |
| 208 | + public function setCookieSupport($cookieSupport) { |
| 209 | + $this->cookieSupport = $cookieSupport; |
| 210 | + return $this; |
| 211 | + } |
| 212 | + |
| 213 | + /** |
| 214 | + * Get the Cookie Support status. |
| 215 | + * |
| 216 | + * @return Boolean the Cookie Support status |
| 217 | + */ |
| 218 | + public function useCookieSupport() { |
| 219 | + return $this->cookieSupport; |
| 220 | + } |
| 221 | + |
| 222 | + /** |
| 223 | + * Set the base domain for the Cookie. |
| 224 | + * |
| 225 | + * @param String $domain the base domain |
| 226 | + */ |
| 227 | + public function setBaseDomain($domain) { |
| 228 | + $this->baseDomain = $domain; |
| 229 | + return $this; |
| 230 | + } |
| 231 | + |
| 232 | + /** |
| 233 | + * Get the base domain for the Cookie. |
| 234 | + * |
| 235 | + * @return String the base domain |
| 236 | + */ |
| 237 | + public function getBaseDomain() { |
| 238 | + return $this->baseDomain; |
| 239 | + } |
| 240 | + |
| 241 | + /** |
| 242 | + * Set the Session. |
| 243 | + * |
| 244 | + * @param Array $session the session |
| 245 | + * @param Boolean $write_cookie indicate if a cookie should be written. this |
| 246 | + * value is ignored if cookie support has been disabled. |
| 247 | + */ |
| 248 | + public function setSession($session=null, $write_cookie=true) { |
| 249 | + $session = $this->validateSessionObject($session); |
| 250 | + $this->sessionLoaded = true; |
| 251 | + $this->session = $session; |
| 252 | + if ($write_cookie) { |
| 253 | + $this->setCookieFromSession($session); |
| 254 | + } |
| 255 | + return $this; |
| 256 | + } |
| 257 | + |
| 258 | + /** |
| 259 | + * Get the session object. This will automatically look for a signed session |
| 260 | + * sent via the Cookie or Query Parameters if needed. |
| 261 | + * |
| 262 | + * @return Array the session |
| 263 | + */ |
| 264 | + public function getSession() { |
| 265 | + if (!$this->sessionLoaded) { |
| 266 | + $session = null; |
| 267 | + $write_cookie = true; |
| 268 | + |
| 269 | + // try loading session from $_REQUEST |
| 270 | + if (isset($_REQUEST['session'])) { |
| 271 | + $session = json_decode( |
| 272 | + get_magic_quotes_gpc() |
| 273 | + ? stripslashes($_REQUEST['session']) |
| 274 | + : $_REQUEST['session'], |
| 275 | + true |
| 276 | + ); |
| 277 | + $session = $this->validateSessionObject($session); |
| 278 | + } |
| 279 | + |
| 280 | + // try loading session from cookie if necessary |
| 281 | + if (!$session && $this->useCookieSupport()) { |
| 282 | + $cookieName = $this->getSessionCookieName(); |
| 283 | + if (isset($_COOKIE[$cookieName])) { |
| 284 | + $session = array(); |
| 285 | + parse_str(trim( |
| 286 | + get_magic_quotes_gpc() |
| 287 | + ? stripslashes($_COOKIE[$cookieName]) |
| 288 | + : $_COOKIE[$cookieName], |
| 289 | + '"' |
| 290 | + ), $session); |
| 291 | + $session = $this->validateSessionObject($session); |
| 292 | + // write only if we need to delete a invalid session cookie |
| 293 | + $write_cookie = empty($session); |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + $this->setSession($session, $write_cookie); |
| 298 | + } |
| 299 | + |
| 300 | + return $this->session; |
| 301 | + } |
| 302 | + |
| 303 | + /** |
| 304 | + * Get the UID from the session. |
| 305 | + * |
| 306 | + * @return String the UID if available |
| 307 | + */ |
| 308 | + public function getUser() { |
| 309 | + $session = $this->getSession(); |
| 310 | + return $session ? $session['uid'] : null; |
| 311 | + } |
| 312 | + |
| 313 | + /** |
| 314 | + * Get a Login URL for use with redirects. By default, full page redirect is |
| 315 | + * assumed. If you are using the generated URL with a window.open() call in |
| 316 | + * JavaScript, you can pass in display=popup as part of the $params. |
| 317 | + * |
| 318 | + * The parameters: |
| 319 | + * - next: the url to go to after a successful login |
| 320 | + * - cancel_url: the url to go to after the user cancels |
| 321 | + * - req_perms: comma separated list of requested extended perms |
| 322 | + * - display: can be "page" (default, full page) or "popup" |
| 323 | + * |
| 324 | + * @param Array $params provide custom parameters |
| 325 | + * @return String the URL for the login flow |
| 326 | + */ |
| 327 | + public function getLoginUrl($params=array()) { |
| 328 | + $currentUrl = $this->getCurrentUrl(); |
| 329 | + return $this->getUrl( |
| 330 | + 'www', |
| 331 | + 'login.php', |
| 332 | + array_merge(array( |
| 333 | + 'api_key' => $this->getAppId(), |
| 334 | + 'cancel_url' => $currentUrl, |
| 335 | + 'display' => 'page', |
| 336 | + 'fbconnect' => 1, |
| 337 | + 'next' => $currentUrl, |
| 338 | + 'return_session' => 1, |
| 339 | + 'session_version' => 3, |
| 340 | + 'v' => '1.0', |
| 341 | + ), $params) |
| 342 | + ); |
| 343 | + } |
| 344 | + |
| 345 | + /** |
| 346 | + * Get a Logout URL suitable for use with redirects. |
| 347 | + * |
| 348 | + * The parameters: |
| 349 | + * - next: the url to go to after a successful logout |
| 350 | + * |
| 351 | + * @param Array $params provide custom parameters |
| 352 | + * @return String the URL for the logout flow |
| 353 | + */ |
| 354 | + public function getLogoutUrl($params=array()) { |
| 355 | + $session = $this->getSession(); |
| 356 | + return $this->getUrl( |
| 357 | + 'www', |
| 358 | + 'logout.php', |
| 359 | + array_merge(array( |
| 360 | + 'api_key' => $this->getAppId(), |
| 361 | + 'next' => $this->getCurrentUrl(), |
| 362 | + 'session_key' => $session['session_key'], |
| 363 | + ), $params) |
| 364 | + ); |
| 365 | + } |
| 366 | + |
| 367 | + /** |
| 368 | + * Get a login status URL to fetch the status from facebook. |
| 369 | + * |
| 370 | + * The parameters: |
| 371 | + * - ok_session: the URL to go to if a session is found |
| 372 | + * - no_session: the URL to go to if the user is not connected |
| 373 | + * - no_user: the URL to go to if the user is not signed into facebook |
| 374 | + * |
| 375 | + * @param Array $params provide custom parameters |
| 376 | + * @return String the URL for the logout flow |
| 377 | + */ |
| 378 | + public function getLoginStatusUrl($params=array()) { |
| 379 | + return $this->getUrl( |
| 380 | + 'www', |
| 381 | + 'extern/login_status.php', |
| 382 | + array_merge(array( |
| 383 | + 'api_key' => $this->getAppId(), |
| 384 | + 'no_session' => $this->getCurrentUrl(), |
| 385 | + 'no_user' => $this->getCurrentUrl(), |
| 386 | + 'ok_session' => $this->getCurrentUrl(), |
| 387 | + 'session_version' => 3, |
| 388 | + ), $params) |
| 389 | + ); |
| 390 | + } |
| 391 | + |
| 392 | + /** |
| 393 | + * Make an API call. |
| 394 | + * |
| 395 | + * @param Array $params the API call parameters |
| 396 | + * @return the decoded response |
| 397 | + */ |
| 398 | + public function api(/* polymorphic */) { |
| 399 | + $args = func_get_args(); |
| 400 | + if (is_array($args[0])) { |
| 401 | + return $this->_restserver($args[0]); |
| 402 | + } else { |
| 403 | + return call_user_func_array(array($this, '_graph'), $args); |
| 404 | + } |
| 405 | + } |
| 406 | + |
| 407 | + /** |
| 408 | + * Invoke the old restserver.php endpoint. |
| 409 | + * |
| 410 | + * @param Array $params method call object |
| 411 | + * @return the decoded response object |
| 412 | + * @throws FacebookApiException |
| 413 | + */ |
| 414 | + protected function _restserver($params) { |
| 415 | + // generic application level parameters |
| 416 | + $params['api_key'] = $this->getAppId(); |
| 417 | + $params['format'] = 'json-strings'; |
| 418 | + |
| 419 | + $result = json_decode($this->_oauthRequest( |
| 420 | + $this->getApiUrl($params['method']), |
| 421 | + $params |
| 422 | + ), true); |
| 423 | + |
| 424 | + // results are returned, errors are thrown |
| 425 | + if (is_array($result) && isset($result['error_code'])) { |
| 426 | + throw new FacebookApiException($result); |
| 427 | + } |
| 428 | + return $result; |
| 429 | + } |
| 430 | + |
| 431 | + /** |
| 432 | + * Invoke the Graph API. |
| 433 | + * |
| 434 | + * @param String $path the path (required) |
| 435 | + * @param String $method the http method (default 'GET') |
| 436 | + * @param Array $params the query/post data |
| 437 | + * @return the decoded response object |
| 438 | + * @throws FacebookApiException |
| 439 | + */ |
| 440 | + protected function _graph($path, $method='GET', $params=array()) { |
| 441 | + if (is_array($method) && empty($params)) { |
| 442 | + $params = $method; |
| 443 | + $method = 'GET'; |
| 444 | + } |
| 445 | + $params['method'] = $method; // method override as we always do a POST |
| 446 | + |
| 447 | + $result = json_decode($this->_oauthRequest( |
| 448 | + $this->getUrl('graph', $path), |
| 449 | + $params |
| 450 | + ), true); |
| 451 | + |
| 452 | + // results are returned, errors are thrown |
| 453 | + if (is_array($result) && isset($result['error'])) { |
| 454 | + $e = new FacebookApiException($result); |
| 455 | + if ($e->getType() === 'OAuthException') { |
| 456 | + $this->setSession(null); |
| 457 | + } |
| 458 | + throw $e; |
| 459 | + } |
| 460 | + return $result; |
| 461 | + } |
| 462 | + |
| 463 | + /** |
| 464 | + * Make a OAuth Request |
| 465 | + * |
| 466 | + * @param String $path the path (required) |
| 467 | + * @param Array $params the query/post data |
| 468 | + * @return the decoded response object |
| 469 | + * @throws FacebookApiException |
| 470 | + */ |
| 471 | + protected function _oauthRequest($url, $params) { |
| 472 | + if (!isset($params['access_token'])) { |
| 473 | + $session = $this->getSession(); |
| 474 | + // either user session signed, or app signed |
| 475 | + if ($session) { |
| 476 | + $params['access_token'] = $session['access_token']; |
| 477 | + } else { |
| 478 | + $params['access_token'] = $this->getAppId() .'|'. $this->getApiSecret(); |
| 479 | + } |
| 480 | + } |
| 481 | + |
| 482 | + // json_encode all params values that are not strings |
| 483 | + foreach ($params as $key => $value) { |
| 484 | + if (!is_string($value)) { |
| 485 | + $params[$key] = json_encode($value); |
| 486 | + } |
| 487 | + } |
| 488 | + return $this->makeRequest($url, $params); |
| 489 | + } |
| 490 | + |
| 491 | + /** |
| 492 | + * Makes an HTTP request. This method can be overriden by subclasses if |
| 493 | + * developers want to do fancier things or use something other than curl to |
| 494 | + * make the request. |
| 495 | + * |
| 496 | + * @param String $url the URL to make the request to |
| 497 | + * @param Array $params the parameters to use for the POST body |
| 498 | + * @param CurlHandler $ch optional initialized curl handle |
| 499 | + * @return String the response text |
| 500 | + */ |
| 501 | + protected function makeRequest($url, $params, $ch=null) { |
| 502 | + if (!$ch) { |
| 503 | + $ch = curl_init(); |
| 504 | + } |
| 505 | + |
| 506 | + $opts = self::$CURL_OPTS; |
| 507 | + $opts[CURLOPT_POSTFIELDS] = $params; |
| 508 | + $opts[CURLOPT_URL] = $url; |
| 509 | + curl_setopt_array($ch, $opts); |
| 510 | + $result = curl_exec($ch); |
| 511 | + if ($result === false) { |
| 512 | + $e = new FacebookApiException(array( |
| 513 | + 'error_code' => curl_errno($ch), |
| 514 | + 'error' => array( |
| 515 | + 'message' => curl_error($ch), |
| 516 | + 'type' => 'CurlException', |
| 517 | + ), |
| 518 | + )); |
| 519 | + curl_close($ch); |
| 520 | + throw $e; |
| 521 | + } |
| 522 | + curl_close($ch); |
| 523 | + return $result; |
| 524 | + } |
| 525 | + |
| 526 | + /** |
| 527 | + * The name of the Cookie that contains the session. |
| 528 | + * |
| 529 | + * @return String the cookie name |
| 530 | + */ |
| 531 | + protected function getSessionCookieName() { |
| 532 | + return 'fbs_' . $this->getAppId(); |
| 533 | + } |
| 534 | + |
| 535 | + /** |
| 536 | + * Set a JS Cookie based on the _passed in_ session. It does not use the |
| 537 | + * currently stored session -- you need to explicitly pass it in. |
| 538 | + * |
| 539 | + * @param Array $session the session to use for setting the cookie |
| 540 | + */ |
| 541 | + protected function setCookieFromSession($session=null) { |
| 542 | + if (!$this->useCookieSupport()) { |
| 543 | + return; |
| 544 | + } |
| 545 | + |
| 546 | + $cookieName = $this->getSessionCookieName(); |
| 547 | + $value = 'deleted'; |
| 548 | + $expires = time() - 3600; |
| 549 | + $domain = $this->getBaseDomain(); |
| 550 | + if ($session) { |
| 551 | + $value = '"' . http_build_query($session, null, '&') . '"'; |
| 552 | + if (isset($session['base_domain'])) { |
| 553 | + $domain = $session['base_domain']; |
| 554 | + } |
| 555 | + $expires = $session['expires']; |
| 556 | + } |
| 557 | + |
| 558 | + // prepend dot if a domain is found |
| 559 | + if ($domain) { |
| 560 | + $domain = '.' . $domain; |
| 561 | + } |
| 562 | + |
| 563 | + // if an existing cookie is not set, we dont need to delete it |
| 564 | + if ($value == 'deleted' && empty($_COOKIE[$cookieName])) { |
| 565 | + return; |
| 566 | + } |
| 567 | + |
| 568 | + if (headers_sent()) { |
| 569 | + // disable error log if we are running in a CLI environment |
| 570 | + // @codeCoverageIgnoreStart |
| 571 | + if (php_sapi_name() != 'cli') { |
| 572 | + error_log('Could not set cookie. Headers already sent.'); |
| 573 | + } |
| 574 | + // @codeCoverageIgnoreEnd |
| 575 | + |
| 576 | + // ignore for code coverage as we will never be able to setcookie in a CLI |
| 577 | + // environment |
| 578 | + // @codeCoverageIgnoreStart |
| 579 | + } else { |
| 580 | + setcookie($cookieName, $value, $expires, '/', $domain); |
| 581 | + } |
| 582 | + // @codeCoverageIgnoreEnd |
| 583 | + } |
| 584 | + |
| 585 | + /** |
| 586 | + * Validates a session_version=3 style session object. |
| 587 | + * |
| 588 | + * @param Array $session the session object |
| 589 | + * @return Array the session object if it validates, null otherwise |
| 590 | + */ |
| 591 | + protected function validateSessionObject($session) { |
| 592 | + // make sure some essential fields exist |
| 593 | + if (is_array($session) && |
| 594 | + isset($session['uid']) && |
| 595 | + isset($session['session_key']) && |
| 596 | + isset($session['secret']) && |
| 597 | + isset($session['access_token']) && |
| 598 | + isset($session['sig'])) { |
| 599 | + // validate the signature |
| 600 | + $session_without_sig = $session; |
| 601 | + unset($session_without_sig['sig']); |
| 602 | + $expected_sig = self::generateSignature( |
| 603 | + $session_without_sig, |
| 604 | + $this->getApiSecret() |
| 605 | + ); |
| 606 | + if ($session['sig'] != $expected_sig) { |
| 607 | + // disable error log if we are running in a CLI environment |
| 608 | + // @codeCoverageIgnoreStart |
| 609 | + if (php_sapi_name() != 'cli') { |
| 610 | + error_log('Got invalid session signature in cookie.'); |
| 611 | + } |
| 612 | + // @codeCoverageIgnoreEnd |
| 613 | + $session = null; |
| 614 | + } |
| 615 | + // check expiry time |
| 616 | + } else { |
| 617 | + $session = null; |
| 618 | + } |
| 619 | + return $session; |
| 620 | + } |
| 621 | + |
| 622 | + /** |
| 623 | + * Build the URL for api given parameters. |
| 624 | + * |
| 625 | + * @param $method String the method name. |
| 626 | + * @return String the URL for the given parameters |
| 627 | + */ |
| 628 | + protected function getApiUrl($method) { |
| 629 | + static $READ_ONLY_CALLS = |
| 630 | + array('admin.getallocation' => 1, |
| 631 | + 'admin.getappproperties' => 1, |
| 632 | + 'admin.getbannedusers' => 1, |
| 633 | + 'admin.getlivestreamvialink' => 1, |
| 634 | + 'admin.getmetrics' => 1, |
| 635 | + 'admin.getrestrictioninfo' => 1, |
| 636 | + 'application.getpublicinfo' => 1, |
| 637 | + 'auth.getapppublickey' => 1, |
| 638 | + 'auth.getsession' => 1, |
| 639 | + 'auth.getsignedpublicsessiondata' => 1, |
| 640 | + 'comments.get' => 1, |
| 641 | + 'connect.getunconnectedfriendscount' => 1, |
| 642 | + 'dashboard.getactivity' => 1, |
| 643 | + 'dashboard.getcount' => 1, |
| 644 | + 'dashboard.getglobalnews' => 1, |
| 645 | + 'dashboard.getnews' => 1, |
| 646 | + 'dashboard.multigetcount' => 1, |
| 647 | + 'dashboard.multigetnews' => 1, |
| 648 | + 'data.getcookies' => 1, |
| 649 | + 'events.get' => 1, |
| 650 | + 'events.getmembers' => 1, |
| 651 | + 'fbml.getcustomtags' => 1, |
| 652 | + 'feed.getappfriendstories' => 1, |
| 653 | + 'feed.getregisteredtemplatebundlebyid' => 1, |
| 654 | + 'feed.getregisteredtemplatebundles' => 1, |
| 655 | + 'fql.multiquery' => 1, |
| 656 | + 'fql.query' => 1, |
| 657 | + 'friends.arefriends' => 1, |
| 658 | + 'friends.get' => 1, |
| 659 | + 'friends.getappusers' => 1, |
| 660 | + 'friends.getlists' => 1, |
| 661 | + 'friends.getmutualfriends' => 1, |
| 662 | + 'gifts.get' => 1, |
| 663 | + 'groups.get' => 1, |
| 664 | + 'groups.getmembers' => 1, |
| 665 | + 'intl.gettranslations' => 1, |
| 666 | + 'links.get' => 1, |
| 667 | + 'notes.get' => 1, |
| 668 | + 'notifications.get' => 1, |
| 669 | + 'pages.getinfo' => 1, |
| 670 | + 'pages.isadmin' => 1, |
| 671 | + 'pages.isappadded' => 1, |
| 672 | + 'pages.isfan' => 1, |
| 673 | + 'permissions.checkavailableapiaccess' => 1, |
| 674 | + 'permissions.checkgrantedapiaccess' => 1, |
| 675 | + 'photos.get' => 1, |
| 676 | + 'photos.getalbums' => 1, |
| 677 | + 'photos.gettags' => 1, |
| 678 | + 'profile.getinfo' => 1, |
| 679 | + 'profile.getinfooptions' => 1, |
| 680 | + 'stream.get' => 1, |
| 681 | + 'stream.getcomments' => 1, |
| 682 | + 'stream.getfilters' => 1, |
| 683 | + 'users.getinfo' => 1, |
| 684 | + 'users.getloggedinuser' => 1, |
| 685 | + 'users.getstandardinfo' => 1, |
| 686 | + 'users.hasapppermission' => 1, |
| 687 | + 'users.isappuser' => 1, |
| 688 | + 'users.isverified' => 1, |
| 689 | + 'video.getuploadlimits' => 1); |
| 690 | + $name = 'api'; |
| 691 | + if (isset($READ_ONLY_CALLS[strtolower($method)])) { |
| 692 | + $name = 'api_read'; |
| 693 | + } |
| 694 | + return self::getUrl($name, 'restserver.php'); |
| 695 | + } |
| 696 | + |
| 697 | + /** |
| 698 | + * Build the URL for given domain alias, path and parameters. |
| 699 | + * |
| 700 | + * @param $name String the name of the domain |
| 701 | + * @param $path String optional path (without a leading slash) |
| 702 | + * @param $params Array optional query parameters |
| 703 | + * @return String the URL for the given parameters |
| 704 | + */ |
| 705 | + protected function getUrl($name, $path='', $params=array()) { |
| 706 | + $url = self::$DOMAIN_MAP[$name]; |
| 707 | + if ($path) { |
| 708 | + if ($path[0] === '/') { |
| 709 | + $path = substr($path, 1); |
| 710 | + } |
| 711 | + $url .= $path; |
| 712 | + } |
| 713 | + if ($params) { |
| 714 | + $url .= '?' . http_build_query($params); |
| 715 | + } |
| 716 | + return $url; |
| 717 | + } |
| 718 | + |
| 719 | + /** |
| 720 | + * Returns the Current URL, stripping it of known FB parameters that should |
| 721 | + * not persist. |
| 722 | + * |
| 723 | + * @return String the current URL |
| 724 | + */ |
| 725 | + protected function getCurrentUrl() { |
| 726 | + $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' |
| 727 | + ? 'https://' |
| 728 | + : 'http://'; |
| 729 | + $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; |
| 730 | + $parts = parse_url($currentUrl); |
| 731 | + |
| 732 | + // drop known fb params |
| 733 | + $query = ''; |
| 734 | + if (!empty($parts['query'])) { |
| 735 | + $params = array(); |
| 736 | + parse_str($parts['query'], $params); |
| 737 | + foreach(self::$DROP_QUERY_PARAMS as $key) { |
| 738 | + unset($params[$key]); |
| 739 | + } |
| 740 | + if (!empty($params)) { |
| 741 | + $query = '?' . http_build_query($params); |
| 742 | + } |
| 743 | + } |
| 744 | + |
| 745 | + // use port if non default |
| 746 | + $port = |
| 747 | + isset($parts['port']) && |
| 748 | + (($protocol === 'http://' && $parts['port'] !== 80) || |
| 749 | + ($protocol === 'https://' && $parts['port'] !== 443)) |
| 750 | + ? ':' . $parts['port'] : ''; |
| 751 | + |
| 752 | + // rebuild |
| 753 | + return $protocol . $parts['host'] . $port . $parts['path'] . $query; |
| 754 | + } |
| 755 | + |
| 756 | + /** |
| 757 | + * Generate a signature for the given params and secret. |
| 758 | + * |
| 759 | + * @param Array $params the parameters to sign |
| 760 | + * @param String $secret the secret to sign with |
| 761 | + * @return String the generated signature |
| 762 | + */ |
| 763 | + protected static function generateSignature($params, $secret) { |
| 764 | + // work with sorted data |
| 765 | + ksort($params); |
| 766 | + |
| 767 | + // generate the base string |
| 768 | + $base_string = ''; |
| 769 | + foreach($params as $key => $value) { |
| 770 | + $base_string .= $key . '=' . $value; |
| 771 | + } |
| 772 | + $base_string .= $secret; |
| 773 | + |
| 774 | + return md5($base_string); |
| 775 | + } |
| 776 | +} |
Index: trunk/extensions/FBConnect/fbconnect_table.sql |
— | — | @@ -0,0 +1,10 @@ |
| 2 | +-- |
| 3 | +-- SQL schema for FBConnect extension |
| 4 | +-- |
| 5 | + |
| 6 | +CREATE TABLE /*$wgDBprefix*/user_fbconnect ( |
| 7 | + user_fbid BIGINT unsigned NOT NULL PRIMARY KEY, |
| 8 | + user_id int(10) unsigned NOT NULL |
| 9 | +) /*$wgDBTableOptions*/; |
| 10 | + |
| 11 | +CREATE INDEX /*$wgDBprefix*/user_fbconnect_user ON /*$wgDBprefix*/user_fbconnect(user_id); |
Index: trunk/extensions/FBConnect/fbconnect_table.pg.sql |
— | — | @@ -0,0 +1,9 @@ |
| 2 | + |
| 3 | +-- Schema for the FBConnect extension (Postgres version) |
| 4 | + |
| 5 | +CREATE TABLE user_fbconnect ( |
| 6 | + user_fbid BIGINT NOT NULL PRIMARY KEY, |
| 7 | + user_id INTEGER NOT NULL REFERENCES mwuser(user_id) |
| 8 | +); |
| 9 | + |
| 10 | +CREATE INDEX user_fbconnect_user ON user_fbconnect(user_id); |
Index: trunk/extensions/FBConnect/config.sample.php |
— | — | @@ -0,0 +1,198 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * To install: |
| 5 | + * 1. Copy this file to config.php (remove the .sample part). |
| 6 | + * 2. Follow the instructions below to make the extension work. |
| 7 | + */ |
| 8 | + |
| 9 | + |
| 10 | +### FBCONNECT CONFIGURATION VARIABLES ### |
| 11 | + |
| 12 | +/** |
| 13 | + * To use Facebook Connect you will first need to create a Facebook application: |
| 14 | + * 1. Visit the "Create an Application" setup wizard: |
| 15 | + * http://developers.facebook.com/setup/ |
| 16 | + * 2. Enter a descriptive name for your wiki in the Site Name field. |
| 17 | + * This will be seen by users when they sign up for your site. |
| 18 | + * 3. Enter the Site URL and Locale, then click "Create application". |
| 19 | + * 4. Copy the displayed App ID and Secret into this config file. |
| 20 | + * |
| 21 | + * Optionally, you may customize your application: |
| 22 | + * A. Click "developer dashboard" link on the previous screen or visit: |
| 23 | + * http://www.facebook.com/developers/apps.php |
| 24 | + * B. Select your application and click "Edit Settings". |
| 25 | + * C. Upload icon and logo images. The icon appears in Stream stories. |
| 26 | + * D. In the "Connect" section, set your Connect Logo and base domain |
| 27 | + * (which should also be copied into this config file). |
| 28 | + */ |
| 29 | +$fbAppId = 'YOUR_APP_ID'; # Change this! |
| 30 | +$fbSecret = 'YOUR_SECRET'; # Change this! |
| 31 | +#$fbDomain = 'BASE_DOMAIN'; # Optional |
| 32 | + |
| 33 | +/** |
| 34 | + * Disables the creation of new accounts and prevents old accounts from being |
| 35 | + * used to log in if they aren't associated with a Facebook Connect account. |
| 36 | + */ |
| 37 | +$fbConnectOnly = false; |
| 38 | + |
| 39 | +/** |
| 40 | + * Allow the use of XFBML in wiki text. |
| 41 | + * For more info, see http://wiki.developers.facebook.com/index.php/XFBML. |
| 42 | + */ |
| 43 | +$fbUseMarkup = true; |
| 44 | + |
| 45 | +/** |
| 46 | + * If XFBML is enabled, then <fb:photo> maybe be used as a replacement for |
| 47 | + * $wgAllowExternalImages with the added benefit that all photos are screened |
| 48 | + * against Facebook's Code of Conduct <http://www.facebook.com/codeofconduct.php> |
| 49 | + * and subject to dynamic privacy. To disable just <fb:photo> tags, set this to false. |
| 50 | + * |
| 51 | + * Disabled until the JavaScript SDK supports <fb:photo> tags. |
| 52 | + */ |
| 53 | +#$fbAllowFacebookImages = true; |
| 54 | + |
| 55 | +/** |
| 56 | + * For easier wiki rights management, create a group on Facebook and place the |
| 57 | + * group ID here. Three new implicit groups will be created: |
| 58 | + * |
| 59 | + * fb-groupie A member of the specified group |
| 60 | + * fb-officer A group member with an officer title |
| 61 | + * fb-admin An administrator of the Facebook group |
| 62 | + * |
| 63 | + * By default, they map to User, Bureaucrat and Sysop privileges, respectively. |
| 64 | + * Users will automatically be promoted or demoted when their membership, title |
| 65 | + * or admin status is modified from the group page within Facebook. |
| 66 | + */ |
| 67 | +$fbUserRightsFromGroup = false; # Or a group ID |
| 68 | + |
| 69 | +// Not used (yet...) |
| 70 | +#$fbRestrictToGroup = true; |
| 71 | +#$fbRestrictToNotReplied = false; |
| 72 | + |
| 73 | +/** |
| 74 | + * Options regarding the personal toolbar (in the upper right). |
| 75 | + * |
| 76 | + * == Key == == Effect == |
| 77 | + * hide_connect_button Hides the "Log in with Facebook Connect" button. |
| 78 | + * hide_convert_button Hides "Connect this account with Facebook" for non- |
| 79 | + * Connected users. |
| 80 | + * hide_logout_of_fb Hides the "logout of facebook" button and leaves only |
| 81 | + * the button to log out of the current MediaWiki. |
| 82 | + * link_back_to_facebook Shows a handy "Back to facebook.com" link for Connected |
| 83 | + * users. This helps enforce the idea that this wiki is |
| 84 | + * "in front" of Facebook. |
| 85 | + * remove_user_talk_link Remove link to user's talk page |
| 86 | + * use_real_name_from_fb Show the real name for all Connected users |
| 87 | + * |
| 88 | + * Additionally, use $wgShowIPinHeader to hide the IP and its talk link. |
| 89 | + * For more information, see <http://www.mediawiki.org/wiki/Manual:$wgShowIPinHeader>. |
| 90 | + */ |
| 91 | +$fbPersonalUrls = array( |
| 92 | + 'hide_connect_button' => false, |
| 93 | + 'hide_convert_button' => false, |
| 94 | + 'hide_logout_of_fb' => false, |
| 95 | + 'link_back_to_facebook' => true, |
| 96 | + 'remove_user_talk_link' => false, |
| 97 | + 'use_real_name_from_fb' => false, |
| 98 | +); |
| 99 | +#$wgShowIPinHeader = false; |
| 100 | + |
| 101 | +/** |
| 102 | + * The Facebook icon. You can copy this image to your server if you want, or |
| 103 | + * set to false to disable. |
| 104 | + */ |
| 105 | +$fbLogo = 'http://static.ak.fbcdn.net/images/icons/favicon.gif'; |
| 106 | + |
| 107 | +/** |
| 108 | + * URL of the Facebook Connect JavaScript SDK. Because this library is currently |
| 109 | + * a beta release, changes to the APIs may be made on a regular basis. If you |
| 110 | + * use FBConnect on your production website, you may wish to insulate yourself |
| 111 | + * from these changes by downloading and hosting your own copy of the library. |
| 112 | + * |
| 113 | + * For more info, see <http://developers.facebook.com/docs/reference/javascript/> |
| 114 | + */ |
| 115 | +$fbScript = 'http://connect.facebook.net/en_US/all.js'; |
| 116 | +#$fbScript = 'https://connect.facebook.net/en_US/all.js'; |
| 117 | + |
| 118 | +/** |
| 119 | + * Path to the extension's client-side JavaScript |
| 120 | + */ |
| 121 | +global $wgScriptPath; |
| 122 | +#$fbExtensionScript = "$wgScriptPath/extensions/FBConnect/fbconnect.js"; // for development |
| 123 | +$fbExtensionScript = "$wgScriptPath/extensions/FBConnect/fbconnect.min.js"; |
| 124 | + |
| 125 | +/** |
| 126 | + * Whether to include jQuery. This option is for backwards compatibility and is |
| 127 | + * ignored in version 1.16. Otherwise, if you already have jQuery included on |
| 128 | + * your site, you can safely set this to false. |
| 129 | + */ |
| 130 | +$fbIncludeJquery = true; |
| 131 | + |
| 132 | +/** |
| 133 | + * Optionally override the default javascript handling which occurs when a user logs in. |
| 134 | + * |
| 135 | + * This will generally not be needed unless you are doing heavy customization of this extension. |
| 136 | + * |
| 137 | + * NOTE: This will be put inside of double-quotes, so any single-quotes should be used inside |
| 138 | + * of any JS in this variable. |
| 139 | + */ |
| 140 | +//$fbOnLoginJsOverride = "sendToConnectOnLogin();"; |
| 141 | + |
| 142 | +/** |
| 143 | + * Optionally turn off the inclusion of the PreferencesExtension. Since this |
| 144 | + * is an extension that you may already have installed in your instance of |
| 145 | + * MediaWiki, there is the option to turn off FBConnect's inclusion of it (which |
| 146 | + * will require you to already have PreferencesExtension enabled elsewhere). |
| 147 | + * |
| 148 | + * When running on MediaWiki v1.16 and above, the extension won't be included anyway. |
| 149 | + */ |
| 150 | +$fbIncludePreferencesExtension = true; |
| 151 | + |
| 152 | +/** |
| 153 | + * An array of extended permissions to request from the user while they are |
| 154 | + * signing up. |
| 155 | + * |
| 156 | + * NOTE: If fbEnablePushToFacebook is true, then publish_stream will automatically be |
| 157 | + * added to this array. |
| 158 | + * |
| 159 | + * For more details see: http://developers.facebook.com/docs/authentication/permissions |
| 160 | + */ |
| 161 | +$fbExtendedPermissions = array( |
| 162 | + //'publish_stream', |
| 163 | + //'read_stream', |
| 164 | + //'email', |
| 165 | + //'read_mailbox', |
| 166 | + //'offline_access', |
| 167 | + //'create_event', |
| 168 | + //'rsvp_event', |
| 169 | + //'sms', |
| 170 | + //'xmpp_login', |
| 171 | +); |
| 172 | + |
| 173 | +/** |
| 174 | + * PUSH EVENTS |
| 175 | + * |
| 176 | + * This section allows controlling of whether push events are enabled, and which |
| 177 | + * of the push events to use. |
| 178 | + */ |
| 179 | +$fbEnablePushToFacebook = false; |
| 180 | +if(!empty($fbEnablePushToFacebook)){ |
| 181 | + $fbPushDir = dirname(__FILE__) . '/pushEvents/'; |
| 182 | + |
| 183 | + // Convenience loop for push event classes in the fbPushDir directory |
| 184 | + // whose file-name corresponds to the class-name. To add a push event |
| 185 | + // which does not meet these criteria, just explicitly add it below. |
| 186 | + $pushEventClassNames = array( |
| 187 | + 'FBPush_OnAddImage', |
| 188 | + 'FBPush_OnLargeEdit', |
| 189 | + 'FBPush_OnWatchArticle', |
| 190 | + ); |
| 191 | + foreach($pushEventClassNames as $pClassName){ |
| 192 | + $fbPushEventClasses[] = $pClassName; |
| 193 | + $wgAutoloadClasses[$pClassName] = $fbPushDir . "$pClassName.php"; |
| 194 | + } |
| 195 | + |
| 196 | + // Example of explicitly adding a push event which doesn't meet the criteria above. |
| 197 | + // $fbPushEventClasses[] = 'FBPush_OnEXAMPLE_CLASS'; |
| 198 | + // $wgAutoloadClasses['FBPush_OnEXAMPLE_CLASS'] = $fbPushDir . 'FBPush_OnEXAMPLE_version_1.php'; |
| 199 | +} |
Index: trunk/extensions/FBConnect/fbconnect.js |
— | — | @@ -0,0 +1,102 @@ |
| 2 | +/* |
| 3 | + * Copyright � 2010 Garrett Brown <http://www.mediawiki.org/wiki/User:Gbruin> |
| 4 | + * This program is free software; you can redistribute it and/or modify |
| 5 | + * it under the terms of the GNU General Public License as published by |
| 6 | + * the Free Software Foundation; either version 2 of the License, or |
| 7 | + * (at your option) any later version. |
| 8 | + * |
| 9 | + * This program is distributed in the hope that it will be useful, |
| 10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + * GNU General Public License for more details. |
| 13 | + * |
| 14 | + * You should have received a copy of the GNU General Public License along |
| 15 | + * with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | + */ |
| 17 | + |
| 18 | +/** |
| 19 | + * fbconnect.js and fbconnect-min.js |
| 20 | + * |
| 21 | + * FBConnect relies on several different libraries and frameworks for its |
| 22 | + * JavaScript code. Each framework has its own method to verify that the proper |
| 23 | + * code won't be called before it's ready. (Below, lambda represents a named or |
| 24 | + * anonymous function.) |
| 25 | + * |
| 26 | + * MediaWiki: addOnloadHook(lambda); |
| 27 | + * This function manages an array of window.onLoad event handlers to be |
| 28 | + * called be called by a MediaWiki script when the window is fully loaded. |
| 29 | + * Because the DOM may be ready before the window (due to large images to |
| 30 | + * be downloaded), a faster alternative is JQuery's document-ready function. |
| 31 | + * |
| 32 | + * Facebook JavaScript SDK: window.fbAsyncInit = lambda; |
| 33 | + * This global variable is called when the JavaScript SDK is fully |
| 34 | + * initialized asynchronously to the document's state. This might be long |
| 35 | + * after the document is finished rendering the first time the script is |
| 36 | + * downloaded. Subsequently, it may even be called before the DOM is ready. |
| 37 | + * |
| 38 | + * jQuery: $(document).ready(lambda); |
| 39 | + * Self-explanatory; to be called when the DOM is ready to be manipulated. |
| 40 | + * Typically this should occur sooner than MediaWiki's addOnloadHook |
| 41 | + * function is called. |
| 42 | + */ |
| 43 | + |
| 44 | +/** |
| 45 | + * After the Facebook JavaScript SDK has been asynchronously loaded, |
| 46 | + * it looks for the global fbAsyncInit and executes the function when found. |
| 47 | + */ |
| 48 | +window.fbAsyncInit = function() { |
| 49 | + // Initialize the library with the API key |
| 50 | + FB.init({ |
| 51 | + appId : window.fbAppId, // See $fbAppId in config.php |
| 52 | + session: window.fbSession, // Don't re-fetch the session if PHP provides it |
| 53 | + status : true, // Check login status |
| 54 | + cookie : true, // Enable cookies to allow the server to access the session |
| 55 | + xfbml : window.fbUseMarkup // Whether XFBML should be automatically parsed |
| 56 | + }); |
| 57 | + |
| 58 | + // NOTE: Auth.login doesn't appear to work anymore. |
| 59 | + // The onlogin attribute of the fb:login-buttons is being used instead. |
| 60 | + |
| 61 | + // Register a function for when the user logs out of Facebook |
| 62 | + FB.Event.subscribe('auth.logout', function(response) { |
| 63 | + // TODO: Internationalize |
| 64 | + var login = confirm("Not logged in.\n\nYou have been loggout out of " + |
| 65 | + "Facebook. Press OK to log in via Facebook Connect " + |
| 66 | + "again, or press Cancel to stay on the current page."); |
| 67 | + if (login) { |
| 68 | + window.location = window.wgArticlePath.replace(/\$1/, "Special:Connect"); |
| 69 | + } |
| 70 | + }); |
| 71 | +}; |
| 72 | + |
| 73 | +/** |
| 74 | + * jQuery code to be run when the DOM is ready to be manhandled. |
| 75 | + */ |
| 76 | +$(document).ready(function() { |
| 77 | + // Add a pretty logo to Facebook links |
| 78 | + $('#pt-fbconnect,#pt-fblink,#pt-fbconvert').addClass('mw-fblink'); |
| 79 | + |
| 80 | + // Add the logout behavior to the "Logout of Facebook" button |
| 81 | + $('#pt-fblogout').click(function() { |
| 82 | + // TODO: Where did the fancy DHTML window go? Maybe consider jQuery Alert Dialogs: |
| 83 | + // http://abeautifulsite.net/2008/12/jquery-alert-dialogs/ |
| 84 | + var logout = confirm("You are logging out of both this site and Facebook."); |
| 85 | + if (logout) { |
| 86 | + FB.logout(function(response) { |
| 87 | + window.location = window.fbLogoutURL; |
| 88 | + }); |
| 89 | + } |
| 90 | + }); |
| 91 | +}); |
| 92 | + |
| 93 | +/** |
| 94 | + * An optional handler to use in fbOnLoginJsOverride for when a user logs in |
| 95 | + * via Facebook Connect. This will redirect to Special:Connect with the |
| 96 | + * returnto variables configured properly. |
| 97 | + * |
| 98 | + * TODO: Also set the value for 'returntoquery'!! |
| 99 | + */ |
| 100 | +function sendToConnectOnLogin(){ |
| 101 | + var destUrl = wgServer + wgScript + "?title=Special:Connect&returnto=" + wgPageName + "&returntoquery=" + wgPagequery; |
| 102 | + window.location.href = destUrl; |
| 103 | +} |