3 * StatusNet, the distributed open-source microblogging tool
5 * Login or register a local user based on a Facebook user
9 * LICENCE: This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Affero General Public License for more details.
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24 * @author Zach Copley <zach@status.net>
25 * @copyright 2010 StatusNet, Inc.
26 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
27 * @link http://status.net/
30 if (!defined('STATUSNET')) {
34 class FacebookfinishloginAction extends Action
36 private $facebook = null; // Facebook client
37 private $fbuid = null; // Facebook user ID
38 private $fbuser = null; // Facebook user object (JSON)
40 function prepare($args) {
42 parent::prepare($args);
44 $this->facebook = new Facebook(
46 'appId' => common_config('facebook', 'appid'),
47 'secret' => common_config('facebook', 'secret'),
52 // Check for a Facebook user session
54 $session = $this->facebook->getSession();
59 $this->fbuid = $this->facebook->getUser();
60 $this->fbuser = $this->facebook->api('/me');
61 } catch (FacebookApiException $e) {
62 common_log(LOG_ERROR, $e, __FILE__);
66 if (!empty($this->fbuser)) {
68 // OKAY, all is well... proceed to register
70 common_debug("Found a valid Facebook user.", __FILE__);
73 // This shouldn't happen in the regular course of things
75 list($proxy, $ip) = common_client_ip();
80 'Failed Facebook authentication attempt, proxy = %s, ip = %s.',
88 _m('You must be logged into Facebook to register a local account using Facebook.')
95 function handle($args)
97 parent::handle($args);
99 if (common_is_real_login()) {
101 // User is already logged in, are her accounts already linked?
103 $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
105 if (!empty($flink)) {
107 // User already has a linked Facebook account and shouldn't be here!
111 'There\'s already a local user %d linked with Facebook user %s.',
118 _m('There is already a local account linked with that Facebook account.')
123 // Possibly reconnect an existing account
125 $this->connectUser();
128 } else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
130 $token = $this->trimmed('token');
132 if (!$token || $token != common_session_token()) {
134 _m('There was a problem with your session token. Try again, please.'));
138 if ($this->arg('create')) {
140 if (!$this->boolean('license')) {
142 _m('You can\'t register if you don\'t agree to the license.'),
143 $this->trimmed('newname')
148 // We has a valid Facebook session and the Facebook user has
149 // agreed to the SN license, so create a new user
150 $this->createNewUser();
152 } else if ($this->arg('connect')) {
154 $this->connectNewUser();
159 _m('An unknown error has occured.'),
160 $this->trimmed('newname')
169 function showPageNotice()
173 $this->element('div', array('class' => 'error'), $this->error);
178 'div', 'instructions',
179 // TRANS: %s is the site name.
181 _m('This is the first time you\'ve logged into %s so we must connect your Facebook to a local account. You can either create a new local account, or connect with an existing local account.'),
182 common_config('site', 'name')
190 // TRANS: Page title.
191 return _m('Facebook Setup');
194 function showForm($error=null, $username=null)
196 $this->error = $error;
197 $this->username = $username;
208 * @fixme much of this duplicates core code, which is very fragile.
209 * Should probably be replaced with an extensible mini version of
210 * the core registration form.
212 function showContent()
214 if (!empty($this->message_text)) {
215 $this->element('p', null, $this->message);
219 $this->elementStart('form', array('method' => 'post',
220 'id' => 'form_settings_facebook_connect',
221 'class' => 'form_settings',
222 'action' => common_local_url('facebookfinishlogin')));
223 $this->elementStart('fieldset', array('id' => 'settings_facebook_connect_options'));
225 $this->element('legend', null, _m('Connection options'));
226 $this->elementStart('ul', 'form_data');
227 $this->elementStart('li');
228 $this->element('input', array('type' => 'checkbox',
230 'class' => 'checkbox',
233 $this->elementStart('label', array('class' => 'checkbox', 'for' => 'license'));
234 // TRANS: %s is the name of the license used by the user for their status updates.
235 $message = _m('My text and files are available under %s ' .
236 'except this private data: password, ' .
237 'email address, IM address, and phone number.');
238 $link = '<a href="' .
239 htmlspecialchars(common_config('license', 'url')) .
241 htmlspecialchars(common_config('license', 'title')) .
243 $this->raw(sprintf(htmlspecialchars($message), $link));
244 $this->elementEnd('label');
245 $this->elementEnd('li');
246 $this->elementEnd('ul');
248 $this->elementStart('fieldset');
249 $this->hidden('token', common_session_token());
250 $this->element('legend', null,
252 _m('Create new account'));
253 $this->element('p', null,
254 _m('Create a new user with this nickname.'));
255 $this->elementStart('ul', 'form_data');
256 $this->elementStart('li');
257 // TRANS: Field label.
258 $this->input('newname', _m('New nickname'),
259 ($this->username) ? $this->username : '',
260 _m('1-64 lowercase letters or numbers, no punctuation or spaces'));
261 $this->elementEnd('li');
262 $this->elementEnd('ul');
263 // TRANS: Submit button.
264 $this->submit('create', _m('BUTTON','Create'));
265 $this->elementEnd('fieldset');
267 $this->elementStart('fieldset');
269 $this->element('legend', null,
270 _m('Connect existing account'));
271 $this->element('p', null,
272 _m('If you already have an account, login with your username and password to connect it to your Facebook.'));
273 $this->elementStart('ul', 'form_data');
274 $this->elementStart('li');
275 // TRANS: Field label.
276 $this->input('nickname', _m('Existing nickname'));
277 $this->elementEnd('li');
278 $this->elementStart('li');
279 $this->password('password', _m('Password'));
280 $this->elementEnd('li');
281 $this->elementEnd('ul');
282 // TRANS: Submit button.
283 $this->submit('connect', _m('BUTTON','Connect'));
284 $this->elementEnd('fieldset');
286 $this->elementEnd('fieldset');
287 $this->elementEnd('form');
290 function message($msg)
292 $this->message_text = $msg;
296 function createNewUser()
298 if (common_config('site', 'closed')) {
299 // TRANS: Client error trying to register with registrations not allowed.
300 $this->clientError(_m('Registration not allowed.'));
306 if (common_config('site', 'inviteonly')) {
307 $code = $_SESSION['invitecode'];
309 // TRANS: Client error trying to register with registrations 'invite only'.
310 $this->clientError(_m('Registration not allowed.'));
314 $invite = Invitation::staticGet($code);
316 if (empty($invite)) {
317 // TRANS: Client error trying to register with an invalid invitation code.
318 $this->clientError(_m('Not a valid invitation code.'));
323 $nickname = $this->trimmed('newname');
325 if (!Validate::string($nickname, array('min_length' => 1,
327 'format' => NICKNAME_FMT))) {
328 $this->showForm(_m('Nickname must have only lowercase letters and numbers and no spaces.'));
332 if (!User::allowed_nickname($nickname)) {
333 $this->showForm(_m('Nickname not allowed.'));
337 if (User::staticGet('nickname', $nickname)) {
338 $this->showForm(_m('Nickname already in use. Try another one.'));
343 'nickname' => $nickname,
344 'fullname' => $this->fbuser['first_name']
345 . ' ' . $this->fbuser['last_name'],
346 'email' => $this->fbuser['email'],
347 'email_confirmed' => true,
348 'homepage' => $this->fbuser['website'],
349 'bio' => $this->fbuser['about'],
350 'location' => $this->fbuser['location']['name']
353 if (!empty($invite)) {
354 $args['code'] = $invite->code;
357 $user = User::register($args);
359 $result = $this->flinkUser($user->id, $this->fbuid);
362 $this->serverError(_m('Error connecting user to Facebook.'));
366 $this->setAvatar($user);
368 common_set_user($user);
369 common_real_login(true);
374 'Registered new user %d from Facebook user %s',
384 array('nickname' => $user->nickname)
391 * Attempt to download the user's Facebook picture and create a
392 * StatusNet avatar for the new user.
394 function setAvatar($user)
397 'http://graph.facebook.com/%s/picture?type=large',
401 // fetch the picture from Facebook
402 $client = new HTTPClient();
404 common_debug("status = $status - " . $finalUrl , __FILE__);
406 // fetch the actual picture
407 $response = $client->get($picUrl);
409 if ($response->isOk()) {
411 $finalUrl = $client->getUrl();
412 $filename = 'facebook-' . substr(strrchr($finalUrl, '/'), 1 );
414 common_debug("Filename = " . $filename, __FILE__);
416 $ok = file_put_contents(
417 Avatar::path($filename),
425 'Couldn\'t save Facebook avatar %s',
433 $profile = $user->getProfile();
435 if ($profile->setOriginal($filename)) {
439 'Saved avatar for %s (%d) from Facebook profile %s, filename = %s',
452 function connectNewUser()
454 $nickname = $this->trimmed('nickname');
455 $password = $this->trimmed('password');
457 if (!common_check_user($nickname, $password)) {
458 $this->showForm(_m('Invalid username or password.'));
462 $user = User::staticGet('nickname', $nickname);
465 common_debug('Facebook Connect Plugin - ' .
466 "Legit user to connect to Facebook: $nickname");
469 $result = $this->flinkUser($user->id, $this->fbuid);
472 $this->serverError(_m('Error connecting user to Facebook.'));
476 common_debug('Facebook Connnect Plugin - ' .
477 "Connected Facebook user $this->fbuid to local user $user->id");
479 common_set_user($user);
480 common_real_login(true);
482 $this->goHome($user->nickname);
485 function connectUser()
487 $user = common_current_user();
489 $result = $this->flinkUser($user->id, $this->fbuid);
491 if (empty($result)) {
492 $this->serverError(_m('Error connecting user to Facebook.'));
498 'Connected Facebook user %s to local user %d',
505 common_redirect(common_local_url('facebookfinishlogin'), 303);
512 'Trying login for Facebook user %s',
518 $flink = Foreign_link::getByForeignID($this->fbuid, FACEBOOK_SERVICE);
520 if (!empty($flink)) {
521 $user = $flink->getUser();
528 'Logged in Facebook user %s as user %d (%s)',
536 common_set_user($user);
537 common_real_login(true);
538 $this->goHome($user->nickname);
545 'No flink found for fbuid: %s - new user',
551 $this->showForm(null, $this->bestNewNickname());
555 function goHome($nickname)
557 $url = common_get_returnto();
559 // We don't have to return to it again
560 common_set_returnto(null);
562 $url = common_local_url('all',
567 common_redirect($url, 303);
570 function flinkUser($user_id, $fbuid)
572 $flink = new Foreign_link();
573 $flink->user_id = $user_id;
574 $flink->foreign_id = $fbuid;
575 $flink->service = FACEBOOK_SERVICE;
577 // Pull the access token from the Facebook cookies
578 $flink->credentials = $this->facebook->getAccessToken();
580 $flink->created = common_sql_now();
582 $flink_id = $flink->insert();
587 function bestNewNickname()
589 if (!empty($this->fbuser['name'])) {
590 $nickname = $this->nicknamize($this->fbuser['name']);
591 if ($this->isNewNickname($nickname)) {
598 $fullname = trim($this->fbuser['firstname'] .
599 ' ' . $this->fbuser['lastname']);
601 if (!empty($fullname)) {
602 $fullname = $this->nicknamize($fullname);
603 if ($this->isNewNickname($fullname)) {
612 * Given a string, try to make it work as a nickname
614 function nicknamize($str)
616 $str = preg_replace('/\W/', '', $str);
617 return strtolower($str);
620 function isNewNickname($str)
622 if (!Validate::string($str, array('min_length' => 1,
624 'format' => NICKNAME_FMT))) {
627 if (!User::allowed_nickname($str)) {
630 if (User::staticGet('nickname', $str)) {