Replace spinner.gif with a new version (ajax-loader.gif resized to 20x20 with convert)
[lhc/web/wiklou.git] / includes / specials / SpecialCreateAccount.php
1 <?php
2 /**
3 * Special page for creating/registering new user accounts.
4 * @ingroup SpecialPage
5 */
6 class SpecialCreateAccount extends SpecialPage {
7
8 var $mUsername, $mPassword, $mRetype, $mReturnTo, $mPosted;
9 var $mCreateaccountMail, $mRemember, $mEmail, $mDomain, $mLanguage;
10 var $mReturnToQuery;
11
12 public $mDomains = array();
13
14 public $mUseEmail = true; # Can be switched off by AuthPlugins etc
15 public $mUseRealname = true;
16 public $mUseRemember = true;
17
18 public $mFormHeader = '';
19 public $mFormFields = array(
20 'Name' => array(
21 'type' => 'text',
22 'label-message' => 'yourname',
23 'id' => 'wpName2',
24 'tabindex' => '1',
25 'size' => '20',
26 'required' => '1',
27 'autofocus' => '',
28 ),
29 'Password' => array(
30 'type' => 'password',
31 'label-message' => 'yourpassword',
32 'size' => '20',
33 'id' => 'wpPassword2',
34 'required' => '',
35 ),
36 'Retype' => array(
37 'type' => 'password',
38 'label-message' => 'yourpasswordagain',
39 'size' => '20',
40 'id' => 'wpRetype',
41 'required' => '',
42 ),
43 'Email' => array(
44 'type' => 'email',
45 'label-message' => 'youremail',
46 'size' => '20',
47 'id' => 'wpEmail',
48 ),
49 'RealName' => array(
50 'type' => 'text',
51 'label-message' => 'yourrealname',
52 'id' => 'wpRealName',
53 'tabindex' => '1',
54 'size' => '20',
55 'help-message' => 'prefs-help-realname',
56 ),
57 'Remember' => array(
58 'type' => 'check',
59 'label-message' => 'remembermypassword',
60 'id' => 'wpRemember',
61 ),
62 'Domain' => array(
63 'type' => 'select',
64 'id' => 'wpDomain',
65 'label-message' => 'yourdomainname',
66 'options' => null,
67 'default' => null,
68 ),
69 );
70
71 public function __construct(){
72 parent::__construct( 'CreateAccount', 'createaccount' );
73 $this->mLogin = new Login();
74 }
75
76 public function execute( $par ){
77 global $wgUser, $wgOut;
78
79 $this->setHeaders();
80 $this->loadQuery();
81
82 # Block signup here if in readonly. Keeps user from
83 # going through the process (filling out data, etc)
84 # and being informed later.
85 if( wfReadOnly() ) {
86 $wgOut->readOnlyPage();
87 return;
88 }
89 # Bail out straightaway on permissions errors
90 if( !$this->userCanExecute( $wgUser ) ) {
91 $this->displayRestrictionError();
92 return;
93 } elseif( $wgUser->isBlockedFromCreateAccount() ) {
94 $this->userBlockedMessage();
95 return;
96 } elseif( count( $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) {
97 $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' );
98 return;
99 }
100
101 if( $this->mPosted ) {
102 if( $this->mCreateaccountMail ) {
103 return $this->addNewAccountMailPassword();
104 } else {
105 return $this->addNewAccount();
106 }
107 } else {
108 $this->showMainForm('');
109 }
110 }
111
112 /**
113 * Load the member variables from the request parameters
114 */
115 protected function loadQuery(){
116 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
117 $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
118 && $wgEnableEmail;
119
120 $this->mUsername = $wgRequest->getText( 'wpName' );
121 $this->mPassword = $wgRequest->getText( 'wpPassword' );
122 $this->mRetype = $wgRequest->getText( 'wpRetype' );
123 $this->mDomain = $wgRequest->getText( 'wpDomain' );
124 $this->mReturnTo = $wgRequest->getVal( 'returnto' );
125 $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
126 $this->mPosted = $wgRequest->wasPosted();
127 $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
128 && $wgEnableEmail;
129 $this->mRemember = $wgRequest->getCheck( 'wpRemember' );
130 $this->mLanguage = $wgRequest->getText( 'uselang' );
131
132 if ( $wgRedirectOnLogin ) {
133 $this->mReturnTo = $wgRedirectOnLogin;
134 $this->mReturnToQuery = '';
135 }
136
137 if( $wgEnableEmail ) {
138 $this->mEmail = $wgRequest->getText( 'wpEmail' );
139 } else {
140 $this->mEmail = '';
141 }
142 if( !in_array( 'realname', $wgHiddenPrefs ) ) {
143 $this->mRealName = $wgRequest->getText( 'wpRealName' );
144 } else {
145 $this->mRealName = '';
146 }
147
148 if( !$wgAuth->validDomain( $this->mDomain ) ) {
149 $this->mDomain = 'invaliddomain';
150 }
151 $wgAuth->setDomain( $this->mDomain );
152
153 # When switching accounts, it sucks to get automatically logged out
154 $returnToTitle = Title::newFromText( $this->mReturnTo );
155 if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
156 $this->mReturnTo = '';
157 $this->mReturnToQuery = '';
158 }
159 }
160
161 /**
162 * Add a new account, and mail its password to the user
163 */
164 protected function addNewAccountMailPassword() {
165 global $wgOut;
166
167 if( !$this->mEmail ) {
168 $this->showMainForm( wfMsg( 'noemail', htmlspecialchars( $this->mUsername ) ) );
169 return;
170 }
171
172 if( !$this->addNewaccountInternal() ) {
173 return;
174 }
175
176 # Wipe the initial password
177 $this->mLogin->mUser->setPassword( null );
178 $this->mLogin->mUser->saveSettings();
179
180 # And mail them a temporary one
181 $result = $this->mLogin->mailPassword( 'createaccount-title', 'createaccount-text' );
182
183 wfRunHooks( 'AddNewAccount', array( $this->mLogin->mUser, true ) );
184 $this->mLogin->mUser->addNewUserLogEntry();
185
186 $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
187 $wgOut->setRobotPolicy( 'noindex,nofollow' );
188 $wgOut->setArticleRelated( false );
189
190 if( $result != Login::SUCCESS ) {
191 if( $result == Login::MAIL_ERROR ){
192 $this->showMainForm( wfMsg( 'mailerror', $this->mLogin->mMailResult->getMessage() ) );
193 } else {
194 $this->showMainForm( wfMsg( 'mailerror' ) );
195 }
196 } else {
197 $wgOut->addWikiMsg( 'accmailtext', $this->mLogin->mUser->getName(), $this->mLogin->mUser->getEmail() );
198 $wgOut->returnToMain( false );
199 }
200 }
201
202 /**
203 * Create a new user account from the provided data
204 */
205 protected function addNewAccount() {
206 global $wgUser, $wgEmailAuthentication;
207
208 # Create the account and abort if there's a problem doing so
209 if( !$this->addNewAccountInternal() )
210 return;
211 $user = $this->mLogin->mUser;
212
213 # If we showed up language selection links, and one was in use, be
214 # smart (and sensible) and save that language as the user's preference
215 global $wgLoginLanguageSelector;
216 if( $wgLoginLanguageSelector && $this->mLanguage )
217 $user->setOption( 'language', $this->mLanguage );
218
219 # Send out an email authentication message if needed
220 if( $wgEmailAuthentication && User::isValidEmailAddr( $user->getEmail() ) ) {
221 global $wgOut;
222 $error = $user->sendConfirmationMail();
223 if( WikiError::isError( $error ) ) {
224 $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() );
225 } else {
226 $wgOut->addWikiMsg( 'confirmemail_oncreate' );
227 }
228 }
229
230 # Save settings (including confirmation token)
231 $user->saveSettings();
232
233 # If not logged in, assume the new account as the current one and set
234 # session cookies then show a "welcome" message or a "need cookies"
235 # message as needed
236 if( $wgUser->isAnon() ) {
237 $wgUser = $user;
238 $wgUser->setCookies();
239 wfRunHooks( 'AddNewAccount', array( $wgUser ) );
240 $wgUser->addNewUserLogEntry();
241 if( $this->hasSessionCookie() ) {
242 return $this->successfulCreation();
243 } else {
244 return $this->cookieRedirectCheck();
245 }
246 } else {
247 # Confirm that the account was created
248 global $wgOut;
249 $self = SpecialPage::getTitleFor( 'Userlogin' );
250 $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
251 $wgOut->setArticleRelated( false );
252 $wgOut->setRobotPolicy( 'noindex,nofollow' );
253 $wgOut->addWikiMsg( 'accountcreatedtext', $user->getName() );
254 $wgOut->returnToMain( false, $self );
255 wfRunHooks( 'AddNewAccount', array( $user ) );
256 $user->addNewUserLogEntry();
257 return true;
258 }
259 }
260
261 /**
262 * Deeper mechanics of initialising a new user and passing it
263 * off to Login::initUser()
264 * return Bool whether the user was successfully created
265 */
266 protected function addNewAccountInternal() {
267 global $wgUser, $wgOut;
268 global $wgEnableSorbs, $wgProxyWhitelist;
269 global $wgMemc, $wgAccountCreationThrottle;
270 global $wgAuth, $wgMinimalPasswordLength;
271 global $wgEmailConfirmToEdit;
272
273 # If the user passes an invalid domain, something is fishy
274 if( !$wgAuth->validDomain( $this->mDomain ) ) {
275 $this->showMainForm( wfMsg( 'wrongpassword' ) );
276 return false;
277 }
278
279 # If we are not allowing users to login locally, we should be checking
280 # to see if the user is actually able to authenticate to the authenti-
281 # cation server before they create an account (otherwise, they can
282 # create a local account and login as any domain user). We only need
283 # to check this for domains that aren't local.
284 if( !in_array( $this->mDomain, array( 'local', '' ) )
285 && !$wgAuth->canCreateAccounts()
286 && ( !$wgAuth->userExists( $this->mUsername )
287 || !$wgAuth->authenticate( $this->mUsername, $this->mPassword )
288 ) )
289 {
290 $this->showMainForm( wfMsg( 'wrongpassword' ) );
291 return false;
292 }
293
294 $ip = wfGetIP();
295 if( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) &&
296 $wgUser->inSorbsBlacklist( $ip ) )
297 {
298 $this->showMainForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' );
299 return false;
300 }
301
302 # Now create a dummy user ($user) and check if it is valid
303 $name = trim( $this->mUsername );
304 $user = User::newFromName( $name, 'creatable' );
305 if( is_null( $user ) ) {
306 $this->showMainForm( wfMsg( 'noname' ) );
307 return false;
308 }
309
310 if( 0 != $user->idForName() ) {
311 $this->showMainForm( wfMsg( 'userexists' ) );
312 return false;
313 }
314
315 if( 0 != strcmp( $this->mPassword, $this->mRetype ) ) {
316 $this->showMainForm( wfMsg( 'badretype' ) );
317 return false;
318 }
319
320 # check for minimal password length
321 $valid = $user->isValidPassword( $this->mPassword );
322 if( $valid !== true ) {
323 if ( !$this->mCreateaccountMail ) {
324 $this->showMainForm( wfMsgExt( $valid, array( 'parsemag' ), $wgMinimalPasswordLength ) );
325 return false;
326 } else {
327 # do not force a password for account creation by email
328 # set invalid password, it will be replaced later by a random generated password
329 $this->mPassword = null;
330 }
331 }
332
333 # if you need a confirmed email address to edit, then obviously you
334 # need an email address.
335 if( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) {
336 $this->showMainForm( wfMsg( 'noemailtitle' ) );
337 return false;
338 }
339
340 if( !empty( $this->mEmail ) && !User::isValidEmailAddr( $this->mEmail ) ) {
341 $this->showMainForm( wfMsg( 'invalidemailaddress' ) );
342 return false;
343 }
344
345 # Set some additional data so the AbortNewAccount hook can be used for
346 # more than just username validation
347 $user->setEmail( $this->mEmail );
348 $user->setRealName( $this->mRealName );
349
350 $abortError = '';
351 if( !wfRunHooks( 'AbortNewAccount', array( $user, &$abortError ) ) ) {
352 # Hook point to add extra creation throttles and blocks
353 wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
354 $this->showMainForm( $abortError );
355 return false;
356 }
357
358 if( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) {
359 $key = wfMemcKey( 'acctcreate', 'ip', $ip );
360 $value = $wgMemc->get( $key );
361 if ( !$value ) {
362 $wgMemc->set( $key, 0, 86400 );
363 }
364 if( $value >= $wgAccountCreationThrottle ) {
365 $this->showMainForm( wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $wgAccountCreationThrottle ) );
366 return false;
367 }
368 $wgMemc->incr( $key );
369 }
370
371 if( !$wgAuth->addUser( $user, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
372 $this->showMainForm( wfMsg( 'externaldberror' ) );
373 return false;
374 }
375
376 $this->mLogin->mUser = $user;
377 $this->mLogin->initUser( false );
378 return true;
379 }
380
381 /**
382 * Run any hooks registered for logins, then
383 * display a message welcoming the user.
384 */
385 protected function successfulCreation(){
386 global $wgUser, $wgOut;
387
388 # Run any hooks; display injected HTML
389 $injected_html = '';
390 wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
391
392 SpecialUserLogin::displaySuccessfulLogin(
393 'welcomecreation',
394 $injected_html,
395 $this->mReturnTo,
396 $this->mReturnToQuery );
397 }
398
399 /**
400 * Display a message indicating that account creation from their IP has
401 * been blocked by a (range)block with 'block account creation' enabled.
402 * It's likely that this feature will be used for blocking large numbers
403 * of innocent people, e.g. range blocks on schools. Don't blame it on
404 * the user. There's a small chance that it really is the user's fault,
405 * i.e. the username is blocked and they haven't bothered to log out
406 * before trying to create an account to evade it, but we'll leave that
407 * to their guilty conscience to figure out...
408 */
409 protected function userBlockedMessage() {
410 global $wgOut, $wgUser;
411
412 $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
413 $wgOut->setRobotPolicy( 'noindex,nofollow' );
414 $wgOut->setArticleRelated( false );
415
416 $ip = wfGetIP();
417 $blocker = User::whoIs( $wgUser->mBlock->mBy );
418 $block_reason = $wgUser->mBlock->mReason;
419
420 if( strval( $block_reason ) === '' ) {
421 $block_reason = wfMsgExt( 'blockednoreason' );
422 }
423 $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker );
424 $wgOut->returnToMain( false );
425 }
426
427 /**
428 * Show the main input form, with an appropriate error message
429 * from a previous iteration, if necessary
430 * @param $msg String HTML of message received previously
431 * @param $msgtype String type of message, usually 'error'
432 */
433 protected function showMainForm( $msg, $msgtype = 'error' ) {
434 global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
435 global $wgCookiePrefix, $wgLoginLanguageSelector;
436 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
437
438 # Parse the error message if we got one
439 if( $msg ){
440 if( $msgtype == 'error' ){
441 $msg = wfMsg( 'loginerror' ) . ' ' . $msg;
442 }
443 $msg = Html::rawElement(
444 'div',
445 array( 'class' => $msgtype . 'box' ),
446 $msg
447 );
448 } else {
449 $msg = '';
450 }
451
452 # Make sure the returnTo strings don't get lost if the
453 # user changes language, etc
454 $linkq = array();
455 if ( !empty( $this->mReturnTo ) ) {
456 $linkq['returnto'] = wfUrlencode( $this->mReturnTo );
457 if ( !empty( $this->mReturnToQuery ) )
458 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery );
459 }
460
461 # Pass any language selection on to the mode switch link
462 if( $wgLoginLanguageSelector && $this->mLanguage )
463 $linkq['uselang'] = $this->mLanguage;
464
465 $skin = $wgUser->getSkin();
466 $link = $skin->link(
467 SpecialPage::getTitleFor( 'Userlogin' ),
468 wfMsgHtml( 'gotaccountlink' ),
469 array(),
470 $linkq );
471 $link = $wgUser->isLoggedIn()
472 ? ''
473 : wfMsgWikiHtml( 'gotaccount', $link );
474
475 # Prepare language selection links as needed
476 $langSelector = $wgLoginLanguageSelector
477 ? Html::rawElement(
478 'div',
479 array( 'id' => 'languagelinks' ),
480 SpecialUserLogin::makeLanguageSelector( $this->getTitle(), $this->mReturnTo ) )
481 : '';
482
483 # Add a 'send password by email' button if available
484 $buttons = '';
485 if( $wgEnableEmail && $wgUser->isLoggedIn() ){
486 $buttons = Html::element(
487 'input',
488 array(
489 'type' => 'submit',
490 'name' => 'wpCreateaccountMail',
491 'value' => wfMsg( 'createaccountmail' ),
492 'id' => 'wpCreateaccountMail',
493 )
494 );
495 }
496
497 # Give authentication and captcha plugins a chance to
498 # modify the form, by hook or by using $wgAuth
499 $wgAuth->modifyUITemplate( $this, 'new' );
500 wfRunHooks( 'UserCreateForm', array( &$this ) );
501
502 # The most likely use of the hook is to enable domains;
503 # check that now, and add fields if necessary
504 if( $this->mDomains ){
505 $this->mFormFields['Domain']['options'] = $this->mDomains;
506 $this->mFormFields['Domain']['default'] = $this->mDomain;
507 } else {
508 unset( $this->mFormFields['Domain'] );
509 }
510
511 # Or to switch email on or off
512 if( !$wgEnableEmail || !$this->mUseEmail ){
513 unset( $this->mFormFields['Email'] );
514 } else {
515 if( $wgEmailConfirmToEdit ){
516 $this->mFormFields['Email']['help-message'] = 'prefs-help-email-required' ;
517 $this->mFormFields['Email']['required'] = '';
518 } else {
519 $this->mFormFields['Email']['help-message'] = 'prefs-help-email';
520 }
521 }
522
523 # Or to play with realname
524 if( in_array( 'realname', $wgHiddenPrefs ) || !$this->mUseRealname ){
525 unset( $this->mFormFields['Realname'] );
526 }
527
528 # Or to tweak the 'remember my password' checkbox
529 if( !($wgCookieExpiration > 0) || !$this->mUseRemember ){
530 # Remove it altogether
531 unset( $this->mFormFields['Remember'] );
532 } elseif( $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ){
533 # Or check it by default
534 # FIXME: this doesn't always work?
535 $this->mFormFields['Remember']['checked'] = '1';
536 }
537
538 $form = new HTMLForm( $this->mFormFields, '' );
539 $form->setTitle( $this->getTitle() );
540 $form->setSubmitText( wfMsg( 'createaccount' ) );
541 $form->setSubmitId( 'wpCreateaccount' );
542 $form->suppressReset();
543 $form->loadData();
544
545 $formContents = ''
546 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
547 $link )
548 . $this->mFormHeader
549 . $langSelector
550 . $form->getBody()
551 . $form->getButtons()
552 . $buttons
553 . Html::hidden( 'returnto', $this->mReturnTo )
554 . Html::hidden( 'returntoquery', $this->mReturnToQuery )
555 ;
556
557 $wgOut->setPageTitle( wfMsg( 'createaccount' ) );
558 $wgOut->setRobotPolicy( 'noindex,nofollow' );
559 $wgOut->setArticleRelated( false );
560 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
561
562 $wgOut->addHTML(
563 Html::rawElement(
564 'div',
565 array( 'id' => 'loginstart' ),
566 wfMsgExt( 'loginstart', array( 'parseinline' ) )
567 ) .
568 $msg .
569 Html::rawElement(
570 'div',
571 array( 'id' => 'userloginForm' ),
572 $form->wrapForm( $formContents )
573 ) .
574 Html::rawElement(
575 'div',
576 array( 'id' => 'loginend' ),
577 wfMsgExt( 'loginend', array( 'parseinline' ) )
578 )
579 );
580
581 }
582
583 /**
584 * Check if a session cookie is present.
585 *
586 * This will not pick up a cookie set during _this_ request, but is meant
587 * to ensure that the client is returning the cookie which was set on a
588 * previous pass through the system.
589 *
590 * @private
591 */
592 protected function hasSessionCookie() {
593 global $wgDisableCookieCheck, $wgRequest;
594 return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
595 }
596
597 /**
598 * Do a redirect back to the same page, so we can check any
599 * new session cookies.
600 */
601 protected function cookieRedirectCheck() {
602 global $wgOut;
603
604 $query = array( 'wpCookieCheck' => '1' );
605 if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
606 $check = $this->getTitle()->getFullURL( $query );
607
608 return $wgOut->redirect( $check );
609 }
610
611 /**
612 * Check the cookies and show errors if they're not enabled.
613 * @param $type String action being performed
614 */
615 protected function onCookieRedirectCheck() {
616 if ( !$this->hasSessionCookie() ) {
617 return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
618 } else {
619 return SpecialUserLogin::successfulLogin(
620 'welcomecreate',
621 $this->mReturnTo,
622 $this->mReturnToQuery );
623 }
624 }
625
626 /**
627 * Since the UserCreateForm hook was changed to pass a SpecialPage
628 * instead of a QuickTemplate derivative, old extensions might
629 * easily try calling these methods expecing them to exist. Tempting
630 * though it is to let them have the fatal error, let's at least
631 * fail gracefully...
632 * @deprecated
633 */
634 public function set(){
635 wfDeprecated( __METHOD__ );
636 }
637 public function addInputItem(){
638 wfDeprecated( __METHOD__ );
639 }
640 }