/** * Entry point of the special page. * * Special:Connect uses a MVC architecture, with execute() being the * controller. The control flow happens by switching the subpage's name and * then moving through a boatload of nested ifs (notice how early returns * are avoided). Subpages are exclusively used for secondary stages of the * connecting process; that is, they are only invoked from Special:Connect * itself. AJAX functions can also streamline the connecting process by * sending the user directly to one of these subpages. A similar control * structure has been laid out in the login function of ext.facebook.js. * * Now on to the MVC part. * * Function execute() operates on three possible models: the MediaWiki User * class, FacebookUser and FacebookApplication. At the end of a control path, * the process is this: update the model, then call the view (which may depend * on the model, but strictly on a read-only basis). * * Models are only updated from subpages, not Special:Connect. Subpages are * the endpoint of any connecting/disconnecting process, and are only valid * when POSTed to from Special:Connect or AJAX calls acting on behalf of * Special:Connect. The one exception is if the user is logged in to Facebook * and has a MediaWiki account, but isn't logged in to MediaWiki; they will * then be logged in to MediaWiki. Oh, and a second exception is that * Special:Connect/Debug can be navigated to, but it's only for testing purposes. */ public function execute($subPageName) { global $wgUser, $wgRequest; // Setup the session global $wgSessionStarted; if (!$wgSessionStarted) { wfSetupSession(); } $this->setReturnTo(); switch ($subPageName) { /** * Special:Connect/ChooseName is POSTed to after the new Facebook user * has chosen their MediaWiki account options (the wpNameChoice param), * either to connect an existing account (if allowed) or to create a * new account with the specified options. * * TODO: Verify that the request is a POST, not a GET (currently they * both do the same thing, I think). */ case 'ChooseName': if ($wgRequest->getCheck('wpCancel')) { $this->sendError('facebook-cancel', 'facebook-canceltext'); } else { $choice = $wgRequest->getText('wpNameChoice'); try { if ($choice == 'existing') { // Handle accidental reposts if (!$wgUser->isLoggedIn()) { $name = $wgRequest->getText('wpExistingName'); $password = $wgRequest->getText('wpExistingPassword'); // Update the model $fbUser = new FacebookUser(); $fbUser->attachUser($name, $password, $this->getUpdatePrefs()); } // Send the view $this->sendPage('displaySuccessAttachingView'); } else { // Handle accidental reposts if (!$wgUser->isLoggedIn()) { // Update the model (wpDomain isn't currently set...) $fbUser = new FacebookUser(); $fbUser->createUser($this->getUsername($choice), $wgRequest->getText('wpDomain')); } $this->sendPage('loginSuccessView', true); } } catch (FacebookUserException $e) { // HACK: If the title msg is 'connectNewUserView' then we // send the view instead of an error if ($e->getTitleMsg() == 'connectNewUserView') { $this->sendPage('connectNewUserView', $e->getTextMsg()); } else { $this->sendError($e->getTitleMsg(), $e->getTextMsg(), $e->getMsgParams()); } } } break; /** * Special:Connect/LogoutAndContinue does just that -- logs the current * MediaWiki user out and another MediaWiki user in based on the current * Facebook credentials. No parameters, so if a non-Facebook users GETs * this they will be logged out and sent to Special:UserLogin. * * TODO: In the case above, redirect to Special:UserLogout. */ /** * Special:Connect/LogoutAndContinue does just that -- logs the current * MediaWiki user out and another MediaWiki user in based on the current * Facebook credentials. No parameters, so if a non-Facebook users GETs * this they will be logged out and sent to Special:UserLogin. * * TODO: In the case above, redirect to Special:UserLogout. */ case 'LogoutAndContinue': // Update the model (MediaWiki user) $oldName = $wgUser->logout(); $injected_html = ''; // unused wfRunHooks('UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName)); $fbUser = new FacebookUser(); if ($fbUser->getMWUser()->getId()) { // Update the model again (Facebook user) $fbUser->login(); $this->sendPage('loginSuccessView'); } else { $this->sendRedirect('UserLogin'); } break; /** * Special:Connect/MergeAccount takes care of connecting Facebook users * to existing accounts. * * TODO: Verify this is a POST request */ /** * Special:Connect/MergeAccount takes care of connecting Facebook users * to existing accounts. * * TODO: Verify this is a POST request */ case 'MergeAccount': if (!$wgUser->isLoggedIn()) { // Shouldn't be here $this->sendError('facebook-error', 'facebook-errortext'); } else { try { $fbUser = new FacebookUser(); // Handle accidental reposts if ($fbUser->getMWUser()->getId() != $wgUser->getId()) { $fbUser->attachCurrentUser($this->getUpdatePrefs()); } $this->sendPage('displaySuccessAttachingView'); } catch (FacebookUserException $e) { $this->sendError($e->getTitleMsg(), $e->getTextMsg(), $e->getMsgParams()); } } break; /** * Special:Connect/Deauth is a callback used by Facebook to notify the * application that the Facebook user has deauthenticated the app * (removed it from their app settings page). If the request for this * page isn't signed by Facebook, it will redirect to Special:Connect. */ /** * Special:Connect/Deauth is a callback used by Facebook to notify the * application that the Facebook user has deauthenticated the app * (removed it from their app settings page). If the request for this * page isn't signed by Facebook, it will redirect to Special:Connect. */ case 'Deauth': // Facebook will include a signed_request param to verify authenticity global $facebook; $signed_request = $facebook->getSignedRequest(); if ($signed_request) { // Update the model $fbUser = new FacebookUser($signed_request['user_id']); $fbUser->disconnect(); // What view should we show to Facebook? It doesn't really matter... $this->setHeaders(); } else { // signed_request not present or hash mismatch $this->sendRedirect('Connect'); } break; /** * Special:Connect/Debug allows an administrator to verify that both * the Facebook application this extension are setup and working * correctly. This page can only be accessed if $wgFbAllowDebug is * true; see config.default.php for more information. */ /** * Special:Connect/Debug allows an administrator to verify that both * the Facebook application this extension are setup and working * correctly. This page can only be accessed if $wgFbAllowDebug is * true; see config.default.php for more information. */ case 'Debug': global $wgFbAllowDebug; if (!empty($wgFbAllowDebug)) { $app = new FacebookApplication(); if ($app->canEdit()) { $this->sendPage('debugView'); } else { // Something went wrong $fbUser = new FacebookUser(); // First, check MediaWiki permissions. Then check with Facebook if ($fbUser->getMWUser()->getId()) { // If $wgFbUserRightsFromGroups is set, this should trigger a group check $groups = $fbUser->getMWUser()->getEffectiveGroups(); if (in_array('sysop', $groups) || in_array('fb-admin', $groups)) { $this->sendError('facebook-error', 'facebook-error-application'); // roles } else { global $wgLang, $wgFbUserRightsFromGroup; $groupArray = array('sysop'); if (!empty($wgFbUserRightsFromGroup)) { $groupArray[] = 'fb-admin'; } $groupsList = array_map(array('User', 'makeGroupLinkWiki'), $groupArray); $this->sendError('facebook-error', 'badaccess-groups', array($wgLang->commaList($groupsList), count($groupsList))); } } else { // Facebook user is not logged in or is not connected to a MediaWiki user // Show special instructions to MediaWiki administrators $isAdmin = false; if ($wgUser->isLoggedIn()) { $groups = $wgUser->getEffectiveGroups(); if (in_array('sysop', $groups) || in_array('fb-admin', $groups)) { $isAdmin = true; } } if ($isAdmin) { $this->sendError('facebook-error', 'facebook-error-needs-convert'); } else { // Generic validation error $this->sendError('facebook-error', 'facebook-errortext'); } } } break; } // no break /** * Special:Connect was called with no subpage specified. */ // no break /** * Special:Connect was called with no subpage specified. */ default: // If $subPageName was invalid, redirect to Special:Connect with no subpage if (!empty($subPageName)) { $this->sendRedirect('Connect'); break; } $fbUser = new FacebookUser(); // Our Facebook session might be stale. If this becomes a problem, use // $fbUser->isLoggedIn($ping = true) to force a refresh of the state if (!$fbUser->isLoggedIn()) { // The user isn't logged in to Facebook if (!$wgUser->isLoggedIn()) { // The user isn't logged in to Facebook or MediaWiki $this->sendRedirect('UserLogin'); // Nothing to see here, move along } else { // The user is logged in to MediaWiki but not Facebook $this->sendPage('loginToFacebookView'); } } else { // The user is logged in to Facebook $mwId = $fbUser->getMWUser()->getId(); if (!$wgUser->isLoggedIn()) { // The user is logged in to Facebook but not MediaWiki if (!$mwId) { // The Facebook user is new to MediaWiki $this->sendPage('connectNewUserView'); } else { // The user is logged in to Facebook, but not MediaWiki. // The UserLoadAfterLoadFromSession hook might have misfired // if the user's "remember me" option was disabled. $fbUser->login(); $this->sendPage('loginSuccessView'); } } else { // The user is logged in to Facebook and MediaWiki if ($mwId == $wgUser->getId()) { // MediaWiki user belongs to the Facebook account $this->sendRedirect('UserLogin'); // Nothing to see here, move along } else { // Accounts don't agree if (!$mwId) { // Facebook user is new $fb_ids = FacebookDB::getFacebookIDs($wgUser); if (count($fb_ids) == 0) { // MediaWiki user is free // Both accounts are free. Ask to merge $this->sendPage('mergeAccountView'); } else { // MediaWiki user already associated with Facebook ID // TODO: LogoutAndCreateNewUser form global $wgContLang; $param1 = '[[' . $wgContLang->getNsText(NS_USER) . ":{$wgUser->getName()}|{$wgUser->getName()}]]"; $param2 = $fbUser->getUserInfo('name'); $this->sendError('errorpagetitle', 'facebook-error-wrong-id', array('$1' => $param1, '$2' => $param2)); } } else { // Facebook account has a MediaWiki user // Ask to log out and continue as the new user ($mwId) $this->sendPage('logoutAndContinueView', $mwId); } } } } } }