Merge in Login rewrite, second time lucky.
[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 protected $mLogin;
13
14 public $mDomains = array();
15
16 public $mUseEmail = true; # Can be switched off by AuthPlugins etc
17 public $mUseRealname = true;
18 public $mUseRemember = true;
19
20 public $mFormHeader = '';
21 public $mFormFields = array(
22 'Name' => array(
23 'type' => 'text',
24 'label-message' => 'yourname',
25 'id' => 'wpName2',
26 'tabindex' => '1',
27 'size' => '20',
28 'required' => '1',
29 'autofocus' => '',
30 ),
31 'Password' => array(
32 'type' => 'password',
33 'label-message' => 'yourpassword',
34 'size' => '20',
35 'id' => 'wpPassword2',
36 'required' => '',
37 ),
38 'Retype' => array(
39 'type' => 'password',
40 'label-message' => 'yourpasswordagain',
41 'size' => '20',
42 'id' => 'wpRetype',
43 'required' => '',
44 ),
45 'Email' => array(
46 'type' => 'email',
47 'label-message' => 'youremail',
48 'size' => '20',
49 'id' => 'wpEmail',
50 ),
51 'RealName' => array(
52 'type' => 'text',
53 'label-message' => 'yourrealname',
54 'id' => 'wpRealName',
55 'tabindex' => '1',
56 'size' => '20',
57 ),
58 'Remember' => array(
59 'type' => 'check',
60 'label-message' => 'remembermypassword',
61 'id' => 'wpRemember',
62 ),
63 'Domain' => array(
64 'type' => 'select',
65 'id' => 'wpDomain',
66 'label-message' => 'yourdomainname',
67 'options' => null,
68 'default' => null,
69 ),
70 );
71
72 public function __construct(){
73 parent::__construct( 'CreateAccount', 'createaccount' );
74 $this->mLogin = new Login();
75 $this->mFormFields['RealName']['label-help'] = 'prefs-help-realname';
76 }
77
78 public function execute( $par ){
79 global $wgUser, $wgOut;
80
81 $this->setHeaders();
82 $this->loadQuery();
83
84 # Block signup here if in readonly. Keeps user from
85 # going through the process (filling out data, etc)
86 # and being informed later.
87 if ( wfReadOnly() ) {
88 $wgOut->readOnlyPage();
89 return;
90 }
91 # Bail out straightaway on permissions errors
92 if ( !$this->userCanExecute( $wgUser ) ) {
93 $this->displayRestrictionError();
94 return;
95 } elseif ( $wgUser->isBlockedFromCreateAccount() ) {
96 $this->userBlockedMessage();
97 return;
98 } elseif ( count( $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $wgUser, true ) )>0 ) {
99 $wgOut->showPermissionsErrorPage( $permErrors, 'createaccount' );
100 return;
101 }
102
103 if( $this->mPosted ) {
104 $this->addNewAccount( $this->mCreateaccountMail );
105 } else {
106 $this->showMainForm('');
107 }
108 }
109
110 /**
111 * Load the member variables from the request parameters
112 */
113 protected function loadQuery(){
114 global $wgRequest, $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
115 $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
116 && $wgEnableEmail;
117
118 $this->mUsername = $wgRequest->getText( 'wpName' );
119 $this->mPassword = $wgRequest->getText( 'wpPassword' );
120 $this->mRetype = $wgRequest->getText( 'wpRetype' );
121 $this->mDomain = $wgRequest->getText( 'wpDomain' );
122 $this->mReturnTo = $wgRequest->getVal( 'returnto' );
123 $this->mReturnToQuery = $wgRequest->getVal( 'returntoquery' );
124 $this->mPosted = $wgRequest->wasPosted();
125 $this->mCreateaccountMail = $wgRequest->getCheck( 'wpCreateaccountMail' )
126 && $wgEnableEmail;
127 $this->mRemember = $wgRequest->getCheck( 'wpRemember' );
128 $this->mLanguage = $wgRequest->getText( 'uselang' );
129
130 if ( $wgRedirectOnLogin ) {
131 $this->mReturnTo = $wgRedirectOnLogin;
132 $this->mReturnToQuery = '';
133 }
134
135 if( $wgEnableEmail ) {
136 $this->mEmail = $wgRequest->getText( 'wpEmail' );
137 } else {
138 $this->mEmail = '';
139 }
140 if( !in_array( 'realname', $wgHiddenPrefs ) ) {
141 $this->mRealName = $wgRequest->getText( 'wpRealName' );
142 } else {
143 $this->mRealName = '';
144 }
145
146 if( !$wgAuth->validDomain( $this->mDomain ) ) {
147 $this->mDomain = 'invaliddomain';
148 }
149 $wgAuth->setDomain( $this->mDomain );
150
151 # When switching accounts, it sucks to get automatically logged out
152 $returnToTitle = Title::newFromText( $this->mReturnTo );
153 if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
154 $this->mReturnTo = '';
155 $this->mReturnToQuery = '';
156 }
157 }
158
159 /**
160 * Create a new user account from the provided data
161 */
162 protected function addNewAccount( $byEmail=false ) {
163 global $wgUser, $wgEmailAuthentication;
164
165 # Do a quick check that the user actually managed to type
166 # the password in the same both times
167 if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) {
168 return $this->showMainForm( wfMsgExt( 'badretype', 'parseinline' ) );
169 }
170
171 # Create the account and abort if there's a problem doing so
172 $status = $this->mLogin->attemptCreation( $byEmail );
173 switch( $status ){
174 case Login::SUCCESS:
175 case Login::MAIL_ERROR:
176 break;
177
178 case Login::CREATE_BADDOMAIN:
179 case Login::CREATE_EXISTS:
180 case Login::NO_NAME:
181 case Login::CREATE_NEEDEMAIL:
182 case Login::CREATE_BADEMAIL:
183 case Login::CREATE_BADNAME:
184 case Login::WRONG_PLUGIN_PASS:
185 case Login::ABORTED:
186 return $this->showMainForm( wfMsgExt( $this->mLogin->mCreateResult, 'parseinline' ) );
187
188 case Login::CREATE_SORBS:
189 return $this->showMainForm( wfMsgExt( 'sorbs_create_account_reason', 'parseinline' ) . ' (' . wfGetIP() . ')' );
190
191 case Login::CREATE_BLOCKED:
192 return $this->userBlockedMessage();
193
194 case Login::CREATE_BADPASS:
195 global $wgMinimalPasswordLength;
196 return $this->showMainForm( wfMsgExt( $this->mLogin->mCreateResult, array( 'parsemag' ), $wgMinimalPasswordLength ) );
197
198 case Login::THROTTLED:
199 global $wgAccountCreationThrottle;
200 return $this->showMainForm( wfMsgExt( 'acct_creation_throttle_hit', array( 'parseinline' ), $wgAccountCreationThrottle ) );
201
202 default:
203 throw new MWException( "Unhandled status code $status in " . __METHOD__ );
204 }
205
206 # If we showed up language selection links, and one was in use, be
207 # smart (and sensible) and save that language as the user's preference
208 global $wgLoginLanguageSelector;
209 if( $wgLoginLanguageSelector && $this->mLanguage )
210 $this->mLogin->mUser->setOption( 'language', $this->mLanguage );
211 $this->mLogin->mUser->saveSettings();
212
213 if( $byEmail ) {
214 if( $result == Login::MAIL_ERROR ){
215 # FIXME: we are totally screwed if we end up here...
216 $this->showMainForm( wfMsgExt( 'mailerror', 'parseinline', $this->mLogin->mMailResult->getMessage() ) );
217 } else {
218 $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) );
219 $wgOut->addWikiMsg( 'accmailtext', $this->mLogin->mUser->getName(), $this->mLogin->mUser->getEmail() );
220 $wgOut->returnToMain( false );
221 }
222
223 } else {
224
225 # There might be a message stored from the confirmation mail
226 # send, which we can display.
227 if( $wgEmailAuthentication && $this->mLogin->mMailResult ) {
228 global $wgOut;
229 if( WikiError::isError( $this->mLogin->mMailResult ) ) {
230 $wgOut->addWikiMsg( 'confirmemail_sendfailed', $this->mLogin->mMailResult->getMessage() );
231 } else {
232 $wgOut->addWikiMsg( 'confirmemail_oncreate' );
233 }
234 }
235
236 # If not logged in, assume the new account as the current
237 # one and set session cookies then show a "welcome" message
238 # or a "need cookies" message as needed
239 if( $wgUser->isAnon() ) {
240 $wgUser = $this->mLogin->mUser;
241 $wgUser->setCookies();
242 if( $this->hasSessionCookie() ) {
243 return $this->successfulCreation();
244 } else {
245 return $this->cookieRedirectCheck();
246 }
247 } else {
248 # Show confirmation that the account was created
249 global $wgOut;
250 $self = SpecialPage::getTitleFor( 'Userlogin' );
251 $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) );
252 $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $this->mLogin->mUser->getName() ) );
253 $wgOut->returnToMain( false, $self );
254 return true;
255 }
256 }
257 }
258
259 /**
260 * Run any hooks registered for logins, then
261 * display a message welcoming the user.
262 */
263 protected function successfulCreation(){
264 global $wgUser, $wgOut;
265
266 # Run any hooks; display injected HTML
267 $injected_html = '';
268 wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html));
269
270 SpecialUserLogin::displaySuccessfulLogin(
271 'welcomecreation',
272 $injected_html,
273 $this->mReturnTo,
274 $this->mReturnToQuery );
275 }
276
277 /**
278 * Display a message indicating that account creation from their IP has
279 * been blocked by a (range)block with 'block account creation' enabled.
280 * It's likely that this feature will be used for blocking large numbers
281 * of innocent people, e.g. range blocks on schools. Don't blame it on
282 * the user. There's a small chance that it really is the user's fault,
283 * i.e. the username is blocked and they haven't bothered to log out
284 * before trying to create an account to evade it, but we'll leave that
285 * to their guilty conscience to figure out...
286 */
287 protected function userBlockedMessage() {
288 global $wgOut, $wgUser;
289
290 $wgOut->setPageTitle( wfMsg( 'cantcreateaccounttitle' ) );
291 $wgOut->setRobotPolicy( 'noindex,nofollow' );
292 $wgOut->setArticleRelated( false );
293
294 $ip = wfGetIP();
295 $blocker = User::whoIs( $wgUser->mBlock->mBy );
296 $block_reason = $wgUser->mBlock->mReason;
297
298 if ( strval( $block_reason ) === '' ) {
299 $block_reason = wfMsgExt( 'blockednoreason', 'parseinline' );
300 }
301 $wgOut->addWikiMsg( 'cantcreateaccount-text', $ip, $block_reason, $blocker );
302 $wgOut->returnToMain( false );
303 }
304
305 /**
306 * Show the main input form, with an appropriate error message
307 * from a previous iteration, if necessary
308 * @param $msg String HTML of message received previously
309 * @param $msgtype String type of message, usually 'error'
310 */
311 protected function showMainForm( $msg, $msgtype = 'error' ) {
312 global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
313 global $wgCookiePrefix, $wgLoginLanguageSelector;
314 global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
315
316 # Parse the error message if we got one
317 if( $msg ){
318 if( $msgtype == 'error' ){
319 $msg = wfMsgExt( 'loginerror', 'parseinline' ) . ' ' . $msg;
320 }
321 $msg = Html::rawElement(
322 'div',
323 array( 'class' => $msgtype . 'box' ),
324 $msg
325 );
326 } else {
327 $msg = '';
328 }
329
330 # Make sure the returnTo strings don't get lost if the
331 # user changes language, etc
332 $linkq = array();
333 if ( !empty( $this->mReturnTo ) ) {
334 $linkq['returnto'] = wfUrlencode( $this->mReturnTo );
335 if ( !empty( $this->mReturnToQuery ) )
336 $linkq['returntoquery'] = wfUrlencode( $this->mReturnToQuery );
337 }
338
339 # Pass any language selection on to the mode switch link
340 if( $wgLoginLanguageSelector && $this->mLanguage )
341 $linkq['uselang'] = $this->mLanguage;
342
343 $skin = $wgUser->getSkin();
344 $link = $skin->link(
345 SpecialPage::getTitleFor( 'Userlogin' ),
346 wfMsgHtml( 'gotaccountlink' ),
347 array(),
348 $linkq );
349 $link = $wgUser->isLoggedIn()
350 ? ''
351 : wfMsgWikiHtml( 'gotaccount', $link );
352
353 # Prepare language selection links as needed
354 $langSelector = $wgLoginLanguageSelector
355 ? Html::rawElement(
356 'div',
357 array( 'id' => 'languagelinks' ),
358 SpecialUserLogin::makeLanguageSelector( $this->getTitle(), $this->mReturnTo ) )
359 : '';
360
361 # Add a 'send password by email' button if available
362 $buttons = '';
363 if( $wgEnableEmail && $wgUser->isLoggedIn() ){
364 $buttons = Html::element(
365 'input',
366 array(
367 'type' => 'submit',
368 'name' => 'wpCreateaccountMail',
369 'value' => wfMsg( 'createaccountmail' ),
370 'id' => 'wpCreateaccountMail',
371 )
372 );
373 }
374
375 # Give authentication and captcha plugins a chance to
376 # modify the form, by hook or by using $wgAuth
377 $wgAuth->modifyUITemplate( $this, 'new' );
378 wfRunHooks( 'UserCreateForm', array( &$this ) );
379
380 # The most likely use of the hook is to enable domains;
381 # check that now, and add fields if necessary
382 if( $this->mDomains ){
383 $this->mFormFields['Domain']['options'] = $this->mDomains;
384 $this->mFormFields['Domain']['default'] = $this->mDomain;
385 } else {
386 unset( $this->mFormFields['Domain'] );
387 }
388
389 # Or to switch email on or off
390 if( !$wgEnableEmail || !$this->mUseEmail ){
391 unset( $this->mFormFields['Email'] );
392 } else {
393 if( $wgEmailConfirmToEdit ){
394 $this->mFormFields['Email']['label-help'] = 'prefs-help-email-required';
395 $this->mFormFields['Email']['required'] = '';
396 } else {
397 $this->mFormFields['Email']['label-help'] = 'prefs-help-email';
398 }
399 }
400
401 # Or to play with realname
402 if( in_array( 'realname', $wgHiddenPrefs ) || !$this->mUseRealname ){
403 unset( $this->mFormFields['Realname'] );
404 }
405
406 # Or to tweak the 'remember my password' checkbox
407 if( !($wgCookieExpiration > 0) || !$this->mUseRemember ){
408 # Remove it altogether
409 unset( $this->mFormFields['Remember'] );
410 } elseif( $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ){
411 # Or check it by default
412 # FIXME: this doesn't always work?
413 $this->mFormFields['Remember']['checked'] = '1';
414 }
415
416 $form = new HTMLForm( $this->mFormFields, '' );
417 $form->setTitle( $this->getTitle() );
418 $form->setSubmitText( wfMsg( 'createaccount' ) );
419 $form->setSubmitId( 'wpCreateaccount' );
420 $form->suppressReset();
421 $form->loadData();
422
423 $formContents = ''
424 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
425 $link )
426 . $this->mFormHeader
427 . $langSelector
428 . $form->getBody()
429 . $form->getButtons()
430 . $buttons
431 . Xml::hidden( 'returnto', $this->mReturnTo )
432 . Xml::hidden( 'returntoquery', $this->mReturnToQuery )
433 ;
434
435 $wgOut->setPageTitle( wfMsg( 'createaccount' ) );
436 $wgOut->setRobotPolicy( 'noindex,nofollow' );
437 $wgOut->setArticleRelated( false );
438 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
439
440 $wgOut->addHTML(
441 Html::rawElement(
442 'div',
443 array( 'id' => 'loginstart' ),
444 wfMsgExt( 'loginstart', array( 'parseinline' ) )
445 ) .
446 $msg .
447 Html::rawElement(
448 'div',
449 array( 'id' => 'userloginForm' ),
450 $form->wrapForm( $formContents )
451 ) .
452 Html::rawElement(
453 'div',
454 array( 'id' => 'loginend' ),
455 wfMsgExt( 'loginend', array( 'parseinline' ) )
456 )
457 );
458
459 }
460
461 /**
462 * Check if a session cookie is present.
463 *
464 * This will not pick up a cookie set during _this_ request, but is meant
465 * to ensure that the client is returning the cookie which was set on a
466 * previous pass through the system.
467 *
468 * @private
469 */
470 protected function hasSessionCookie() {
471 global $wgDisableCookieCheck, $wgRequest;
472 return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
473 }
474
475 /**
476 * Do a redirect back to the same page, so we can check any
477 * new session cookies.
478 */
479 protected function cookieRedirectCheck() {
480 global $wgOut;
481
482 $query = array( 'wpCookieCheck' => '1' );
483 if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
484 $check = $this->getTitle()->getFullURL( $query );
485
486 return $wgOut->redirect( $check );
487 }
488
489 /**
490 * Check the cookies and show errors if they're not enabled.
491 * @param $type String action being performed
492 */
493 protected function onCookieRedirectCheck() {
494 if ( !$this->hasSessionCookie() ) {
495 return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
496 } else {
497 return SpecialUserlogin::successfulLogin(
498 array( 'welcomecreate' ),
499 $this->mReturnTo,
500 $this->mReturnToQuery
501 );
502 }
503 }
504
505 /**
506 * Since the UserCreateForm hook was changed to pass a SpecialPage
507 * instead of a QuickTemplate derivative, old extensions might
508 * easily try calling these methods expecing them to exist. Tempting
509 * though it is to let them have the fatal error, let's at least
510 * fail gracefully...
511 * @deprecated
512 */
513 public function set(){
514 wfDeprecated( __METHOD__ );
515 }
516 public function addInputItem(){
517 wfDeprecated( __METHOD__ );
518 }
519 }