3 * Special page for creating/registering new user accounts.
6 class SpecialCreateAccount
extends SpecialPage
{
8 var $mUsername, $mPassword, $mRetype, $mReturnTo, $mPosted;
9 var $mCreateaccountMail, $mRemember, $mEmail, $mDomain, $mLanguage;
12 public $mDomains = array();
14 public $mUseEmail = true; # Can be switched off by AuthPlugins etc
15 public $mUseRealname = true;
16 public $mUseRemember = true;
18 public $mFormHeader = '';
19 public $mFormFields = array(
22 'label-message' => 'yourname',
31 'label-message' => 'yourpassword',
33 'id' => 'wpPassword2',
38 'label-message' => 'yourpasswordagain',
45 'label-message' => 'youremail',
51 'label-message' => 'yourrealname',
55 'help-message' => 'prefs-help-realname',
59 'label-message' => 'remembermypassword',
65 'label-message' => 'yourdomainname',
71 public function __construct(){
72 parent
::__construct( 'CreateAccount', 'createaccount' );
73 $this->mLogin
= new Login();
76 public function execute( $par ){
77 global $wgUser, $wgOut;
82 # Block signup here if in readonly. Keeps user from
83 # going through the process (filling out data, etc)
84 # and being informed later.
86 $wgOut->readOnlyPage();
89 # Bail out straightaway on permissions errors
90 if( !$this->userCanExecute( $wgUser ) ) {
91 $this->displayRestrictionError();
93 } elseif( $wgUser->isBlockedFromCreateAccount() ) {
94 $this->userBlockedMessage();
96 } elseif( count( $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) {
97 $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' );
101 if( $this->mPosted
) {
102 if( $this->mCreateaccountMail
) {
103 return $this->addNewAccountMailPassword();
105 return $this->addNewAccount();
108 $this->showMainForm('');
113 * Load the member variables from the request parameters
115 protected function loadQuery(){
116 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
117 $this->mCreateaccountMail
= $wgRequest->getCheck( 'wpCreateaccountMail' )
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' )
129 $this->mRemember
= $wgRequest->getCheck( 'wpRemember' );
130 $this->mLanguage
= $wgRequest->getText( 'uselang' );
132 if ( $wgRedirectOnLogin ) {
133 $this->mReturnTo
= $wgRedirectOnLogin;
134 $this->mReturnToQuery
= '';
137 if( $wgEnableEmail ) {
138 $this->mEmail
= $wgRequest->getText( 'wpEmail' );
142 if( !in_array( 'realname', $wgHiddenPrefs ) ) {
143 $this->mRealName
= $wgRequest->getText( 'wpRealName' );
145 $this->mRealName
= '';
148 if( !$wgAuth->validDomain( $this->mDomain
) ) {
149 $this->mDomain
= 'invaliddomain';
151 $wgAuth->setDomain( $this->mDomain
);
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
= '';
162 * Add a new account, and mail its password to the user
164 protected function addNewAccountMailPassword() {
167 if( !$this->mEmail
) {
168 $this->showMainForm( wfMsg( 'noemail', htmlspecialchars( $this->mUsername
) ) );
172 if( !$this->addNewaccountInternal() ) {
176 # Wipe the initial password
177 $this->mLogin
->mUser
->setPassword( null );
178 $this->mLogin
->mUser
->saveSettings();
180 # And mail them a temporary one
181 $result = $this->mLogin
->mailPassword( 'createaccount-title', 'createaccount-text' );
183 wfRunHooks( 'AddNewAccount', array( $this->mLogin
->mUser
, true ) );
184 $this->mLogin
->mUser
->addNewUserLogEntry();
186 $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
187 $wgOut->setRobotPolicy( 'noindex,nofollow' );
188 $wgOut->setArticleRelated( false );
190 if( $result != Login
::SUCCESS
) {
191 if( $result == Login
::MAIL_ERROR
){
192 $this->showMainForm( wfMsg( 'mailerror', $this->mLogin
->mMailResult
->getMessage() ) );
194 $this->showMainForm( wfMsg( 'mailerror' ) );
197 $wgOut->addWikiMsg( 'accmailtext', $this->mLogin
->mUser
->getName(), $this->mLogin
->mUser
->getEmail() );
198 $wgOut->returnToMain( false );
203 * Create a new user account from the provided data
205 protected function addNewAccount() {
206 global $wgUser, $wgEmailAuthentication;
208 # Create the account and abort if there's a problem doing so
209 if( !$this->addNewAccountInternal() )
211 $user = $this->mLogin
->mUser
;
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
);
219 # Send out an email authentication message if needed
220 if( $wgEmailAuthentication && User
::isValidEmailAddr( $user->getEmail() ) ) {
222 $error = $user->sendConfirmationMail();
223 if( WikiError
::isError( $error ) ) {
224 $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() );
226 $wgOut->addWikiMsg( 'confirmemail_oncreate' );
230 # Save settings (including confirmation token)
231 $user->saveSettings();
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"
236 if( $wgUser->isAnon() ) {
238 $wgUser->setCookies();
239 wfRunHooks( 'AddNewAccount', array( $wgUser ) );
240 $wgUser->addNewUserLogEntry();
241 if( $this->hasSessionCookie() ) {
242 return $this->successfulCreation();
244 return $this->cookieRedirectCheck();
247 # Confirm that the account was created
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();
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
266 protected function addNewAccountInternal() {
267 global $wgUser, $wgOut;
268 global $wgEnableSorbs, $wgProxyWhitelist;
269 global $wgMemc, $wgAccountCreationThrottle;
270 global $wgAuth, $wgMinimalPasswordLength;
271 global $wgEmailConfirmToEdit;
273 # If the user passes an invalid domain, something is fishy
274 if( !$wgAuth->validDomain( $this->mDomain
) ) {
275 $this->showMainForm( wfMsg( 'wrongpassword' ) );
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
)
290 $this->showMainForm( wfMsg( 'wrongpassword' ) );
295 if( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) &&
296 $wgUser->inSorbsBlacklist( $ip ) )
298 $this->showMainForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' );
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' ) );
310 if( 0 != $user->idForName() ) {
311 $this->showMainForm( wfMsg( 'userexists' ) );
315 if( 0 != strcmp( $this->mPassword
, $this->mRetype
) ) {
316 $this->showMainForm( wfMsg( 'badretype' ) );
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 ) );
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;
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' ) );
340 if( !empty( $this->mEmail
) && !User
::isValidEmailAddr( $this->mEmail
) ) {
341 $this->showMainForm( wfMsg( 'invalidemailaddress' ) );
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
);
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 );
358 if( $wgAccountCreationThrottle && $wgUser->isPingLimitable() ) {
359 $key = wfMemcKey( 'acctcreate', 'ip', $ip );
360 $value = $wgMemc->get( $key );
362 $wgMemc->set( $key, 0, 86400 );
364 if( $value >= $wgAccountCreationThrottle ) {
365 $this->showMainForm( wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $wgAccountCreationThrottle ) );
368 $wgMemc->incr( $key );
371 if( !$wgAuth->addUser( $user, $this->mPassword
, $this->mEmail
, $this->mRealName
) ) {
372 $this->showMainForm( wfMsg( 'externaldberror' ) );
376 $this->mLogin
->mUser
= $user;
377 $this->mLogin
->initUser( false );
382 * Run any hooks registered for logins, then
383 * display a message welcoming the user.
385 protected function successfulCreation(){
386 global $wgUser, $wgOut;
388 # Run any hooks; display injected HTML
390 wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
392 SpecialUserLogin
::displaySuccessfulLogin(
396 $this->mReturnToQuery
);
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...
409 protected function userBlockedMessage() {
410 global $wgOut, $wgUser;
412 $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
413 $wgOut->setRobotPolicy( 'noindex,nofollow' );
414 $wgOut->setArticleRelated( false );
417 $blocker = User
::whoIs( $wgUser->mBlock
->mBy
);
418 $block_reason = $wgUser->mBlock
->mReason
;
420 if( strval( $block_reason ) === '' ) {
421 $block_reason = wfMsgExt( 'blockednoreason' );
423 $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker );
424 $wgOut->returnToMain( false );
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'
433 protected function showMainForm( $msg, $msgtype = 'error' ) {
434 global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
435 global $wgCookiePrefix, $wgLoginLanguageSelector;
436 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
438 # Parse the error message if we got one
440 if( $msgtype == 'error' ){
441 $msg = wfMsg( 'loginerror' ) . ' ' . $msg;
443 $msg = Html
::rawElement(
445 array( 'class' => $msgtype . 'box' ),
452 # Make sure the returnTo strings don't get lost if the
453 # user changes language, etc
455 if ( !empty( $this->mReturnTo
) ) {
456 $linkq['returnto'] = wfUrlencode( $this->mReturnTo
);
457 if ( !empty( $this->mReturnToQuery
) )
458 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery
);
461 # Pass any language selection on to the mode switch link
462 if( $wgLoginLanguageSelector && $this->mLanguage
)
463 $linkq['uselang'] = $this->mLanguage
;
465 $skin = $wgUser->getSkin();
467 SpecialPage
::getTitleFor( 'Userlogin' ),
468 wfMsgHtml( 'gotaccountlink' ),
471 $link = $wgUser->isLoggedIn()
473 : wfMsgWikiHtml( 'gotaccount', $link );
475 # Prepare language selection links as needed
476 $langSelector = $wgLoginLanguageSelector
479 array( 'id' => 'languagelinks' ),
480 SpecialUserLogin
::makeLanguageSelector( $this->getTitle(), $this->mReturnTo
) )
483 # Add a 'send password by email' button if available
485 if( $wgEnableEmail && $wgUser->isLoggedIn() ){
486 $buttons = Html
::element(
490 'name' => 'wpCreateaccountMail',
491 'value' => wfMsg( 'createaccountmail' ),
492 'id' => 'wpCreateaccountMail',
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 ) );
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
;
508 unset( $this->mFormFields
['Domain'] );
511 # Or to switch email on or off
512 if( !$wgEnableEmail ||
!$this->mUseEmail
){
513 unset( $this->mFormFields
['Email'] );
515 if( $wgEmailConfirmToEdit ){
516 $this->mFormFields
['Email']['help-message'] = 'prefs-help-email-required' ;
517 $this->mFormFields
['Email']['required'] = '';
519 $this->mFormFields
['Email']['help-message'] = 'prefs-help-email';
523 # Or to play with realname
524 if( in_array( 'realname', $wgHiddenPrefs ) ||
!$this->mUseRealname
){
525 unset( $this->mFormFields
['Realname'] );
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';
538 $form = new HTMLForm( $this->mFormFields
, '' );
539 $form->setTitle( $this->getTitle() );
540 $form->setSubmitText( wfMsg( 'createaccount' ) );
541 $form->setSubmitId( 'wpCreateaccount' );
542 $form->suppressReset();
546 . Html
::rawElement( 'p', array( 'id' => 'userloginlink' ),
551 . $form->getButtons()
553 . Html
::hidden( 'returnto', $this->mReturnTo
)
554 . Html
::hidden( 'returntoquery', $this->mReturnToQuery
)
557 $wgOut->setPageTitle( wfMsg( 'createaccount' ) );
558 $wgOut->setRobotPolicy( 'noindex,nofollow' );
559 $wgOut->setArticleRelated( false );
560 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
565 array( 'id' => 'loginstart' ),
566 wfMsgExt( 'loginstart', array( 'parseinline' ) )
571 array( 'id' => 'userloginForm' ),
572 $form->wrapForm( $formContents )
576 array( 'id' => 'loginend' ),
577 wfMsgExt( 'loginend', array( 'parseinline' ) )
584 * Check if a session cookie is present.
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.
592 protected function hasSessionCookie() {
593 global $wgDisableCookieCheck, $wgRequest;
594 return $wgDisableCookieCheck ?
true : $wgRequest->checkSessionCookie();
598 * Do a redirect back to the same page, so we can check any
599 * new session cookies.
601 protected function cookieRedirectCheck() {
604 $query = array( 'wpCookieCheck' => '1' );
605 if ( $this->mReturnTo
) $query['returnto'] = $this->mReturnTo
;
606 $check = $this->getTitle()->getFullURL( $query );
608 return $wgOut->redirect( $check );
612 * Check the cookies and show errors if they're not enabled.
613 * @param $type String action being performed
615 protected function onCookieRedirectCheck() {
616 if ( !$this->hasSessionCookie() ) {
617 return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
619 return SpecialUserLogin
::successfulLogin(
622 $this->mReturnToQuery
);
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
634 public function set(){
635 wfDeprecated( __METHOD__
);
637 public function addInputItem(){
638 wfDeprecated( __METHOD__
);