PageRenderTime 24ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/specials/SpecialUserlogin.php

https://bitbucket.org/ghostfreeman/freeside-wiki
PHP | 1308 lines | 841 code | 154 blank | 313 comment | 202 complexity | 62ad5e5903ecbc5c877d2d3053acdaaf MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0
  1. <?php
  2. /**
  3. * Implements Special:UserLogin
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License along
  16. * with this program; if not, write to the Free Software Foundation, Inc.,
  17. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. * http://www.gnu.org/copyleft/gpl.html
  19. *
  20. * @file
  21. * @ingroup SpecialPage
  22. */
  23. /**
  24. * Implements Special:UserLogin
  25. *
  26. * @ingroup SpecialPage
  27. */
  28. class LoginForm extends SpecialPage {
  29. const SUCCESS = 0;
  30. const NO_NAME = 1;
  31. const ILLEGAL = 2;
  32. const WRONG_PLUGIN_PASS = 3;
  33. const NOT_EXISTS = 4;
  34. const WRONG_PASS = 5;
  35. const EMPTY_PASS = 6;
  36. const RESET_PASS = 7;
  37. const ABORTED = 8;
  38. const CREATE_BLOCKED = 9;
  39. const THROTTLED = 10;
  40. const USER_BLOCKED = 11;
  41. const NEED_TOKEN = 12;
  42. const WRONG_TOKEN = 13;
  43. var $mUsername, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
  44. var $mAction, $mCreateaccount, $mCreateaccountMail;
  45. var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
  46. var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS;
  47. var $mType, $mReason, $mRealName;
  48. var $mAbortLoginErrorMsg = 'login-abort-generic';
  49. private $mLoaded = false;
  50. /**
  51. * @var ExternalUser
  52. */
  53. private $mExtUser = null;
  54. /**
  55. * @ var WebRequest
  56. */
  57. private $mOverrideRequest = null;
  58. /**
  59. * @param WebRequest $request
  60. */
  61. public function __construct( $request = null ) {
  62. parent::__construct( 'Userlogin' );
  63. $this->mOverrideRequest = $request;
  64. }
  65. /**
  66. * Loader
  67. */
  68. function load() {
  69. global $wgAuth, $wgHiddenPrefs, $wgEnableEmail;
  70. if ( $this->mLoaded ) {
  71. return;
  72. }
  73. $this->mLoaded = true;
  74. if ( $this->mOverrideRequest === null ) {
  75. $request = $this->getRequest();
  76. } else {
  77. $request = $this->mOverrideRequest;
  78. }
  79. $this->mType = $request->getText( 'type' );
  80. $this->mUsername = $request->getText( 'wpName' );
  81. $this->mPassword = $request->getText( 'wpPassword' );
  82. $this->mRetype = $request->getText( 'wpRetype' );
  83. $this->mDomain = $request->getText( 'wpDomain' );
  84. $this->mReason = $request->getText( 'wpReason' );
  85. $this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
  86. $this->mPosted = $request->wasPosted();
  87. $this->mCreateaccount = $request->getCheck( 'wpCreateaccount' );
  88. $this->mCreateaccountMail = $request->getCheck( 'wpCreateaccountMail' )
  89. && $wgEnableEmail;
  90. $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' );
  91. $this->mAction = $request->getVal( 'action' );
  92. $this->mRemember = $request->getCheck( 'wpRemember' );
  93. $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' );
  94. $this->mLanguage = $request->getText( 'uselang' );
  95. $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' );
  96. $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' );
  97. $this->mReturnTo = $request->getVal( 'returnto', '' );
  98. $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
  99. if( $wgEnableEmail ) {
  100. $this->mEmail = $request->getText( 'wpEmail' );
  101. } else {
  102. $this->mEmail = '';
  103. }
  104. if( !in_array( 'realname', $wgHiddenPrefs ) ) {
  105. $this->mRealName = $request->getText( 'wpRealName' );
  106. } else {
  107. $this->mRealName = '';
  108. }
  109. if( !$wgAuth->validDomain( $this->mDomain ) ) {
  110. $this->mDomain = $wgAuth->getDomain();
  111. }
  112. $wgAuth->setDomain( $this->mDomain );
  113. # 1. When switching accounts, it sucks to get automatically logged out
  114. # 2. Do not return to PasswordReset after a successful password change
  115. # but goto Wiki start page (Main_Page) instead ( bug 33997 )
  116. $returnToTitle = Title::newFromText( $this->mReturnTo );
  117. if( is_object( $returnToTitle ) && (
  118. $returnToTitle->isSpecial( 'Userlogout' )
  119. || $returnToTitle->isSpecial( 'PasswordReset' ) ) ) {
  120. $this->mReturnTo = '';
  121. $this->mReturnToQuery = '';
  122. }
  123. }
  124. function getDescription() {
  125. return $this->msg( $this->getUser()->isAllowed( 'createaccount' ) ?
  126. 'userlogin' : 'userloginnocreate' )->text();
  127. }
  128. public function execute( $par ) {
  129. if ( session_id() == '' ) {
  130. wfSetupSession();
  131. }
  132. $this->load();
  133. $this->setHeaders();
  134. if ( $par == 'signup' ) { # Check for [[Special:Userlogin/signup]]
  135. $this->mType = 'signup';
  136. }
  137. if ( !is_null( $this->mCookieCheck ) ) {
  138. $this->onCookieRedirectCheck( $this->mCookieCheck );
  139. return;
  140. } elseif( $this->mPosted ) {
  141. if( $this->mCreateaccount ) {
  142. $this->addNewAccount();
  143. return;
  144. } elseif ( $this->mCreateaccountMail ) {
  145. $this->addNewAccountMailPassword();
  146. return;
  147. } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) {
  148. $this->processLogin();
  149. return;
  150. }
  151. }
  152. $this->mainLoginForm( '' );
  153. }
  154. /**
  155. * @private
  156. */
  157. function addNewAccountMailPassword() {
  158. if ( $this->mEmail == '' ) {
  159. $this->mainLoginForm( $this->msg( 'noemailcreate' )->escaped() );
  160. return;
  161. }
  162. $u = $this->addNewaccountInternal();
  163. if ( $u == null ) {
  164. return;
  165. }
  166. // Wipe the initial password and mail a temporary one
  167. $u->setPassword( null );
  168. $u->saveSettings();
  169. $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' );
  170. wfRunHooks( 'AddNewAccount', array( $u, true ) );
  171. $u->addNewUserLogEntry( true, $this->mReason );
  172. $out = $this->getOutput();
  173. $out->setPageTitle( $this->msg( 'accmailtitle' ) );
  174. if( !$result->isGood() ) {
  175. $this->mainLoginForm( $this->msg( 'mailerror', $result->getWikiText() )->text() );
  176. } else {
  177. $out->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() );
  178. $this->executeReturnTo( 'success' );
  179. }
  180. }
  181. /**
  182. * @private
  183. * @return bool
  184. */
  185. function addNewAccount() {
  186. global $wgUser, $wgEmailAuthentication, $wgLoginLanguageSelector;
  187. # Create the account and abort if there's a problem doing so
  188. $u = $this->addNewAccountInternal();
  189. if( $u == null ) {
  190. return false;
  191. }
  192. # If we showed up language selection links, and one was in use, be
  193. # smart (and sensible) and save that language as the user's preference
  194. if( $wgLoginLanguageSelector && $this->mLanguage ) {
  195. $u->setOption( 'language', $this->mLanguage );
  196. }
  197. $out = $this->getOutput();
  198. # Send out an email authentication message if needed
  199. if( $wgEmailAuthentication && Sanitizer::validateEmail( $u->getEmail() ) ) {
  200. $status = $u->sendConfirmationMail();
  201. if( $status->isGood() ) {
  202. $out->addWikiMsg( 'confirmemail_oncreate' );
  203. } else {
  204. $out->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) );
  205. }
  206. }
  207. # Save settings (including confirmation token)
  208. $u->saveSettings();
  209. # If not logged in, assume the new account as the current one and set
  210. # session cookies then show a "welcome" message or a "need cookies"
  211. # message as needed
  212. if( $this->getUser()->isAnon() ) {
  213. $u->setCookies();
  214. $wgUser = $u;
  215. // This should set it for OutputPage and the Skin
  216. // which is needed or the personal links will be
  217. // wrong.
  218. $this->getContext()->setUser( $u );
  219. wfRunHooks( 'AddNewAccount', array( $u, false ) );
  220. $u->addNewUserLogEntry();
  221. if( $this->hasSessionCookie() ) {
  222. $this->successfulCreation();
  223. } else {
  224. $this->cookieRedirectCheck( 'new' );
  225. }
  226. } else {
  227. # Confirm that the account was created
  228. $out->setPageTitle( $this->msg( 'accountcreated' ) );
  229. $out->addWikiMsg( 'accountcreatedtext', $u->getName() );
  230. $out->addReturnTo( $this->getTitle() );
  231. wfRunHooks( 'AddNewAccount', array( $u, false ) );
  232. $u->addNewUserLogEntry( false, $this->mReason );
  233. }
  234. return true;
  235. }
  236. /**
  237. * @private
  238. * @return bool|User
  239. */
  240. function addNewAccountInternal() {
  241. global $wgAuth, $wgMemc, $wgAccountCreationThrottle,
  242. $wgMinimalPasswordLength, $wgEmailConfirmToEdit;
  243. // If the user passes an invalid domain, something is fishy
  244. if( !$wgAuth->validDomain( $this->mDomain ) ) {
  245. $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
  246. return false;
  247. }
  248. // If we are not allowing users to login locally, we should be checking
  249. // to see if the user is actually able to authenticate to the authenti-
  250. // cation server before they create an account (otherwise, they can
  251. // create a local account and login as any domain user). We only need
  252. // to check this for domains that aren't local.
  253. if( 'local' != $this->mDomain && $this->mDomain != '' ) {
  254. if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mUsername )
  255. || !$wgAuth->authenticate( $this->mUsername, $this->mPassword ) ) ) {
  256. $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
  257. return false;
  258. }
  259. }
  260. if ( wfReadOnly() ) {
  261. throw new ReadOnlyError;
  262. }
  263. # Request forgery checks.
  264. if ( !self::getCreateaccountToken() ) {
  265. self::setCreateaccountToken();
  266. $this->mainLoginForm( $this->msg( 'nocookiesfornew' )->parse() );
  267. return false;
  268. }
  269. # The user didn't pass a createaccount token
  270. if ( !$this->mToken ) {
  271. $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
  272. return false;
  273. }
  274. # Validate the createaccount token
  275. if ( $this->mToken !== self::getCreateaccountToken() ) {
  276. $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
  277. return false;
  278. }
  279. # Check permissions
  280. $currentUser = $this->getUser();
  281. if ( !$currentUser->isAllowed( 'createaccount' ) ) {
  282. throw new PermissionsError( 'createaccount' );
  283. } elseif ( $currentUser->isBlockedFromCreateAccount() ) {
  284. $this->userBlockedMessage( $currentUser->isBlockedFromCreateAccount() );
  285. return false;
  286. }
  287. # Include checks that will include GlobalBlocking (Bug 38333)
  288. $permErrors = $this->getTitle()->getUserPermissionsErrors( 'createaccount', $currentUser, true );
  289. if ( count( $permErrors ) ) {
  290. throw new PermissionsError( 'createaccount', $permErrors );
  291. }
  292. $ip = $this->getRequest()->getIP();
  293. if ( $currentUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
  294. $this->mainLoginForm( $this->msg( 'sorbs_create_account_reason' )->text() . ' ' . $this->msg( 'parentheses', $ip )->escaped() );
  295. return false;
  296. }
  297. # Now create a dummy user ($u) and check if it is valid
  298. $name = trim( $this->mUsername );
  299. $u = User::newFromName( $name, 'creatable' );
  300. if ( !is_object( $u ) ) {
  301. $this->mainLoginForm( $this->msg( 'noname' )->text() );
  302. return false;
  303. }
  304. if ( 0 != $u->idForName() ) {
  305. $this->mainLoginForm( $this->msg( 'userexists' )->text() );
  306. return false;
  307. }
  308. if ( 0 != strcmp( $this->mPassword, $this->mRetype ) ) {
  309. $this->mainLoginForm( $this->msg( 'badretype' )->text() );
  310. return false;
  311. }
  312. # check for minimal password length
  313. $valid = $u->getPasswordValidity( $this->mPassword );
  314. if ( $valid !== true ) {
  315. if ( !$this->mCreateaccountMail ) {
  316. if ( is_array( $valid ) ) {
  317. $message = array_shift( $valid );
  318. $params = $valid;
  319. } else {
  320. $message = $valid;
  321. $params = array( $wgMinimalPasswordLength );
  322. }
  323. $this->mainLoginForm( $this->msg( $message, $params )->text() );
  324. return false;
  325. } else {
  326. # do not force a password for account creation by email
  327. # set invalid password, it will be replaced later by a random generated password
  328. $this->mPassword = null;
  329. }
  330. }
  331. # if you need a confirmed email address to edit, then obviously you
  332. # need an email address.
  333. if ( $wgEmailConfirmToEdit && empty( $this->mEmail ) ) {
  334. $this->mainLoginForm( $this->msg( 'noemailtitle' )->text() );
  335. return false;
  336. }
  337. if( !empty( $this->mEmail ) && !Sanitizer::validateEmail( $this->mEmail ) ) {
  338. $this->mainLoginForm( $this->msg( 'invalidemailaddress' )->text() );
  339. return false;
  340. }
  341. # Set some additional data so the AbortNewAccount hook can be used for
  342. # more than just username validation
  343. $u->setEmail( $this->mEmail );
  344. $u->setRealName( $this->mRealName );
  345. $abortError = '';
  346. if( !wfRunHooks( 'AbortNewAccount', array( $u, &$abortError ) ) ) {
  347. // Hook point to add extra creation throttles and blocks
  348. wfDebug( "LoginForm::addNewAccountInternal: a hook blocked creation\n" );
  349. $this->mainLoginForm( $abortError );
  350. return false;
  351. }
  352. // Hook point to check for exempt from account creation throttle
  353. if ( !wfRunHooks( 'ExemptFromAccountCreationThrottle', array( $ip ) ) ) {
  354. wfDebug( "LoginForm::exemptFromAccountCreationThrottle: a hook allowed account creation w/o throttle\n" );
  355. } else {
  356. if ( ( $wgAccountCreationThrottle && $currentUser->isPingLimitable() ) ) {
  357. $key = wfMemcKey( 'acctcreate', 'ip', $ip );
  358. $value = $wgMemc->get( $key );
  359. if ( !$value ) {
  360. $wgMemc->set( $key, 0, 86400 );
  361. }
  362. if ( $value >= $wgAccountCreationThrottle ) {
  363. $this->throttleHit( $wgAccountCreationThrottle );
  364. return false;
  365. }
  366. $wgMemc->incr( $key );
  367. }
  368. }
  369. if( !$wgAuth->addUser( $u, $this->mPassword, $this->mEmail, $this->mRealName ) ) {
  370. $this->mainLoginForm( $this->msg( 'externaldberror' )->text() );
  371. return false;
  372. }
  373. self::clearCreateaccountToken();
  374. return $this->initUser( $u, false );
  375. }
  376. /**
  377. * Actually add a user to the database.
  378. * Give it a User object that has been initialised with a name.
  379. *
  380. * @param $u User object.
  381. * @param $autocreate boolean -- true if this is an autocreation via auth plugin
  382. * @return User object.
  383. * @private
  384. */
  385. function initUser( $u, $autocreate ) {
  386. global $wgAuth;
  387. $u->addToDatabase();
  388. if ( $wgAuth->allowPasswordChange() ) {
  389. $u->setPassword( $this->mPassword );
  390. }
  391. $u->setEmail( $this->mEmail );
  392. $u->setRealName( $this->mRealName );
  393. $u->setToken();
  394. $wgAuth->initUser( $u, $autocreate );
  395. if ( $this->mExtUser ) {
  396. $this->mExtUser->linkToLocal( $u->getId() );
  397. $email = $this->mExtUser->getPref( 'emailaddress' );
  398. if ( $email && !$this->mEmail ) {
  399. $u->setEmail( $email );
  400. }
  401. }
  402. $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
  403. $u->saveSettings();
  404. # Update user count
  405. $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 );
  406. $ssUpdate->doUpdate();
  407. return $u;
  408. }
  409. /**
  410. * Internally authenticate the login request.
  411. *
  412. * This may create a local account as a side effect if the
  413. * authentication plugin allows transparent local account
  414. * creation.
  415. * @return int
  416. */
  417. public function authenticateUserData() {
  418. global $wgUser, $wgAuth;
  419. $this->load();
  420. if ( $this->mUsername == '' ) {
  421. return self::NO_NAME;
  422. }
  423. // We require a login token to prevent login CSRF
  424. // Handle part of this before incrementing the throttle so
  425. // token-less login attempts don't count towards the throttle
  426. // but wrong-token attempts do.
  427. // If the user doesn't have a login token yet, set one.
  428. if ( !self::getLoginToken() ) {
  429. self::setLoginToken();
  430. return self::NEED_TOKEN;
  431. }
  432. // If the user didn't pass a login token, tell them we need one
  433. if ( !$this->mToken ) {
  434. return self::NEED_TOKEN;
  435. }
  436. $throttleCount = self::incLoginThrottle( $this->mUsername );
  437. if ( $throttleCount === true ) {
  438. return self::THROTTLED;
  439. }
  440. // Validate the login token
  441. if ( $this->mToken !== self::getLoginToken() ) {
  442. return self::WRONG_TOKEN;
  443. }
  444. // Load the current user now, and check to see if we're logging in as
  445. // the same name. This is necessary because loading the current user
  446. // (say by calling getName()) calls the UserLoadFromSession hook, which
  447. // potentially creates the user in the database. Until we load $wgUser,
  448. // checking for user existence using User::newFromName($name)->getId() below
  449. // will effectively be using stale data.
  450. if ( $this->getUser()->getName() === $this->mUsername ) {
  451. wfDebug( __METHOD__ . ": already logged in as {$this->mUsername}\n" );
  452. return self::SUCCESS;
  453. }
  454. $this->mExtUser = ExternalUser::newFromName( $this->mUsername );
  455. # TODO: Allow some magic here for invalid external names, e.g., let the
  456. # user choose a different wiki name.
  457. $u = User::newFromName( $this->mUsername );
  458. if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
  459. return self::ILLEGAL;
  460. }
  461. $isAutoCreated = false;
  462. if ( 0 == $u->getID() ) {
  463. $status = $this->attemptAutoCreate( $u );
  464. if ( $status !== self::SUCCESS ) {
  465. return $status;
  466. } else {
  467. $isAutoCreated = true;
  468. }
  469. } else {
  470. global $wgExternalAuthType, $wgAutocreatePolicy;
  471. if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never'
  472. && is_object( $this->mExtUser )
  473. && $this->mExtUser->authenticate( $this->mPassword ) ) {
  474. # The external user and local user have the same name and
  475. # password, so we assume they're the same.
  476. $this->mExtUser->linkToLocal( $u->getID() );
  477. }
  478. $u->load();
  479. }
  480. // Give general extensions, such as a captcha, a chance to abort logins
  481. $abort = self::ABORTED;
  482. if( !wfRunHooks( 'AbortLogin', array( $u, $this->mPassword, &$abort, &$this->mAbortLoginErrorMsg ) ) ) {
  483. return $abort;
  484. }
  485. global $wgBlockDisablesLogin;
  486. if ( !$u->checkPassword( $this->mPassword ) ) {
  487. if( $u->checkTemporaryPassword( $this->mPassword ) ) {
  488. // The e-mailed temporary password should not be used for actu-
  489. // al logins; that's a very sloppy habit, and insecure if an
  490. // attacker has a few seconds to click "search" on someone's o-
  491. // pen mail reader.
  492. //
  493. // Allow it to be used only to reset the password a single time
  494. // to a new value, which won't be in the user's e-mail ar-
  495. // chives.
  496. //
  497. // For backwards compatibility, we'll still recognize it at the
  498. // login form to minimize surprises for people who have been
  499. // logging in with a temporary password for some time.
  500. //
  501. // As a side-effect, we can authenticate the user's e-mail ad-
  502. // dress if it's not already done, since the temporary password
  503. // was sent via e-mail.
  504. if( !$u->isEmailConfirmed() ) {
  505. $u->confirmEmail();
  506. $u->saveSettings();
  507. }
  508. // At this point we just return an appropriate code/ indicating
  509. // that the UI should show a password reset form; bot inter-
  510. // faces etc will probably just fail cleanly here.
  511. $retval = self::RESET_PASS;
  512. } else {
  513. $retval = ( $this->mPassword == '' ) ? self::EMPTY_PASS : self::WRONG_PASS;
  514. }
  515. } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
  516. // If we've enabled it, make it so that a blocked user cannot login
  517. $retval = self::USER_BLOCKED;
  518. } else {
  519. $wgAuth->updateUser( $u );
  520. $wgUser = $u;
  521. // This should set it for OutputPage and the Skin
  522. // which is needed or the personal links will be
  523. // wrong.
  524. $this->getContext()->setUser( $u );
  525. // Please reset throttle for successful logins, thanks!
  526. if ( $throttleCount ) {
  527. self::clearLoginThrottle( $this->mUsername );
  528. }
  529. if ( $isAutoCreated ) {
  530. // Must be run after $wgUser is set, for correct new user log
  531. wfRunHooks( 'AuthPluginAutoCreate', array( $u ) );
  532. }
  533. $retval = self::SUCCESS;
  534. }
  535. wfRunHooks( 'LoginAuthenticateAudit', array( $u, $this->mPassword, $retval ) );
  536. return $retval;
  537. }
  538. /**
  539. * Increment the login attempt throttle hit count for the (username,current IP)
  540. * tuple unless the throttle was already reached.
  541. * @param $username string The user name
  542. * @return Bool|Integer The integer hit count or True if it is already at the limit
  543. */
  544. public static function incLoginThrottle( $username ) {
  545. global $wgPasswordAttemptThrottle, $wgMemc, $wgRequest;
  546. $username = trim( $username ); // sanity
  547. $throttleCount = 0;
  548. if ( is_array( $wgPasswordAttemptThrottle ) ) {
  549. $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
  550. $count = $wgPasswordAttemptThrottle['count'];
  551. $period = $wgPasswordAttemptThrottle['seconds'];
  552. $throttleCount = $wgMemc->get( $throttleKey );
  553. if ( !$throttleCount ) {
  554. $wgMemc->add( $throttleKey, 1, $period ); // start counter
  555. } elseif ( $throttleCount < $count ) {
  556. $wgMemc->incr( $throttleKey );
  557. } elseif ( $throttleCount >= $count ) {
  558. return true;
  559. }
  560. }
  561. return $throttleCount;
  562. }
  563. /**
  564. * Clear the login attempt throttle hit count for the (username,current IP) tuple.
  565. * @param $username string The user name
  566. * @return void
  567. */
  568. public static function clearLoginThrottle( $username ) {
  569. global $wgMemc, $wgRequest;
  570. $username = trim( $username ); // sanity
  571. $throttleKey = wfMemcKey( 'password-throttle', $wgRequest->getIP(), md5( $username ) );
  572. $wgMemc->delete( $throttleKey );
  573. }
  574. /**
  575. * Attempt to automatically create a user on login. Only succeeds if there
  576. * is an external authentication method which allows it.
  577. *
  578. * @param $user User
  579. *
  580. * @return integer Status code
  581. */
  582. function attemptAutoCreate( $user ) {
  583. global $wgAuth, $wgAutocreatePolicy;
  584. if ( $this->getUser()->isBlockedFromCreateAccount() ) {
  585. wfDebug( __METHOD__ . ": user is blocked from account creation\n" );
  586. return self::CREATE_BLOCKED;
  587. }
  588. /**
  589. * If the external authentication plugin allows it, automatically cre-
  590. * ate a new account for users that are externally defined but have not
  591. * yet logged in.
  592. */
  593. if ( $this->mExtUser ) {
  594. # mExtUser is neither null nor false, so use the new ExternalAuth
  595. # system.
  596. if ( $wgAutocreatePolicy == 'never' ) {
  597. return self::NOT_EXISTS;
  598. }
  599. if ( !$this->mExtUser->authenticate( $this->mPassword ) ) {
  600. return self::WRONG_PLUGIN_PASS;
  601. }
  602. } else {
  603. # Old AuthPlugin.
  604. if ( !$wgAuth->autoCreate() ) {
  605. return self::NOT_EXISTS;
  606. }
  607. if ( !$wgAuth->userExists( $user->getName() ) ) {
  608. wfDebug( __METHOD__ . ": user does not exist\n" );
  609. return self::NOT_EXISTS;
  610. }
  611. if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
  612. wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" );
  613. return self::WRONG_PLUGIN_PASS;
  614. }
  615. }
  616. $abortError = '';
  617. if( !wfRunHooks( 'AbortAutoAccount', array( $user, &$abortError ) ) ) {
  618. // Hook point to add extra creation throttles and blocks
  619. wfDebug( "LoginForm::attemptAutoCreate: a hook blocked creation: $abortError\n" );
  620. $this->mAbortLoginErrorMsg = $abortError;
  621. return self::ABORTED;
  622. }
  623. wfDebug( __METHOD__ . ": creating account\n" );
  624. $this->initUser( $user, true );
  625. return self::SUCCESS;
  626. }
  627. function processLogin() {
  628. global $wgMemc, $wgLang;
  629. switch ( $this->authenticateUserData() ) {
  630. case self::SUCCESS:
  631. # We've verified now, update the real record
  632. $user = $this->getUser();
  633. if( (bool)$this->mRemember != (bool)$user->getOption( 'rememberpassword' ) ) {
  634. $user->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
  635. $user->saveSettings();
  636. } else {
  637. $user->invalidateCache();
  638. }
  639. $user->setCookies();
  640. self::clearLoginToken();
  641. // Reset the throttle
  642. $request = $this->getRequest();
  643. $key = wfMemcKey( 'password-throttle', $request->getIP(), md5( $this->mUsername ) );
  644. $wgMemc->delete( $key );
  645. if( $this->hasSessionCookie() || $this->mSkipCookieCheck ) {
  646. /* Replace the language object to provide user interface in
  647. * correct language immediately on this first page load.
  648. */
  649. $code = $request->getVal( 'uselang', $user->getOption( 'language' ) );
  650. $userLang = Language::factory( $code );
  651. $wgLang = $userLang;
  652. $this->getContext()->setLanguage( $userLang );
  653. $this->successfulLogin();
  654. } else {
  655. $this->cookieRedirectCheck( 'login' );
  656. }
  657. break;
  658. case self::NEED_TOKEN:
  659. $this->mainLoginForm( $this->msg( 'nocookiesforlogin' )->parse() );
  660. break;
  661. case self::WRONG_TOKEN:
  662. $this->mainLoginForm( $this->msg( 'sessionfailure' )->text() );
  663. break;
  664. case self::NO_NAME:
  665. case self::ILLEGAL:
  666. $this->mainLoginForm( $this->msg( 'noname' )->text() );
  667. break;
  668. case self::WRONG_PLUGIN_PASS:
  669. $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
  670. break;
  671. case self::NOT_EXISTS:
  672. if( $this->getUser()->isAllowed( 'createaccount' ) ) {
  673. $this->mainLoginForm( $this->msg( 'nosuchuser',
  674. wfEscapeWikiText( $this->mUsername ) )->parse() );
  675. } else {
  676. $this->mainLoginForm( $this->msg( 'nosuchusershort',
  677. wfEscapeWikiText( $this->mUsername ) )->text() );
  678. }
  679. break;
  680. case self::WRONG_PASS:
  681. $this->mainLoginForm( $this->msg( 'wrongpassword' )->text() );
  682. break;
  683. case self::EMPTY_PASS:
  684. $this->mainLoginForm( $this->msg( 'wrongpasswordempty' )->text() );
  685. break;
  686. case self::RESET_PASS:
  687. $this->resetLoginForm( $this->msg( 'resetpass_announce' )->text() );
  688. break;
  689. case self::CREATE_BLOCKED:
  690. $this->userBlockedMessage( $this->getUser()->mBlock );
  691. break;
  692. case self::THROTTLED:
  693. $this->mainLoginForm( $this->msg( 'login-throttled' )->text() );
  694. break;
  695. case self::USER_BLOCKED:
  696. $this->mainLoginForm( $this->msg( 'login-userblocked',
  697. $this->mUsername )->escaped() );
  698. break;
  699. case self::ABORTED:
  700. $this->mainLoginForm( $this->msg( $this->mAbortLoginErrorMsg )->text() );
  701. break;
  702. default:
  703. throw new MWException( 'Unhandled case value' );
  704. }
  705. }
  706. function resetLoginForm( $error ) {
  707. $this->getOutput()->addHTML( Xml::element('p', array( 'class' => 'error' ), $error ) );
  708. $reset = new SpecialChangePassword();
  709. $reset->setContext( $this->getContext() );
  710. $reset->execute( null );
  711. }
  712. /**
  713. * @param $u User object
  714. * @param $throttle Boolean
  715. * @param $emailTitle String: message name of email title
  716. * @param $emailText String: message name of email text
  717. * @return Status object
  718. */
  719. function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
  720. global $wgServer, $wgScript, $wgNewPasswordExpiry;
  721. if ( $u->getEmail() == '' ) {
  722. return Status::newFatal( 'noemail', $u->getName() );
  723. }
  724. $ip = $this->getRequest()->getIP();
  725. if( !$ip ) {
  726. return Status::newFatal( 'badipaddress' );
  727. }
  728. $currentUser = $this->getUser();
  729. wfRunHooks( 'User::mailPasswordInternal', array( &$currentUser, &$ip, &$u ) );
  730. $np = $u->randomPassword();
  731. $u->setNewpassword( $np, $throttle );
  732. $u->saveSettings();
  733. $userLanguage = $u->getOption( 'language' );
  734. $m = $this->msg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript,
  735. round( $wgNewPasswordExpiry / 86400 ) )->inLanguage( $userLanguage )->text();
  736. $result = $u->sendMail( $this->msg( $emailTitle )->inLanguage( $userLanguage )->text(), $m );
  737. return $result;
  738. }
  739. /**
  740. * Run any hooks registered for logins, then HTTP redirect to
  741. * $this->mReturnTo (or Main Page if that's undefined). Formerly we had a
  742. * nice message here, but that's really not as useful as just being sent to
  743. * wherever you logged in from. It should be clear that the action was
  744. * successful, given the lack of error messages plus the appearance of your
  745. * name in the upper right.
  746. *
  747. * @private
  748. */
  749. function successfulLogin() {
  750. # Run any hooks; display injected HTML if any, else redirect
  751. $currentUser = $this->getUser();
  752. $injected_html = '';
  753. wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
  754. if( $injected_html !== '' ) {
  755. $this->displaySuccessfulLogin( 'loginsuccess', $injected_html );
  756. } else {
  757. $this->executeReturnTo( 'successredirect' );
  758. }
  759. }
  760. /**
  761. * Run any hooks registered for logins, then display a message welcoming
  762. * the user.
  763. *
  764. * @private
  765. */
  766. function successfulCreation() {
  767. # Run any hooks; display injected HTML
  768. $currentUser = $this->getUser();
  769. $injected_html = '';
  770. $welcome_creation_msg = 'welcomecreation';
  771. wfRunHooks( 'UserLoginComplete', array( &$currentUser, &$injected_html ) );
  772. /**
  773. * Let any extensions change what message is shown.
  774. * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeWelcomeCreation
  775. * @since 1.18
  776. */
  777. wfRunHooks( 'BeforeWelcomeCreation', array( &$welcome_creation_msg, &$injected_html ) );
  778. $this->displaySuccessfulLogin( $welcome_creation_msg, $injected_html );
  779. }
  780. /**
  781. * Display a "login successful" page.
  782. * @param $msgname string
  783. * @param $injected_html string
  784. */
  785. private function displaySuccessfulLogin( $msgname, $injected_html ) {
  786. $out = $this->getOutput();
  787. $out->setPageTitle( $this->msg( 'loginsuccesstitle' ) );
  788. if( $msgname ){
  789. $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
  790. }
  791. $out->addHTML( $injected_html );
  792. $this->executeReturnTo( 'success' );
  793. }
  794. /**
  795. * Output a message that informs the user that they cannot create an account because
  796. * there is a block on them or their IP which prevents account creation. Note that
  797. * User::isBlockedFromCreateAccount(), which gets this block, ignores the 'hardblock'
  798. * setting on blocks (bug 13611).
  799. * @param $block Block the block causing this error
  800. */
  801. function userBlockedMessage( Block $block ) {
  802. # Let's be nice about this, it's likely that this feature will be used
  803. # for blocking large numbers of innocent people, e.g. range blocks on
  804. # schools. Don't blame it on the user. There's a small chance that it
  805. # really is the user's fault, i.e. the username is blocked and they
  806. # haven't bothered to log out before trying to create an account to
  807. # evade it, but we'll leave that to their guilty conscience to figure
  808. # out.
  809. $out = $this->getOutput();
  810. $out->setPageTitle( $this->msg( 'cantcreateaccounttitle' ) );
  811. $block_reason = $block->mReason;
  812. if ( strval( $block_reason ) === '' ) {
  813. $block_reason = $this->msg( 'blockednoreason' )->text();
  814. }
  815. $out->addWikiMsg(
  816. 'cantcreateaccount-text',
  817. $block->getTarget(),
  818. $block_reason,
  819. $block->getByName()
  820. );
  821. $this->executeReturnTo( 'error' );
  822. }
  823. /**
  824. * Add a "return to" link or redirect to it.
  825. *
  826. * @param $type string, one of the following:
  827. * - error: display a return to link ignoring $wgRedirectOnLogin
  828. * - success: display a return to link using $wgRedirectOnLogin if needed
  829. * - successredirect: send an HTTP redirect using $wgRedirectOnLogin if needed
  830. */
  831. private function executeReturnTo( $type ) {
  832. global $wgRedirectOnLogin, $wgSecureLogin;
  833. if ( $type != 'error' && $wgRedirectOnLogin !== null ) {
  834. $returnTo = $wgRedirectOnLogin;
  835. $returnToQuery = array();
  836. } else {
  837. $returnTo = $this->mReturnTo;
  838. $returnToQuery = wfCgiToArray( $this->mReturnToQuery );
  839. }
  840. $returnToTitle = Title::newFromText( $returnTo );
  841. if ( !$returnToTitle ) {
  842. $returnToTitle = Title::newMainPage();
  843. }
  844. if ( $type == 'successredirect' ) {
  845. $redirectUrl = $returnToTitle->getFullURL( $returnToQuery );
  846. if( $wgSecureLogin && !$this->mStickHTTPS ) {
  847. $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl );
  848. }
  849. $this->getOutput()->redirect( $redirectUrl );
  850. } else {
  851. $this->getOutput()->addReturnTo( $returnToTitle, $returnToQuery );
  852. }
  853. }
  854. /**
  855. * @private
  856. */
  857. function mainLoginForm( $msg, $msgtype = 'error' ) {
  858. global $wgEnableEmail, $wgEnableUserEmail;
  859. global $wgHiddenPrefs, $wgLoginLanguageSelector;
  860. global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
  861. global $wgSecureLogin, $wgPasswordResetRoutes;
  862. $titleObj = $this->getTitle();
  863. $user = $this->getUser();
  864. if ( $this->mType == 'signup' ) {
  865. // Block signup here if in readonly. Keeps user from
  866. // going through the process (filling out data, etc)
  867. // and being informed later.
  868. $permErrors = $titleObj->getUserPermissionsErrors( 'createaccount', $user, true );
  869. if ( count( $permErrors ) ) {
  870. throw new PermissionsError( 'createaccount', $permErrors );
  871. } elseif ( $user->isBlockedFromCreateAccount() ) {
  872. $this->userBlockedMessage( $user->isBlockedFromCreateAccount() );
  873. return;
  874. } elseif ( wfReadOnly() ) {
  875. throw new ReadOnlyError;
  876. }
  877. }
  878. if ( $this->mUsername == '' ) {
  879. if ( $user->isLoggedIn() ) {
  880. $this->mUsername = $user->getName();
  881. } else {
  882. $this->mUsername = $this->getRequest()->getCookie( 'UserName' );
  883. }
  884. }
  885. if ( $this->mType == 'signup' ) {
  886. $template = new UsercreateTemplate();
  887. $q = 'action=submitlogin&type=signup';
  888. $linkq = 'type=login';
  889. $linkmsg = 'gotaccount';
  890. } else {
  891. $template = new UserloginTemplate();
  892. $q = 'action=submitlogin&type=login';
  893. $linkq = 'type=signup';
  894. $linkmsg = 'nologin';
  895. }
  896. if ( $this->mReturnTo !== '' ) {
  897. $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
  898. if ( $this->mReturnToQuery !== '' ) {
  899. $returnto .= '&returntoquery=' .
  900. wfUrlencode( $this->mReturnToQuery );
  901. }
  902. $q .= $returnto;
  903. $linkq .= $returnto;
  904. }
  905. # Don't show a "create account" link if the user can't
  906. if( $this->showCreateOrLoginLink( $user ) ) {
  907. # Pass any language selection on to the mode switch link
  908. if( $wgLoginLanguageSelector && $this->mLanguage ) {
  909. $linkq .= '&uselang=' . $this->mLanguage;
  910. }
  911. $link = Html::element( 'a', array( 'href' => $titleObj->getLocalURL( $linkq ) ),
  912. $this->msg( $linkmsg . 'link' )->text() ); # Calling either 'gotaccountlink' or 'nologinlink'
  913. $template->set( 'link', $this->msg( $linkmsg )->rawParams( $link )->parse() );
  914. } else {
  915. $template->set( 'link', '' );
  916. }
  917. $resetLink = $this->mType == 'signup'
  918. ? null
  919. : is_array( $wgPasswordResetRoutes ) && in_array( true, array_values( $wgPasswordResetRoutes ) );
  920. $template->set( 'header', '' );
  921. $template->set( 'name', $this->mUsername );
  922. $template->set( 'password', $this->mPassword );
  923. $template->set( 'retype', $this->mRetype );
  924. $template->set( 'email', $this->mEmail );
  925. $template->set( 'realname', $this->mRealName );
  926. $template->set( 'domain', $this->mDomain );
  927. $template->set( 'reason', $this->mReason );
  928. $template->set( 'action', $titleObj->getLocalURL( $q ) );
  929. $template->set( 'message', $msg );
  930. $template->set( 'messagetype', $msgtype );
  931. $template->set( 'createemail', $wgEnableEmail && $user->isLoggedIn() );
  932. $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
  933. $template->set( 'useemail', $wgEnableEmail );
  934. $template->set( 'emailrequired', $wgEmailConfirmToEdit );
  935. $template->set( 'emailothers', $wgEnableUserEmail );
  936. $template->set( 'canreset', $wgAuth->allowPasswordChange() );
  937. $template->set( 'resetlink', $resetLink );
  938. $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
  939. $template->set( 'usereason', $user->isLoggedIn() );
  940. $template->set( 'remember', $user->getOption( 'rememberpassword' ) || $this->mRemember );
  941. $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
  942. $template->set( 'stickHTTPS', $this->mStickHTTPS );
  943. if ( $this->mType == 'signup' ) {
  944. if ( !self::getCreateaccountToken() ) {
  945. self::setCreateaccountToken();
  946. }
  947. $template->set( 'token', self::getCreateaccountToken() );
  948. } else {
  949. if ( !self::getLoginToken() ) {
  950. self::setLoginToken();
  951. }
  952. $template->set( 'token', self::getLoginToken() );
  953. }
  954. # Prepare language selection links as needed
  955. if( $wgLoginLanguageSelector ) {
  956. $template->set( 'languages', $this->makeLanguageSelector() );
  957. if( $this->mLanguage ) {
  958. $template->set( 'uselang', $this->mLanguage );
  959. }
  960. }
  961. // Use loginend-https for HTTPS requests if it's not blank, loginend otherwise
  962. // Ditto for signupend
  963. $usingHTTPS = WebRequest::detectProtocol() == 'https';
  964. $loginendHTTPS = $this->msg( 'loginend-https' );
  965. $signupendHTTPS = $this->msg( 'signupend-https' );
  966. if ( $usingHTTPS && !$loginendHTTPS->isBlank() ) {
  967. $template->set( 'loginend', $loginendHTTPS->parse() );
  968. } else {
  969. $template->set( 'loginend', $this->msg( 'loginend' )->parse() );
  970. }
  971. if ( $usingHTTPS && !$signupendHTTPS->isBlank() ) {
  972. $template->set( 'signupend', $signupendHTTPS->parse() );
  973. } else {
  974. $template->set( 'signupend', $this->msg( 'signupend' )->parse() );
  975. }
  976. // Give authentication and captcha plugins a chance to modify the form
  977. $wgAuth->modifyUITemplate( $template, $this->mType );
  978. if ( $this->mType == 'signup' ) {
  979. wfRunHooks( 'UserCreateForm', array( &$template ) );
  980. } else {
  981. wfRunHooks( 'UserLoginForm', array( &$template ) );
  982. }
  983. $out = $this->getOutput();
  984. $out->disallowUserJs(); // just in case...
  985. $out->addTemplate( $template );
  986. }
  987. /**
  988. * @private
  989. *
  990. * @param $user User
  991. *
  992. * @return Boolean
  993. */
  994. function showCreateOrLoginLink( &$user ) {
  995. if( $this->mType == 'signup' ) {
  996. return true;
  997. } elseif( $user->isAllowed( 'createaccount' ) ) {
  998. return true;
  999. } else {
  1000. return false;
  1001. }
  1002. }
  1003. /**
  1004. * Check if a session cookie is present.
  1005. *
  1006. * This will not pick up a cookie set during _this_ request, but is meant
  1007. * to ensure that the client is returning the cookie which was set on a
  1008. * previous pass through the system.
  1009. *
  1010. * @private
  1011. * @return bool
  1012. */
  1013. function hasSessionCookie() {
  1014. global $wgDisableCookieCheck;
  1015. return $wgDisableCookieCheck ? true : $this->getRequest()->checkSessionCookie();
  1016. }
  1017. /**
  1018. * Get the login token from the current session
  1019. * @return Mixed
  1020. */
  1021. public static function getLoginToken() {
  1022. global $wgRequest;
  1023. return $wgRequest->getSessionData( 'wsLoginToken' );
  1024. }
  1025. /**
  1026. * Randomly generate a new login token and attach it to the current session
  1027. */
  1028. public static function setLoginToken() {
  1029. global $wgRequest;
  1030. // Generate a token directly instead of using $user->editToken()
  1031. // because the latter reuses $_SESSION['wsEditToken']
  1032. $wgRequest->setSessionData( 'wsLoginToken', MWCryptRand::generateHex( 32 ) );
  1033. }
  1034. /**
  1035. * Remove any login token attached to the current session
  1036. */
  1037. public static function clearLoginToken() {
  1038. global $wgRequest;
  1039. $wgRequest->setSessionData( 'wsLoginToken', null );
  1040. }
  1041. /**
  1042. * Get the createaccount token from the current session
  1043. * @return Mixed
  1044. */
  1045. public static function getCreateaccountToken() {
  1046. global $wgRequest;
  1047. return $wgRequest->getSessionData( 'wsCreateaccountToken' );
  1048. }
  1049. /**
  1050. * Randomly generate a new createaccount token and attach it to the current session
  1051. */
  1052. public static function setCreateaccountToken() {
  1053. global $wgRequest;
  1054. $wgRequest->setSessionData( 'wsCreateaccountToken', MWCryptRand::generateHex( 32 ) );
  1055. }
  1056. /**
  1057. * Remove any createaccount token attached to the current session
  1058. */
  1059. public static function clearCreateaccountToken() {
  1060. global $wgRequest;
  1061. $wgRequest->setSessionData( 'wsCreateaccountToken', null );
  1062. }
  1063. /**
  1064. * @private
  1065. */
  1066. function cookieRedirectCheck( $type ) {
  1067. $titleObj = SpecialPage::getTitleFor( 'Userlogin' );
  1068. $query = array( 'wpCookieCheck' => $type );
  1069. if ( $this->mReturnTo !== '' ) {
  1070. $query['returnto'] = $this->mReturnTo;
  1071. $query['returntoquery'] = $this->mReturnToQuery;
  1072. }
  1073. $check = $titleObj->getFullURL( $query );
  1074. $this->getOutput()->redirect( $check );
  1075. }
  1076. /**
  1077. * @private
  1078. */
  1079. function onCookieRedirectCheck( $type ) {
  1080. if ( !$this->hasSessionCookie() ) {
  1081. if ( $type == 'new' ) {
  1082. $this->mainLoginForm( $this->msg( 'nocookiesnew' )->parse() );
  1083. } elseif ( $type == 'login' ) {
  1084. $this->mainLoginForm( $this->msg( 'nocookieslogin' )->parse() );
  1085. } else {
  1086. # shouldn't happen
  1087. $this->mainLoginForm( $this->msg( 'error' )->text() );
  1088. }
  1089. } else {
  1090. $this->successfulLogin();
  1091. }
  1092. }
  1093. /**
  1094. * @private
  1095. */
  1096. function throttleHit( $limit ) {
  1097. $this->mainLoginForm( $this->msg( 'acct_creation_throttle_hit' )->numParams( $limit )->parse() );
  1098. }
  1099. /**
  1100. * Produce a bar of links which allow the user to select another language
  1101. * during login/registration but retain "returnto"
  1102. *
  1103. * @return string
  1104. */
  1105. function makeLanguageSelector() {
  1106. $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
  1107. if( !$msg->isBlank() ) {
  1108. $langs = explode( "\n", $msg->text() );
  1109. $links = array();
  1110. foreach( $langs as $lang ) {
  1111. $lang = trim( $lang, '* ' );
  1112. $parts = explode( '|', $lang );
  1113. if ( count( $parts ) >= 2 ) {
  1114. $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
  1115. }
  1116. }
  1117. return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
  1118. $this->getLanguage()->pipeList( $links ) )->escaped() : '';
  1119. } else {
  1120. return '';
  1121. }
  1122. }
  1123. /**
  1124. * Create a language selector link for a particular language
  1125. * Links back to this page preserving type and returnto
  1126. *
  1127. * @param $text Link text
  1128. * @param $lang Language code
  1129. * @return string
  1130. */
  1131. function makeLanguageSelectorLink( $text, $lang ) {
  1132. if( $this->getLanguage()->getCode() == $lang ) {
  1133. // no link for currently used language
  1134. return htmlspecialchars( $text );
  1135. }
  1136. $query = array( 'uselang' => $lang );
  1137. if( $this->mType == 'signup' ) {
  1138. $query['type'] = 'signup';
  1139. }
  1140. if( $this->mReturnTo !== '' ) {
  1141. $query['returnto'] = $this->mReturnTo;
  1142. $query['returntoquery'] = $this->mReturnToQuery;
  1143. }
  1144. $attr = array();
  1145. $targetLanguage = Language::factory( $lang );
  1146. $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
  1147. return Linker::linkKnown(
  1148. $this->getTitle(),
  1149. htmlspecialchars( $text ),
  1150. $attr,
  1151. $query
  1152. );
  1153. }
  1154. }