Allow extra buttons and hidden fields to be included in HTMLForm. Accessible either...
[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( 'createaccounterror', array( 'parseinline', 'replaceafter' ), $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 # Give authentication and captcha plugins a chance to
362 # modify the form, by hook or by using $wgAuth
363 $wgAuth->modifyUITemplate( $this, 'new' );
364 wfRunHooks( 'UserCreateForm', array( &$this ) );
365
366 # The most likely use of the hook is to enable domains;
367 # check that now, and add fields if necessary
368 if( $this->mDomains ){
369 $this->mFormFields['Domain']['options'] = $this->mDomains;
370 $this->mFormFields['Domain']['default'] = $this->mDomain;
371 } else {
372 unset( $this->mFormFields['Domain'] );
373 }
374
375 # Or to switch email on or off
376 if( !$wgEnableEmail || !$this->mUseEmail ){
377 unset( $this->mFormFields['Email'] );
378 } else {
379 if( $wgEmailConfirmToEdit ){
380 $this->mFormFields['Email']['label-help'] = 'prefs-help-email-required';
381 $this->mFormFields['Email']['required'] = '';
382 } else {
383 $this->mFormFields['Email']['label-help'] = 'prefs-help-email';
384 }
385 }
386
387 # Or to play with realname
388 if( in_array( 'realname', $wgHiddenPrefs ) || !$this->mUseRealname ){
389 unset( $this->mFormFields['Realname'] );
390 }
391
392 # Or to tweak the 'remember my password' checkbox
393 if( !($wgCookieExpiration > 0) || !$this->mUseRemember ){
394 # Remove it altogether
395 unset( $this->mFormFields['Remember'] );
396 } elseif( $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ){
397 # Or check it by default
398 # FIXME: this doesn't always work?
399 $this->mFormFields['Remember']['checked'] = '1';
400 }
401
402 $form = new HTMLForm( $this->mFormFields, '' );
403 $form->setTitle( $this->getTitle() );
404 $form->setSubmitText( wfMsg( 'createaccount' ) );
405 $form->setSubmitId( 'wpCreateaccount' );
406 $form->suppressReset();
407 $form->loadData();
408 $form->addHiddenField( 'returnto', $this->mReturnTo );
409 $form->addHiddenField( 'returntoquery', $this->mReturnToQuery );
410
411 # Add a 'send password by email' button if available
412 if( $wgEnableEmail && $wgUser->isLoggedIn() ){
413 $form->addButton(
414 'wpCreateaccountMail',
415 wfMsg( 'createaccountmail' ),
416 'wpCreateaccountMail'
417 );
418 }
419
420 $formContents = ''
421 . Html::rawElement( 'p', array( 'id' => 'userloginlink' ),
422 $link )
423 . $this->mFormHeader
424 . $langSelector
425 . $form->getBody()
426 . $form->getHiddenFields()
427 . $form->getButtons()
428 ;
429
430 $wgOut->setPageTitle( wfMsg( 'createaccount' ) );
431 $wgOut->setRobotPolicy( 'noindex,nofollow' );
432 $wgOut->setArticleRelated( false );
433 $wgOut->disallowUserJs(); # Stop malicious userscripts sniffing passwords
434
435 $wgOut->addHTML(
436 Html::rawElement(
437 'div',
438 array( 'id' => 'loginstart' ),
439 wfMsgExt( 'loginstart', array( 'parseinline' ) )
440 ) .
441 $msg .
442 Html::rawElement(
443 'div',
444 array( 'id' => 'userloginForm' ),
445 $form->wrapForm( $formContents )
446 ) .
447 Html::rawElement(
448 'div',
449 array( 'id' => 'loginend' ),
450 wfMsgExt( 'loginend', array( 'parseinline' ) )
451 )
452 );
453
454 }
455
456 /**
457 * Check if a session cookie is present.
458 *
459 * This will not pick up a cookie set during _this_ request, but is meant
460 * to ensure that the client is returning the cookie which was set on a
461 * previous pass through the system.
462 *
463 * @private
464 */
465 protected function hasSessionCookie() {
466 global $wgDisableCookieCheck, $wgRequest;
467 return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie();
468 }
469
470 /**
471 * Do a redirect back to the same page, so we can check any
472 * new session cookies.
473 */
474 protected function cookieRedirectCheck() {
475 global $wgOut;
476
477 $query = array( 'wpCookieCheck' => '1' );
478 if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo;
479 $check = $this->getTitle()->getFullURL( $query );
480
481 return $wgOut->redirect( $check );
482 }
483
484 /**
485 * Check the cookies and show errors if they're not enabled.
486 * @param $type String action being performed
487 */
488 protected function onCookieRedirectCheck() {
489 if ( !$this->hasSessionCookie() ) {
490 return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
491 } else {
492 return SpecialUserlogin::successfulLogin(
493 array( 'welcomecreate' ),
494 $this->mReturnTo,
495 $this->mReturnToQuery
496 );
497 }
498 }
499
500 /**
501 * Add text to the header. Only write to $mFormHeader directly
502 * if you're determined to overwrite anything that other
503 * extensions might have added.
504 * @param $text String HTML
505 */
506 public function addFormHeader( $text ){
507 $this->mFormHeader .= $text;
508 }
509
510 /**
511 * Since the UserCreateForm hook was changed to pass a SpecialPage
512 * instead of a QuickTemplate derivative, old extensions might
513 * easily try calling these methods expecing them to exist. Tempting
514 * though it is to let them have the fatal error, let's at least
515 * fail gracefully...
516 * @deprecated
517 */
518 public function set(){
519 wfDeprecated( __METHOD__ );
520 }
521 public function addInputItem(){
522 wfDeprecated( __METHOD__ );
523 }
524 }