3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2011, StatusNet, Inc.
6 * Register a user by their email address
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 * @category Email registration
25 * @author Evan Prodromou <evan@status.net>
26 * @copyright 2011 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET')) {
32 // This check helps protect against security problems;
33 // your code file can't be executed directly from the web.
40 * There are four cases where we're called:
42 * 1. GET, no arguments. Initial registration; ask for an email address.
43 * 2. POST, email address argument. Initial registration; send an email to confirm.
44 * 3. GET, code argument. Confirming an invitation or a registration; look them up,
45 * create the relevant user if possible, login as that user, and
46 * show a password-entry form.
47 * 4. POST, password argument. After confirmation, set the password for the new
48 * user, and redirect to a registration complete action with some instructions.
52 * @author Evan Prodromou <evan@status.net>
53 * @copyright 2011 StatusNet, Inc.
54 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
55 * @link http://status.net/
57 class EmailregisterAction extends Action
60 const SETPASSWORD = 2;
61 const NEWREGISTER = 3;
62 const CONFIRMINVITE = 4;
63 const CONFIRMREGISTER = 5;
65 const CONFIRMTYPE = 'register';
70 protected $invitation;
71 protected $confirmation;
78 function prepare($argarray)
80 parent::prepare($argarray);
82 if (common_config('site', 'closed')) {
83 // TRANS: Client exception trown when registration by e-mail is not allowed.
84 throw new ClientException(_m('Registration not allowed.'), 403);
87 if ($this->isPost()) {
89 $this->checkSessionToken();
91 $this->email = $this->trimmed('email');
93 if (!empty($this->email)) {
94 if (common_config('site', 'inviteonly')) {
95 // TRANS: Client exception trown when trying to register without an invitation.
96 throw new ClientException(_m('Sorry, only invited people can register.'), 403);
98 $this->email = common_canonical_email($this->email);
99 $this->state = self::NEWEMAIL;
101 $this->state = self::SETPASSWORD;
103 $this->code = $this->trimmed('code');
105 if (empty($this->code)) {
106 // TRANS: Client exception thrown when no confirmation code was provided.
107 throw new ClientException(_m('No confirmation code.'));
110 $this->invitation = Invitation::getKV('code', $this->code);
112 if (!empty($this->invitation)) {
113 if (!empty($this->invitation->registered_user_id)) {
114 // TRANS: Client exception trown when using an invitation multiple times.
115 throw new ClientException(_m('Invitation already used.'), 403);
119 $this->confirmation = Confirm_address::getKV('code', $this->code);
121 if (empty($this->confirmation)) {
122 // TRANS: Client exception thrown when given confirmation code was not issued.
123 throw new ClientException(_m('No such confirmation code.'), 403);
127 $this->nickname = Nickname::normalize($this->trimmed('nickname'));
128 $this->password1 = $this->trimmed('password1');
129 $this->password2 = $this->trimmed('password2');
131 $this->tos = $this->boolean('tos');
134 $this->code = $this->trimmed('code');
136 if (empty($this->code)) {
137 if (common_config('site', 'inviteonly')) {
138 // TRANS: Client exception trown when trying to register without an invitation.
139 throw new ClientException(_m('Sorry, only invited people can register.'), 403);
141 $this->state = self::NEWREGISTER;
143 $this->invitation = Invitation::getKV('code', $this->code);
144 if (!empty($this->invitation)) {
145 if (!empty($this->invitation->registered_user_id)) {
146 // TRANS: Client exception trown when using an invitation multiple times.
147 throw new ClientException(_m('Invitation already used.'), 403);
149 $this->state = self::CONFIRMINVITE;
151 $this->state = self::CONFIRMREGISTER;
152 $this->confirmation = Confirm_address::getKV('code', $this->code);
154 if (empty($this->confirmation)) {
155 // TRANS: Client exception thrown when given confirmation code was not issued.
156 throw new ClientException(_m('No such confirmation code.'), 405);
167 switch ($this->state) {
168 case self::NEWREGISTER:
170 // TRANS: Title for registration page.
171 return _m('TITLE','Register');
173 case self::SETPASSWORD:
174 case self::CONFIRMINVITE:
175 case self::CONFIRMREGISTER:
176 // TRANS: Title for page where to register with a confirmation code.
177 return _m('TITLE','Complete registration');
185 * @param array $argarray is ignored since it's now passed in in prepare()
189 function handle($argarray=null)
191 $cur = common_current_user();
194 common_redirect(common_local_url('all', array('nickname' => $cur->nickname)));
197 switch ($this->state) {
198 case self::NEWREGISTER:
199 $this->showRegistrationForm();
202 $this->registerUser();
204 case self::CONFIRMINVITE:
205 $this->confirmRegistration();
207 case self::CONFIRMREGISTER:
208 $this->confirmRegistration();
210 case self::SETPASSWORD:
211 $this->setPassword();
217 function showRegistrationForm()
219 $this->form = new EmailRegistrationForm($this, $this->email);
223 function registerUser()
226 $confirm = EmailRegistrationPlugin::registerEmail($this->email);
227 } catch (ClientException $ce) {
228 $this->error = $ce->getMessage();
229 $this->showRegistrationForm();
233 EmailRegistrationPlugin::sendConfirmEmail($confirm);
235 // TRANS: Confirmation text after initial registration.
236 // TRANS: %s an e-mail address.
237 $prompt = sprintf(_m('An email was sent to %s to confirm that address. Check your email inbox for instructions.'),
240 $this->complete = $prompt;
245 function confirmRegistration()
247 if (!empty($this->invitation)) {
248 $email = $this->invitation->address;
249 } else if (!empty($this->confirmation)) {
250 $email = $this->confirmation->address;
253 $nickname = $this->nicknameFromEmail($email);
255 $this->form = new ConfirmRegistrationForm($this,
262 function setPassword()
264 if (Event::handle('StartRegistrationTry', array($this))) {
265 if (!empty($this->invitation)) {
266 $email = trim($this->invitation->address);
267 } else if (!empty($this->confirmation)) {
268 $email = trim($this->confirmation->address);
270 // TRANS: Client exception trown when trying to set password with an invalid confirmation code.
271 throw new Exception(_m('No confirmation thing.'));
275 // TRANS: Error text when trying to register without agreeing to the terms.
276 $this->error = _m('You must accept the terms of service and privacy policy to register.');
277 } else if (empty($this->password1)) {
278 // TRANS: Error text when trying to register without a password.
279 $this->error = _m('You must set a password');
280 } else if (strlen($this->password1) < 6) {
281 // TRANS: Error text when trying to register with too short a password.
282 $this->error = _m('Password must be 6 or more characters.');
283 } else if ($this->password1 != $this->password2) {
284 // TRANS: Error text when trying to register without providing the same password twice.
285 $this->error = _m('Passwords do not match.');
288 if (!empty($this->error)) {
289 $this->form = new ConfirmRegistrationForm($this, $this->nickname, $email, $this->code);
295 $fields = array('nickname' => $this->nickname,
297 'password' => $this->password1,
298 'email_confirmed' => true);
300 if (!empty($this->invitation)) {
301 $fields['code'] = $this->invitation->code;
303 $this->user = User::register($fields);
304 } catch (ClientException $e) {
305 $this->error = $e->getMessage();
306 $this->form = new ConfirmRegistrationForm($this, $this->nickname, $email, $this->code);
311 if (empty($this->user)) {
312 // TRANS: Exception trown when using an invitation multiple times.
313 throw new Exception(_m('Failed to register user.'));
316 common_set_user($this->user);
317 // this is a real login
318 common_real_login(true);
320 // Re-init language env in case it changed (not yet, but soon)
321 common_init_language();
323 if (!empty($this->confirmation)) {
324 $this->confirmation->delete();
327 Event::handle('EndRegistrationTry', array($this));
330 if (Event::handle('StartRegisterSuccess', array($this))) {
331 Event::handle('EndRegisterSuccess', array($this));
332 common_redirect(common_local_url('doc', array('title' => 'welcome')), 303);
333 // common_redirect exits, so we can't run the event _after_ it of course.
337 function sendConfirmEmail($confirm)
339 $sitename = common_config('site', 'name');
341 $recipients = array($confirm->address);
343 $headers['From'] = mail_notify_from();
344 $headers['To'] = trim($confirm->address);
345 // TRANS: Subject for confirmation e-mail.
346 // TRANS: %s is the StatusNet sitename.
347 $headers['Subject'] = sprintf(_m('Confirm your registration on %s'), $sitename);
349 $confirmUrl = common_local_url('register', array('code' => $confirm->code));
351 // TRANS: Body for confirmation e-mail.
352 // TRANS: %1$s is the StatusNet sitename, %2$s is the confirmation URL.
353 $body = sprintf(_m('Someone (probably you) has requested an account on %1$s using this email address.'.
355 'To confirm the address, click the following URL or copy it into the address bar of your browser.'.
359 'If it was not you, you can safely ignore this message.'),
363 mail_send($recipients, $headers, $body);
366 function showContent()
368 if ($this->complete) {
369 $this->elementStart('p', 'success');
370 $this->raw($this->complete);
371 $this->elementEnd('p');
374 $this->elementStart('p', 'error');
375 $this->raw($this->error);
376 $this->elementEnd('p');
379 if (!empty($this->form)) {
386 * Return true if read only.
390 * @param array $args other arguments
392 * @return boolean is read only action?
394 function isReadOnly($args)
399 function nicknameFromEmail($email)
401 return EmailRegistrationPlugin::nicknameFromEmail($email);
407 * Shows different login/register actions.
411 function showLocalNav()
413 $nav = new LoginGroupNav($this);