PageRenderTime 60ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/User.php

https://bitbucket.org/kgrashad/thawrapedia
PHP | 3877 lines | 2355 code | 344 blank | 1178 comment | 387 complexity | b183c3069cc5bfb68be0a8863c3d0894 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Implements the User class for the %MediaWiki software.
  4. * @file
  5. */
  6. /**
  7. * \int Number of characters in user_token field.
  8. * @ingroup Constants
  9. */
  10. define( 'USER_TOKEN_LENGTH', 32 );
  11. /**
  12. * \int Serialized record version.
  13. * @ingroup Constants
  14. */
  15. define( 'MW_USER_VERSION', 8 );
  16. /**
  17. * \string Some punctuation to prevent editing from broken text-mangling proxies.
  18. * @ingroup Constants
  19. */
  20. define( 'EDIT_TOKEN_SUFFIX', '+\\' );
  21. /**
  22. * Thrown by User::setPassword() on error.
  23. * @ingroup Exception
  24. */
  25. class PasswordError extends MWException {
  26. // NOP
  27. }
  28. /**
  29. * The User object encapsulates all of the user-specific settings (user_id,
  30. * name, rights, password, email address, options, last login time). Client
  31. * classes use the getXXX() functions to access these fields. These functions
  32. * do all the work of determining whether the user is logged in,
  33. * whether the requested option can be satisfied from cookies or
  34. * whether a database query is needed. Most of the settings needed
  35. * for rendering normal pages are set in the cookie to minimize use
  36. * of the database.
  37. */
  38. class User {
  39. /**
  40. * Global constants made accessible as class constants so that autoloader
  41. * magic can be used.
  42. */
  43. const USER_TOKEN_LENGTH = USER_TOKEN_LENGTH;
  44. const MW_USER_VERSION = MW_USER_VERSION;
  45. const EDIT_TOKEN_SUFFIX = EDIT_TOKEN_SUFFIX;
  46. /**
  47. * \type{\arrayof{\string}} List of member variables which are saved to the
  48. * shared cache (memcached). Any operation which changes the
  49. * corresponding database fields must call a cache-clearing function.
  50. * @showinitializer
  51. */
  52. static $mCacheVars = array(
  53. // user table
  54. 'mId',
  55. 'mName',
  56. 'mRealName',
  57. 'mPassword',
  58. 'mNewpassword',
  59. 'mNewpassTime',
  60. 'mEmail',
  61. 'mTouched',
  62. 'mToken',
  63. 'mEmailAuthenticated',
  64. 'mEmailToken',
  65. 'mEmailTokenExpires',
  66. 'mRegistration',
  67. 'mEditCount',
  68. // user_group table
  69. 'mGroups',
  70. // user_properties table
  71. 'mOptionOverrides',
  72. );
  73. /**
  74. * \type{\arrayof{\string}} Core rights.
  75. * Each of these should have a corresponding message of the form
  76. * "right-$right".
  77. * @showinitializer
  78. */
  79. static $mCoreRights = array(
  80. 'apihighlimits',
  81. 'autoconfirmed',
  82. 'autopatrol',
  83. 'bigdelete',
  84. 'block',
  85. 'blockemail',
  86. 'bot',
  87. 'browsearchive',
  88. 'createaccount',
  89. 'createpage',
  90. 'createtalk',
  91. 'delete',
  92. 'deletedhistory',
  93. 'deletedtext',
  94. 'deleterevision',
  95. 'disableaccount',
  96. 'edit',
  97. 'editinterface',
  98. 'editusercssjs', #deprecated
  99. 'editusercss',
  100. 'edituserjs',
  101. 'hideuser',
  102. 'import',
  103. 'importupload',
  104. 'ipblock-exempt',
  105. 'markbotedits',
  106. 'mergehistory',
  107. 'minoredit',
  108. 'move',
  109. 'movefile',
  110. 'move-rootuserpages',
  111. 'move-subpages',
  112. 'nominornewtalk',
  113. 'noratelimit',
  114. 'override-export-depth',
  115. 'patrol',
  116. 'protect',
  117. 'proxyunbannable',
  118. 'purge',
  119. 'read',
  120. 'reupload',
  121. 'reupload-shared',
  122. 'rollback',
  123. 'selenium',
  124. 'sendemail',
  125. 'siteadmin',
  126. 'suppressionlog',
  127. 'suppressredirect',
  128. 'suppressrevision',
  129. 'trackback',
  130. 'unblockself',
  131. 'undelete',
  132. 'unwatchedpages',
  133. 'upload',
  134. 'upload_by_url',
  135. 'userrights',
  136. 'userrights-interwiki',
  137. 'writeapi',
  138. );
  139. /**
  140. * \string Cached results of getAllRights()
  141. */
  142. static $mAllRights = false;
  143. /** @name Cache variables */
  144. //@{
  145. var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime,
  146. $mEmail, $mTouched, $mToken, $mEmailAuthenticated,
  147. $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups, $mOptionOverrides;
  148. //@}
  149. /**
  150. * \bool Whether the cache variables have been loaded.
  151. */
  152. var $mDataLoaded, $mAuthLoaded, $mOptionsLoaded;
  153. /**
  154. * \string Initialization data source if mDataLoaded==false. May be one of:
  155. * - 'defaults' anonymous user initialised from class defaults
  156. * - 'name' initialise from mName
  157. * - 'id' initialise from mId
  158. * - 'session' log in from cookies or session if possible
  159. *
  160. * Use the User::newFrom*() family of functions to set this.
  161. */
  162. var $mFrom;
  163. /** @name Lazy-initialized variables, invalidated with clearInstanceCache */
  164. //@{
  165. var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights,
  166. $mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally,
  167. $mLocked, $mHideName, $mOptions;
  168. //@}
  169. static $idCacheByName = array();
  170. /**
  171. * Lightweight constructor for an anonymous user.
  172. * Use the User::newFrom* factory functions for other kinds of users.
  173. *
  174. * @see newFromName()
  175. * @see newFromId()
  176. * @see newFromConfirmationCode()
  177. * @see newFromSession()
  178. * @see newFromRow()
  179. */
  180. function __construct() {
  181. $this->clearInstanceCache( 'defaults' );
  182. }
  183. /**
  184. * Load the user table data for this object from the source given by mFrom.
  185. */
  186. function load() {
  187. if ( $this->mDataLoaded ) {
  188. return;
  189. }
  190. wfProfileIn( __METHOD__ );
  191. # Set it now to avoid infinite recursion in accessors
  192. $this->mDataLoaded = true;
  193. switch ( $this->mFrom ) {
  194. case 'defaults':
  195. $this->loadDefaults();
  196. break;
  197. case 'name':
  198. $this->mId = self::idFromName( $this->mName );
  199. if ( !$this->mId ) {
  200. # Nonexistent user placeholder object
  201. $this->loadDefaults( $this->mName );
  202. } else {
  203. $this->loadFromId();
  204. }
  205. break;
  206. case 'id':
  207. $this->loadFromId();
  208. break;
  209. case 'session':
  210. $this->loadFromSession();
  211. wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) );
  212. break;
  213. default:
  214. throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
  215. }
  216. wfProfileOut( __METHOD__ );
  217. }
  218. /**
  219. * Load user table data, given mId has already been set.
  220. * @return \bool false if the ID does not exist, true otherwise
  221. * @private
  222. */
  223. function loadFromId() {
  224. global $wgMemc;
  225. if ( $this->mId == 0 ) {
  226. $this->loadDefaults();
  227. return false;
  228. }
  229. # Try cache
  230. $key = wfMemcKey( 'user', 'id', $this->mId );
  231. $data = $wgMemc->get( $key );
  232. if ( !is_array( $data ) || $data['mVersion'] < MW_USER_VERSION ) {
  233. # Object is expired, load from DB
  234. $data = false;
  235. }
  236. if ( !$data ) {
  237. wfDebug( "User: cache miss for user {$this->mId}\n" );
  238. # Load from DB
  239. if ( !$this->loadFromDatabase() ) {
  240. # Can't load from ID, user is anonymous
  241. return false;
  242. }
  243. $this->saveToCache();
  244. } else {
  245. wfDebug( "User: got user {$this->mId} from cache\n" );
  246. # Restore from cache
  247. foreach ( self::$mCacheVars as $name ) {
  248. $this->$name = $data[$name];
  249. }
  250. }
  251. return true;
  252. }
  253. /**
  254. * Save user data to the shared cache
  255. */
  256. function saveToCache() {
  257. $this->load();
  258. $this->loadGroups();
  259. $this->loadOptions();
  260. if ( $this->isAnon() ) {
  261. // Anonymous users are uncached
  262. return;
  263. }
  264. $data = array();
  265. foreach ( self::$mCacheVars as $name ) {
  266. $data[$name] = $this->$name;
  267. }
  268. $data['mVersion'] = MW_USER_VERSION;
  269. $key = wfMemcKey( 'user', 'id', $this->mId );
  270. global $wgMemc;
  271. $wgMemc->set( $key, $data );
  272. }
  273. /** @name newFrom*() static factory methods */
  274. //@{
  275. /**
  276. * Static factory method for creation from username.
  277. *
  278. * This is slightly less efficient than newFromId(), so use newFromId() if
  279. * you have both an ID and a name handy.
  280. *
  281. * @param $name \string Username, validated by Title::newFromText()
  282. * @param $validate \mixed Validate username. Takes the same parameters as
  283. * User::getCanonicalName(), except that true is accepted as an alias
  284. * for 'valid', for BC.
  285. *
  286. * @return User The User object, or false if the username is invalid
  287. * (e.g. if it contains illegal characters or is an IP address). If the
  288. * username is not present in the database, the result will be a user object
  289. * with a name, zero user ID and default settings.
  290. */
  291. static function newFromName( $name, $validate = 'valid' ) {
  292. if ( $validate === true ) {
  293. $validate = 'valid';
  294. }
  295. $name = self::getCanonicalName( $name, $validate );
  296. if ( $name === false ) {
  297. return false;
  298. } else {
  299. # Create unloaded user object
  300. $u = new User;
  301. $u->mName = $name;
  302. $u->mFrom = 'name';
  303. return $u;
  304. }
  305. }
  306. /**
  307. * Static factory method for creation from a given user ID.
  308. *
  309. * @param $id \int Valid user ID
  310. * @return \type{User} The corresponding User object
  311. */
  312. static function newFromId( $id ) {
  313. $u = new User;
  314. $u->mId = $id;
  315. $u->mFrom = 'id';
  316. return $u;
  317. }
  318. /**
  319. * Factory method to fetch whichever user has a given email confirmation code.
  320. * This code is generated when an account is created or its e-mail address
  321. * has changed.
  322. *
  323. * If the code is invalid or has expired, returns NULL.
  324. *
  325. * @param $code \string Confirmation code
  326. * @return \type{User}
  327. */
  328. static function newFromConfirmationCode( $code ) {
  329. $dbr = wfGetDB( DB_SLAVE );
  330. $id = $dbr->selectField( 'user', 'user_id', array(
  331. 'user_email_token' => md5( $code ),
  332. 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
  333. ) );
  334. if( $id !== false ) {
  335. return User::newFromId( $id );
  336. } else {
  337. return null;
  338. }
  339. }
  340. /**
  341. * Create a new user object using data from session or cookies. If the
  342. * login credentials are invalid, the result is an anonymous user.
  343. *
  344. * @return \type{User}
  345. */
  346. static function newFromSession() {
  347. $user = new User;
  348. $user->mFrom = 'session';
  349. return $user;
  350. }
  351. /**
  352. * Create a new user object from a user row.
  353. * The row should have all fields from the user table in it.
  354. * @param $row array A row from the user table
  355. * @return \type{User}
  356. */
  357. static function newFromRow( $row ) {
  358. $user = new User;
  359. $user->loadFromRow( $row );
  360. return $user;
  361. }
  362. //@}
  363. /**
  364. * Get the username corresponding to a given user ID
  365. * @param $id \int User ID
  366. * @return \string The corresponding username
  367. */
  368. static function whoIs( $id ) {
  369. $dbr = wfGetDB( DB_SLAVE );
  370. return $dbr->selectField( 'user', 'user_name', array( 'user_id' => $id ), __METHOD__ );
  371. }
  372. /**
  373. * Get the real name of a user given their user ID
  374. *
  375. * @param $id \int User ID
  376. * @return \string The corresponding user's real name
  377. */
  378. static function whoIsReal( $id ) {
  379. $dbr = wfGetDB( DB_SLAVE );
  380. return $dbr->selectField( 'user', 'user_real_name', array( 'user_id' => $id ), __METHOD__ );
  381. }
  382. /**
  383. * Get database id given a user name
  384. * @param $name \string Username
  385. * @return \types{\int,\null} The corresponding user's ID, or null if user is nonexistent
  386. */
  387. static function idFromName( $name ) {
  388. $nt = Title::makeTitleSafe( NS_USER, $name );
  389. if( is_null( $nt ) ) {
  390. # Illegal name
  391. return null;
  392. }
  393. if ( isset( self::$idCacheByName[$name] ) ) {
  394. return self::$idCacheByName[$name];
  395. }
  396. $dbr = wfGetDB( DB_SLAVE );
  397. $s = $dbr->selectRow( 'user', array( 'user_id' ), array( 'user_name' => $nt->getText() ), __METHOD__ );
  398. if ( $s === false ) {
  399. $result = null;
  400. } else {
  401. $result = $s->user_id;
  402. }
  403. self::$idCacheByName[$name] = $result;
  404. if ( count( self::$idCacheByName ) > 1000 ) {
  405. self::$idCacheByName = array();
  406. }
  407. return $result;
  408. }
  409. /**
  410. * Does the string match an anonymous IPv4 address?
  411. *
  412. * This function exists for username validation, in order to reject
  413. * usernames which are similar in form to IP addresses. Strings such
  414. * as 300.300.300.300 will return true because it looks like an IP
  415. * address, despite not being strictly valid.
  416. *
  417. * We match \d{1,3}\.\d{1,3}\.\d{1,3}\.xxx as an anonymous IP
  418. * address because the usemod software would "cloak" anonymous IP
  419. * addresses like this, if we allowed accounts like this to be created
  420. * new users could get the old edits of these anonymous users.
  421. *
  422. * @param $name \string String to match
  423. * @return \bool True or false
  424. */
  425. static function isIP( $name ) {
  426. return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name) || IP::isIPv6($name);
  427. }
  428. /**
  429. * Is the input a valid username?
  430. *
  431. * Checks if the input is a valid username, we don't want an empty string,
  432. * an IP address, anything that containins slashes (would mess up subpages),
  433. * is longer than the maximum allowed username size or doesn't begin with
  434. * a capital letter.
  435. *
  436. * @param $name \string String to match
  437. * @return \bool True or false
  438. */
  439. static function isValidUserName( $name ) {
  440. global $wgContLang, $wgMaxNameChars;
  441. if ( $name == ''
  442. || User::isIP( $name )
  443. || strpos( $name, '/' ) !== false
  444. || strlen( $name ) > $wgMaxNameChars
  445. || $name != $wgContLang->ucfirst( $name ) ) {
  446. wfDebugLog( 'username', __METHOD__ .
  447. ": '$name' invalid due to empty, IP, slash, length, or lowercase" );
  448. return false;
  449. }
  450. // Ensure that the name can't be misresolved as a different title,
  451. // such as with extra namespace keys at the start.
  452. $parsed = Title::newFromText( $name );
  453. if( is_null( $parsed )
  454. || $parsed->getNamespace()
  455. || strcmp( $name, $parsed->getPrefixedText() ) ) {
  456. wfDebugLog( 'username', __METHOD__ .
  457. ": '$name' invalid due to ambiguous prefixes" );
  458. return false;
  459. }
  460. // Check an additional blacklist of troublemaker characters.
  461. // Should these be merged into the title char list?
  462. $unicodeBlacklist = '/[' .
  463. '\x{0080}-\x{009f}' . # iso-8859-1 control chars
  464. '\x{00a0}' . # non-breaking space
  465. '\x{2000}-\x{200f}' . # various whitespace
  466. '\x{2028}-\x{202f}' . # breaks and control chars
  467. '\x{3000}' . # ideographic space
  468. '\x{e000}-\x{f8ff}' . # private use
  469. ']/u';
  470. if( preg_match( $unicodeBlacklist, $name ) ) {
  471. wfDebugLog( 'username', __METHOD__ .
  472. ": '$name' invalid due to blacklisted characters" );
  473. return false;
  474. }
  475. return true;
  476. }
  477. /**
  478. * Usernames which fail to pass this function will be blocked
  479. * from user login and new account registrations, but may be used
  480. * internally by batch processes.
  481. *
  482. * If an account already exists in this form, login will be blocked
  483. * by a failure to pass this function.
  484. *
  485. * @param $name \string String to match
  486. * @return \bool True or false
  487. */
  488. static function isUsableName( $name ) {
  489. global $wgReservedUsernames;
  490. // Must be a valid username, obviously ;)
  491. if ( !self::isValidUserName( $name ) ) {
  492. return false;
  493. }
  494. static $reservedUsernames = false;
  495. if ( !$reservedUsernames ) {
  496. $reservedUsernames = $wgReservedUsernames;
  497. wfRunHooks( 'UserGetReservedNames', array( &$reservedUsernames ) );
  498. }
  499. // Certain names may be reserved for batch processes.
  500. foreach ( $reservedUsernames as $reserved ) {
  501. if ( substr( $reserved, 0, 4 ) == 'msg:' ) {
  502. $reserved = wfMsgForContent( substr( $reserved, 4 ) );
  503. }
  504. if ( $reserved == $name ) {
  505. return false;
  506. }
  507. }
  508. return true;
  509. }
  510. /**
  511. * Usernames which fail to pass this function will be blocked
  512. * from new account registrations, but may be used internally
  513. * either by batch processes or by user accounts which have
  514. * already been created.
  515. *
  516. * Additional blacklisting may be added here rather than in
  517. * isValidUserName() to avoid disrupting existing accounts.
  518. *
  519. * @param $name \string String to match
  520. * @return \bool True or false
  521. */
  522. static function isCreatableName( $name ) {
  523. global $wgInvalidUsernameCharacters;
  524. // Ensure that the username isn't longer than 235 bytes, so that
  525. // (at least for the builtin skins) user javascript and css files
  526. // will work. (bug 23080)
  527. if( strlen( $name ) > 235 ) {
  528. wfDebugLog( 'username', __METHOD__ .
  529. ": '$name' invalid due to length" );
  530. return false;
  531. }
  532. // Preg yells if you try to give it an empty string
  533. if( $wgInvalidUsernameCharacters ) {
  534. if( preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ) ) {
  535. wfDebugLog( 'username', __METHOD__ .
  536. ": '$name' invalid due to wgInvalidUsernameCharacters" );
  537. return false;
  538. }
  539. }
  540. return self::isUsableName( $name );
  541. }
  542. /**
  543. * Is the input a valid password for this user?
  544. *
  545. * @param $password String Desired password
  546. * @return bool True or false
  547. */
  548. function isValidPassword( $password ) {
  549. //simple boolean wrapper for getPasswordValidity
  550. return $this->getPasswordValidity( $password ) === true;
  551. }
  552. /**
  553. * Given unvalidated password input, return error message on failure.
  554. *
  555. * @param $password String Desired password
  556. * @return mixed: true on success, string of error message on failure
  557. */
  558. function getPasswordValidity( $password ) {
  559. global $wgMinimalPasswordLength, $wgContLang;
  560. static $blockedLogins = array(
  561. 'Useruser' => 'Passpass', 'Useruser1' => 'Passpass1', # r75589
  562. 'Apitestsysop' => 'testpass', 'Apitestuser' => 'testpass' # r75605
  563. );
  564. $result = false; //init $result to false for the internal checks
  565. if( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) )
  566. return $result;
  567. if ( $result === false ) {
  568. if( strlen( $password ) < $wgMinimalPasswordLength ) {
  569. return 'passwordtooshort';
  570. } elseif ( $wgContLang->lc( $password ) == $wgContLang->lc( $this->mName ) ) {
  571. return 'password-name-match';
  572. } elseif ( isset( $blockedLogins[ $this->getName() ] ) && $password == $blockedLogins[ $this->getName() ] ) {
  573. return 'password-login-forbidden';
  574. } else {
  575. //it seems weird returning true here, but this is because of the
  576. //initialization of $result to false above. If the hook is never run or it
  577. //doesn't modify $result, then we will likely get down into this if with
  578. //a valid password.
  579. return true;
  580. }
  581. } elseif( $result === true ) {
  582. return true;
  583. } else {
  584. return $result; //the isValidPassword hook set a string $result and returned true
  585. }
  586. }
  587. /**
  588. * Does a string look like an e-mail address?
  589. *
  590. * This validates an email address using an HTML5 specification found at:
  591. * http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
  592. * Which as of 2011-01-24 says:
  593. *
  594. * A valid e-mail address is a string that matches the ABNF production
  595. * 1*( atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined
  596. * in RFC 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section
  597. * 3.5.
  598. *
  599. * This function is an implementation of the specification as requested in
  600. * bug 22449.
  601. *
  602. * Client-side forms will use the same standard validation rules via JS or
  603. * HTML 5 validation; additional restrictions can be enforced server-side
  604. * by extensions via the 'isValidEmailAddr' hook.
  605. *
  606. * Note that this validation doesn't 100% match RFC 2822, but is believed
  607. * to be liberal enough for wide use. Some invalid addresses will still
  608. * pass validation here.
  609. *
  610. * @param $addr String E-mail address
  611. * @return Bool
  612. */
  613. public static function isValidEmailAddr( $addr ) {
  614. $result = null;
  615. if( !wfRunHooks( 'isValidEmailAddr', array( $addr, &$result ) ) ) {
  616. return $result;
  617. }
  618. // Please note strings below are enclosed in brackets [], this make the
  619. // hyphen "-" a range indicator. Hence it is double backslashed below.
  620. // See bug 26948
  621. $rfc5322_atext = "a-z0-9!#$%&'*+\\-\/=?^_`{|}~" ;
  622. $rfc1034_ldh_str = "a-z0-9\\-" ;
  623. $HTML5_email_regexp = "/
  624. ^ # start of string
  625. [$rfc5322_atext\\.]+ # user part which is liberal :p
  626. @ # 'apostrophe'
  627. [$rfc1034_ldh_str]+ # First domain part
  628. (\\.[$rfc1034_ldh_str]+)* # Following part prefixed with a dot
  629. $ # End of string
  630. /ix" ; // case Insensitive, eXtended
  631. return (bool) preg_match( $HTML5_email_regexp, $addr );
  632. }
  633. /**
  634. * Given unvalidated user input, return a canonical username, or false if
  635. * the username is invalid.
  636. * @param $name \string User input
  637. * @param $validate \types{\string,\bool} Type of validation to use:
  638. * - false No validation
  639. * - 'valid' Valid for batch processes
  640. * - 'usable' Valid for batch processes and login
  641. * - 'creatable' Valid for batch processes, login and account creation
  642. */
  643. static function getCanonicalName( $name, $validate = 'valid' ) {
  644. # Force usernames to capital
  645. global $wgContLang;
  646. $name = $wgContLang->ucfirst( $name );
  647. # Reject names containing '#'; these will be cleaned up
  648. # with title normalisation, but then it's too late to
  649. # check elsewhere
  650. if( strpos( $name, '#' ) !== false )
  651. return false;
  652. # Clean up name according to title rules
  653. $t = ( $validate === 'valid' ) ?
  654. Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name );
  655. # Check for invalid titles
  656. if( is_null( $t ) ) {
  657. return false;
  658. }
  659. # Reject various classes of invalid names
  660. global $wgAuth;
  661. $name = $wgAuth->getCanonicalName( $t->getText() );
  662. switch ( $validate ) {
  663. case false:
  664. break;
  665. case 'valid':
  666. if ( !User::isValidUserName( $name ) ) {
  667. $name = false;
  668. }
  669. break;
  670. case 'usable':
  671. if ( !User::isUsableName( $name ) ) {
  672. $name = false;
  673. }
  674. break;
  675. case 'creatable':
  676. if ( !User::isCreatableName( $name ) ) {
  677. $name = false;
  678. }
  679. break;
  680. default:
  681. throw new MWException( 'Invalid parameter value for $validate in ' . __METHOD__ );
  682. }
  683. return $name;
  684. }
  685. /**
  686. * Count the number of edits of a user
  687. * @todo It should not be static and some day should be merged as proper member function / deprecated -- domas
  688. *
  689. * @param $uid \int User ID to check
  690. * @return \int The user's edit count
  691. */
  692. static function edits( $uid ) {
  693. wfProfileIn( __METHOD__ );
  694. $dbr = wfGetDB( DB_SLAVE );
  695. // check if the user_editcount field has been initialized
  696. $field = $dbr->selectField(
  697. 'user', 'user_editcount',
  698. array( 'user_id' => $uid ),
  699. __METHOD__
  700. );
  701. if( $field === null ) { // it has not been initialized. do so.
  702. $dbw = wfGetDB( DB_MASTER );
  703. $count = $dbr->selectField(
  704. 'revision', 'count(*)',
  705. array( 'rev_user' => $uid ),
  706. __METHOD__
  707. );
  708. $dbw->update(
  709. 'user',
  710. array( 'user_editcount' => $count ),
  711. array( 'user_id' => $uid ),
  712. __METHOD__
  713. );
  714. } else {
  715. $count = $field;
  716. }
  717. wfProfileOut( __METHOD__ );
  718. return $count;
  719. }
  720. /**
  721. * Return a random password. Sourced from mt_rand, so it's not particularly secure.
  722. * @todo hash random numbers to improve security, like generateToken()
  723. *
  724. * @return \string New random password
  725. */
  726. static function randomPassword() {
  727. global $wgMinimalPasswordLength;
  728. $pwchars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz';
  729. $l = strlen( $pwchars ) - 1;
  730. $pwlength = max( 7, $wgMinimalPasswordLength );
  731. $digit = mt_rand( 0, $pwlength - 1 );
  732. $np = '';
  733. for ( $i = 0; $i < $pwlength; $i++ ) {
  734. $np .= $i == $digit ? chr( mt_rand( 48, 57 ) ) : $pwchars{ mt_rand( 0, $l ) };
  735. }
  736. return $np;
  737. }
  738. /**
  739. * Set cached properties to default.
  740. *
  741. * @note This no longer clears uncached lazy-initialised properties;
  742. * the constructor does that instead.
  743. * @private
  744. */
  745. function loadDefaults( $name = false ) {
  746. wfProfileIn( __METHOD__ );
  747. global $wgRequest;
  748. $this->mId = 0;
  749. $this->mName = $name;
  750. $this->mRealName = '';
  751. $this->mPassword = $this->mNewpassword = '';
  752. $this->mNewpassTime = null;
  753. $this->mEmail = '';
  754. $this->mOptionOverrides = null;
  755. $this->mOptionsLoaded = false;
  756. if( $wgRequest->getCookie( 'LoggedOut' ) !== null ) {
  757. $this->mTouched = wfTimestamp( TS_MW, $wgRequest->getCookie( 'LoggedOut' ) );
  758. } else {
  759. $this->mTouched = '0'; # Allow any pages to be cached
  760. }
  761. $this->setToken(); # Random
  762. $this->mEmailAuthenticated = null;
  763. $this->mEmailToken = '';
  764. $this->mEmailTokenExpires = null;
  765. $this->mRegistration = wfTimestamp( TS_MW );
  766. $this->mGroups = array();
  767. wfRunHooks( 'UserLoadDefaults', array( $this, $name ) );
  768. wfProfileOut( __METHOD__ );
  769. }
  770. /**
  771. * @deprecated Use wfSetupSession().
  772. */
  773. function SetupSession() {
  774. wfDeprecated( __METHOD__ );
  775. wfSetupSession();
  776. }
  777. /**
  778. * Load user data from the session or login cookie. If there are no valid
  779. * credentials, initialises the user as an anonymous user.
  780. * @return \bool True if the user is logged in, false otherwise.
  781. */
  782. private function loadFromSession() {
  783. global $wgRequest, $wgExternalAuthType, $wgAutocreatePolicy;
  784. $result = null;
  785. wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) );
  786. if ( $result !== null ) {
  787. return $result;
  788. }
  789. if ( $wgExternalAuthType && $wgAutocreatePolicy == 'view' ) {
  790. $extUser = ExternalUser::newFromCookie();
  791. if ( $extUser ) {
  792. # TODO: Automatically create the user here (or probably a bit
  793. # lower down, in fact)
  794. }
  795. }
  796. if ( $wgRequest->getCookie( 'UserID' ) !== null ) {
  797. $sId = intval( $wgRequest->getCookie( 'UserID' ) );
  798. if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) {
  799. $this->loadDefaults(); // Possible collision!
  800. wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and
  801. cookie user ID ($sId) don't match!" );
  802. return false;
  803. }
  804. $_SESSION['wsUserID'] = $sId;
  805. } else if ( isset( $_SESSION['wsUserID'] ) ) {
  806. if ( $_SESSION['wsUserID'] != 0 ) {
  807. $sId = $_SESSION['wsUserID'];
  808. } else {
  809. $this->loadDefaults();
  810. return false;
  811. }
  812. } else {
  813. $this->loadDefaults();
  814. return false;
  815. }
  816. if ( isset( $_SESSION['wsUserName'] ) ) {
  817. $sName = $_SESSION['wsUserName'];
  818. } else if ( $wgRequest->getCookie('UserName') !== null ) {
  819. $sName = $wgRequest->getCookie('UserName');
  820. $_SESSION['wsUserName'] = $sName;
  821. } else {
  822. $this->loadDefaults();
  823. return false;
  824. }
  825. $proposedUser = User::newFromId( $sId );
  826. if ( !$proposedUser->isLoggedIn() ) {
  827. # Not a valid ID
  828. $this->loadDefaults();
  829. return false;
  830. }
  831. global $wgBlockDisablesLogin;
  832. if( $wgBlockDisablesLogin && $proposedUser->isBlocked() ) {
  833. # User blocked and we've disabled blocked user logins
  834. $this->loadDefaults();
  835. return false;
  836. }
  837. if ( isset( $_SESSION['wsToken'] ) ) {
  838. $passwordCorrect = $proposedUser->getToken() === $_SESSION['wsToken'];
  839. $from = 'session';
  840. } else if ( $wgRequest->getCookie( 'Token' ) !== null ) {
  841. $passwordCorrect = $proposedUser->getToken() === $wgRequest->getCookie( 'Token' );
  842. $from = 'cookie';
  843. } else {
  844. # No session or persistent login cookie
  845. $this->loadDefaults();
  846. return false;
  847. }
  848. if ( ( $sName === $proposedUser->getName() ) && $passwordCorrect ) {
  849. $this->loadFromUserObject( $proposedUser );
  850. $_SESSION['wsToken'] = $this->mToken;
  851. wfDebug( "User: logged in from $from\n" );
  852. return true;
  853. } else {
  854. # Invalid credentials
  855. wfDebug( "User: can't log in from $from, invalid credentials\n" );
  856. $this->loadDefaults();
  857. return false;
  858. }
  859. }
  860. /**
  861. * Load the data for this user object from another user object.
  862. */
  863. protected function loadFromUserObject( $user ) {
  864. $user->load();
  865. $user->loadGroups();
  866. $user->loadOptions();
  867. foreach ( self::$mCacheVars as $var ) {
  868. $this->$var = $user->$var;
  869. }
  870. }
  871. /**
  872. * Load user and user_group data from the database.
  873. * $this::mId must be set, this is how the user is identified.
  874. *
  875. * @return \bool True if the user exists, false if the user is anonymous
  876. * @private
  877. */
  878. function loadFromDatabase() {
  879. # Paranoia
  880. $this->mId = intval( $this->mId );
  881. /** Anonymous user */
  882. if( !$this->mId ) {
  883. $this->loadDefaults();
  884. return false;
  885. }
  886. $dbr = wfGetDB( DB_MASTER );
  887. $s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ );
  888. wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
  889. if ( $s !== false ) {
  890. # Initialise user table data
  891. $this->loadFromRow( $s );
  892. $this->mGroups = null; // deferred
  893. $this->getEditCount(); // revalidation for nulls
  894. return true;
  895. } else {
  896. # Invalid user_id
  897. $this->mId = 0;
  898. $this->loadDefaults();
  899. return false;
  900. }
  901. }
  902. /**
  903. * Initialize this object from a row from the user table.
  904. *
  905. * @param $row \type{\arrayof{\mixed}} Row from the user table to load.
  906. */
  907. function loadFromRow( $row ) {
  908. $this->mDataLoaded = true;
  909. if ( isset( $row->user_id ) ) {
  910. $this->mId = intval( $row->user_id );
  911. }
  912. $this->mName = $row->user_name;
  913. $this->mRealName = $row->user_real_name;
  914. $this->mPassword = $row->user_password;
  915. $this->mNewpassword = $row->user_newpassword;
  916. $this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time );
  917. $this->mEmail = $row->user_email;
  918. $this->decodeOptions( $row->user_options );
  919. $this->mTouched = wfTimestamp(TS_MW,$row->user_touched);
  920. $this->mToken = $row->user_token;
  921. $this->mEmailAuthenticated = wfTimestampOrNull( TS_MW, $row->user_email_authenticated );
  922. $this->mEmailToken = $row->user_email_token;
  923. $this->mEmailTokenExpires = wfTimestampOrNull( TS_MW, $row->user_email_token_expires );
  924. $this->mRegistration = wfTimestampOrNull( TS_MW, $row->user_registration );
  925. $this->mEditCount = $row->user_editcount;
  926. }
  927. /**
  928. * Load the groups from the database if they aren't already loaded.
  929. * @private
  930. */
  931. function loadGroups() {
  932. if ( is_null( $this->mGroups ) ) {
  933. $dbr = wfGetDB( DB_MASTER );
  934. $res = $dbr->select( 'user_groups',
  935. array( 'ug_group' ),
  936. array( 'ug_user' => $this->mId ),
  937. __METHOD__ );
  938. $this->mGroups = array();
  939. foreach ( $res as $row ) {
  940. $this->mGroups[] = $row->ug_group;
  941. }
  942. }
  943. }
  944. /**
  945. * Clear various cached data stored in this object.
  946. * @param $reloadFrom \string Reload user and user_groups table data from a
  947. * given source. May be "name", "id", "defaults", "session", or false for
  948. * no reload.
  949. */
  950. function clearInstanceCache( $reloadFrom = false ) {
  951. $this->mNewtalk = -1;
  952. $this->mDatePreference = null;
  953. $this->mBlockedby = -1; # Unset
  954. $this->mHash = false;
  955. $this->mSkin = null;
  956. $this->mRights = null;
  957. $this->mEffectiveGroups = null;
  958. $this->mOptions = null;
  959. if ( $reloadFrom ) {
  960. $this->mDataLoaded = false;
  961. $this->mFrom = $reloadFrom;
  962. }
  963. }
  964. /**
  965. * Combine the language default options with any site-specific options
  966. * and add the default language variants.
  967. *
  968. * @return \type{\arrayof{\string}} Array of options
  969. */
  970. static function getDefaultOptions() {
  971. global $wgNamespacesToBeSearchedDefault;
  972. /**
  973. * Site defaults will override the global/language defaults
  974. */
  975. global $wgDefaultUserOptions, $wgContLang, $wgDefaultSkin;
  976. $defOpt = $wgDefaultUserOptions + $wgContLang->getDefaultUserOptionOverrides();
  977. /**
  978. * default language setting
  979. */
  980. $variant = $wgContLang->getDefaultVariant();
  981. $defOpt['variant'] = $variant;
  982. $defOpt['language'] = $variant;
  983. foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
  984. $defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
  985. }
  986. $defOpt['skin'] = $wgDefaultSkin;
  987. return $defOpt;
  988. }
  989. /**
  990. * Get a given default option value.
  991. *
  992. * @param $opt \string Name of option to retrieve
  993. * @return \string Default option value
  994. */
  995. public static function getDefaultOption( $opt ) {
  996. $defOpts = self::getDefaultOptions();
  997. if( isset( $defOpts[$opt] ) ) {
  998. return $defOpts[$opt];
  999. } else {
  1000. return null;
  1001. }
  1002. }
  1003. /**
  1004. * Get blocking information
  1005. * @private
  1006. * @param $bFromSlave \bool Whether to check the slave database first. To
  1007. * improve performance, non-critical checks are done
  1008. * against slaves. Check when actually saving should be
  1009. * done against master.
  1010. */
  1011. function getBlockedStatus( $bFromSlave = true ) {
  1012. global $wgProxyWhitelist, $wgUser;
  1013. if ( -1 != $this->mBlockedby ) {
  1014. wfDebug( "User::getBlockedStatus: already loaded.\n" );
  1015. return;
  1016. }
  1017. wfProfileIn( __METHOD__ );
  1018. wfDebug( __METHOD__.": checking...\n" );
  1019. // Initialize data...
  1020. // Otherwise something ends up stomping on $this->mBlockedby when
  1021. // things get lazy-loaded later, causing false positive block hits
  1022. // due to -1 !== 0. Probably session-related... Nothing should be
  1023. // overwriting mBlockedby, surely?
  1024. $this->load();
  1025. $this->mBlockedby = 0;
  1026. $this->mHideName = 0;
  1027. $this->mAllowUsertalk = 0;
  1028. # Check if we are looking at an IP or a logged-in user
  1029. if ( $this->isIP( $this->getName() ) ) {
  1030. $ip = $this->getName();
  1031. } else {
  1032. # Check if we are looking at the current user
  1033. # If we don't, and the user is logged in, we don't know about
  1034. # his IP / autoblock status, so ignore autoblock of current user's IP
  1035. if ( $this->getID() != $wgUser->getID() ) {
  1036. $ip = '';
  1037. } else {
  1038. # Get IP of current user
  1039. $ip = wfGetIP();
  1040. }
  1041. }
  1042. if ( $this->isAllowed( 'ipblock-exempt' ) ) {
  1043. # Exempt from all types of IP-block
  1044. $ip = '';
  1045. }
  1046. # User/IP blocking
  1047. $this->mBlock = new Block();
  1048. $this->mBlock->fromMaster( !$bFromSlave );
  1049. if ( $this->mBlock->load( $ip , $this->mId ) ) {
  1050. wfDebug( __METHOD__ . ": Found block.\n" );
  1051. $this->mBlockedby = $this->mBlock->mBy;
  1052. if( $this->mBlockedby == 0 )
  1053. $this->mBlockedby = $this->mBlock->mByName;
  1054. $this->mBlockreason = $this->mBlock->mReason;
  1055. $this->mHideName = $this->mBlock->mHideName;
  1056. $this->mAllowUsertalk = $this->mBlock->mAllowUsertalk;
  1057. if ( $this->isLoggedIn() && $wgUser->getID() == $this->getID() ) {
  1058. $this->spreadBlock();
  1059. }
  1060. } else {
  1061. // Bug 13611: don't remove mBlock here, to allow account creation blocks to
  1062. // apply to users. Note that the existence of $this->mBlock is not used to
  1063. // check for edit blocks, $this->mBlockedby is instead.
  1064. }
  1065. # Proxy blocking
  1066. if ( !$this->isAllowed( 'proxyunbannable' ) && !in_array( $ip, $wgProxyWhitelist ) ) {
  1067. # Local list
  1068. if ( wfIsLocallyBlockedProxy( $ip ) ) {
  1069. $this->mBlockedby = wfMsg( 'proxyblocker' );
  1070. $this->mBlockreason = wfMsg( 'proxyblockreason' );
  1071. }
  1072. # DNSBL
  1073. if ( !$this->mBlockedby && !$this->getID() ) {
  1074. if ( $this->isDnsBlacklisted( $ip ) ) {
  1075. $this->mBlockedby = wfMsg( 'sorbs' );
  1076. $this->mBlockreason = wfMsg( 'sorbsreason' );
  1077. }
  1078. }
  1079. }
  1080. # Extensions
  1081. wfRunHooks( 'GetBlockedStatus', array( &$this ) );
  1082. wfProfileOut( __METHOD__ );
  1083. }
  1084. /**
  1085. * Whether the given IP is in a DNS blacklist.
  1086. *
  1087. * @param $ip \string IP to check
  1088. * @param $checkWhitelist Boolean: whether to check the whitelist first
  1089. * @return \bool True if blacklisted.
  1090. */
  1091. function isDnsBlacklisted( $ip, $checkWhitelist = false ) {
  1092. global $wgEnableSorbs, $wgEnableDnsBlacklist,
  1093. $wgSorbsUrl, $wgDnsBlacklistUrls, $wgProxyWhitelist;
  1094. if ( !$wgEnableDnsBlacklist && !$wgEnableSorbs )
  1095. return false;
  1096. if ( $checkWhitelist && in_array( $ip, $wgProxyWhitelist ) )
  1097. return false;
  1098. $urls = array_merge( $wgDnsBlacklistUrls, (array)$wgSorbsUrl );
  1099. return $this->inDnsBlacklist( $ip, $urls );
  1100. }
  1101. /**
  1102. * Whether the given IP is in a given DNS blacklist.
  1103. *
  1104. * @param $ip \string IP to check
  1105. * @param $bases \string or Array of Strings: URL of the DNS blacklist
  1106. * @return \bool True if blacklisted.
  1107. */
  1108. function inDnsBlacklist( $ip, $bases ) {
  1109. wfProfileIn( __METHOD__ );
  1110. $found = false;
  1111. // FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
  1112. if( IP::isIPv4( $ip ) ) {
  1113. # Reverse IP, bug 21255
  1114. $ipReversed = implode( '.', array_reverse( explode( '.', $ip ) ) );
  1115. foreach( (array)$bases as $base ) {
  1116. # Make hostname
  1117. $host = "$ipReversed.$base";
  1118. # Send query
  1119. $ipList = gethostbynamel( $host );
  1120. if( $ipList ) {
  1121. wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" );
  1122. $found = true;
  1123. break;
  1124. } else {
  1125. wfDebug( "Requested $host, not found in $base.\n" );
  1126. }
  1127. }
  1128. }
  1129. wfProfileOut( __METHOD__ );
  1130. return $found;
  1131. }
  1132. /**
  1133. * Is this user subject to rate limiting?
  1134. *
  1135. * @return \bool True if rate limited
  1136. */
  1137. public function isPingLimitable() {
  1138. global $wgRateLimitsExcludedGroups;
  1139. global $wgRateLimitsExcludedIPs;
  1140. if( array_intersect( $this->getEffectiveGroups(), $wgRateLimitsExcludedGroups ) ) {
  1141. // Deprecated, but kept for backwards-compatibility config
  1142. return false;
  1143. }
  1144. if( in_array( wfGetIP(), $wgRateLimitsExcludedIPs ) ) {
  1145. // No other good way currently to disable rate limits
  1146. // for specific IPs. :P
  1147. // But this is a crappy hack and should die.
  1148. return false;
  1149. }
  1150. return !$this->isAllowed('noratelimit');
  1151. }
  1152. /**
  1153. * Primitive rate limits: enforce maximum actions per time period
  1154. * to put a brake on flooding.
  1155. *
  1156. * @note When using a shared cache like memcached, IP-address
  1157. * last-hit counters will be shared across wikis.
  1158. *
  1159. * @param $action \string Action to enforce; 'edit' if unspecified
  1160. * @return \bool True if a rate limiter was tripped
  1161. */
  1162. function pingLimiter( $action = 'edit' ) {
  1163. # Call the 'PingLimiter' hook
  1164. $result = false;
  1165. if( !wfRunHooks( 'PingLimiter', array( &$this, $action, $result ) ) ) {
  1166. return $result;
  1167. }
  1168. global $wgRateLimits;
  1169. if( !isset( $wgRateLimits[$action] ) ) {
  1170. return false;
  1171. }
  1172. # Some groups shouldn't trigger the ping limiter, ever
  1173. if( !$this->isPingLimitable() )
  1174. return false;
  1175. global $wgMemc, $wgRateLimitLog;
  1176. wfProfileIn( __METHOD__ );
  1177. $limits = $wgRateLimits[$action];
  1178. $keys = array();
  1179. $id = $this->getId();
  1180. $ip = wfGetIP();
  1181. $userLimit = false;
  1182. if( isset( $limits['anon'] ) && $id == 0 ) {
  1183. $keys[wfMemcKey( 'limiter', $action, 'anon' )] = $limits['anon'];
  1184. }
  1185. if( isset( $limits['user'] ) && $id != 0 ) {
  1186. $userLimit = $limits['user'];
  1187. }
  1188. if( $this->isNewbie() ) {
  1189. if( isset( $limits['newbie'] ) && $id != 0 ) {
  1190. $keys[wfMemcKey( 'limiter', $action, 'user', $id )] = $limits['newbie'];
  1191. }
  1192. if( isset( $limits['ip'] ) ) {
  1193. $keys["mediawiki:limiter:$action:ip:$ip"] = $limits['ip'];
  1194. }
  1195. $matches = array();
  1196. if( isset( $limits['subnet'] ) && preg_match( '/^(\d+\.\d+\.\d+)\.\d+$/', $ip, $matches ) ) {
  1197. $subnet = $matches[1];
  1198. $keys["mediawiki:limiter:$action:subnet:$subnet"] = $limits['subnet'];
  1199. }
  1200. }
  1201. // Check for group-specific permissions
  1202. // If more than one group applies, use the group with the highest limit
  1203. foreach ( $this->getGroups() as $group ) {
  1204. if ( isset( $limits[$group] ) ) {
  1205. if ( $userLimit === false || $limits[$group] > $userLimit ) {
  1206. $userLimit = $limits[$group];
  1207. }
  1208. }
  1209. }
  1210. // Set the user limit key
  1211. if ( $userLimit !== false ) {
  1212. wfDebug( __METHOD__ . ": effective user limit: $userLimit\n" );
  1213. $keys[ wfMemcKey( 'limiter', $action, 'user', $id ) ] = $userLimit;
  1214. }
  1215. $triggered = false;
  1216. foreach( $keys as $key => $limit ) {
  1217. list( $max, $period ) = $limit;
  1218. $summary = "(limit $max in {$period}s)";
  1219. $count = $wgMemc->get( $key );
  1220. // Already pinged?
  1221. if( $count ) {
  1222. if( $count > $max ) {
  1223. wfDebug( __METHOD__ . ": tripped! $key at $count $summary\n" );
  1224. if( $wgRateLimitLog ) {
  1225. @error_log( wfTimestamp( TS_MW ) . ' ' . wfWikiID() . ': ' . $this->getName() . " tripped $key at $count $summary\n", 3, $wgRateLimitLog );
  1226. }
  1227. $triggered = true;
  1228. } else {
  1229. wfDebug( __METHOD__ . ": ok. $key at $count $summary\n" );
  1230. }
  1231. } else {
  1232. wfDebug( __METHOD__ . ": adding record for $key $summary\n" );
  1233. $wgMemc->add( $key, 0, intval( $period ) ); // first ping
  1234. }
  1235. $wgMemc->incr( $key );
  1236. }
  1237. wfProfileOut( __METHOD__ );
  1238. return $triggered;
  1239. }
  1240. /**
  1241. * Check if user is blocked
  1242. *
  1243. * @param $bFromSlave \bool Whether to check the slave database instead of the master
  1244. * @return \bool True if blocked, false otherwise
  1245. */
  1246. function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site
  1247. wfDebug( "User::isBlocked: enter\n" );
  1248. $this->getBlockedStatus( $bFromSlave );
  1249. return $this->mBlockedby !== 0;
  1250. }
  1251. /**
  1252. * Check if user is blocked from editing a particular article
  1253. *
  1254. * @param $title \string Title to check
  1255. * @param $bFromSlave \bool Whether to check the slave database instead of the master
  1256. * @return \bool True if blocked, false otherwise
  1257. */
  1258. function isBlockedFrom( $title, $bFromSlave = false ) {
  1259. global $wgBlockAllowsUTEdit;
  1260. wfProfileIn( __METHOD__ );
  1261. wfDebug( __METHOD__ . ": enter\n" );
  1262. wfDebug( __METHOD__ . ": asking isBlocked()\n" );
  1263. $blocked = $this->isBlocked( $bFromSlave );
  1264. $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
  1265. # If a user's name is suppressed, they cannot make edits anywhere
  1266. if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() &&
  1267. $title->getNamespace() == NS_USER_TALK ) {
  1268. $blocked = false;
  1269. wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
  1270. }
  1271. wfRunHooks( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) );
  1272. wfProfileOut( __METHOD__ );
  1273. return $blocked;
  1274. }
  1275. /**
  1276. * If user is blocked, return the name of the user who placed the block
  1277. * @return \string name of blocker
  1278. */
  1279. function blockedBy() {
  1280. $this->getBlockedStatus();
  1281. return $this->mBlockedby;
  1282. }
  1283. /**
  1284. * If user is blocked, return the specified reason for the block
  1285. * @return \string Blocking reason
  1286. */
  1287. function blockedFor() {
  1288. $this->getBlockedStatus();
  1289. return $this->mBlockreason;
  1290. }
  1291. /**
  1292. * If user is blocked, return the ID for the block
  1293. * @return \int Block ID
  1294. */
  1295. function getBlockId() {
  1296. $this->getBlockedStatus();
  1297. return ( $this->mBlock ? $this->mBlock->mId : false );
  1298. }
  1299. /**
  1300. * Check if user is blocked on all wikis.
  1301. * Do not use for actual edit permission checks!
  1302. * This is intented for quick UI checks.
  1303. *
  1304. * @param $ip \type{\string} IP address, uses current client if none given
  1305. * @return \type{\bool} True if blocked, false otherwise
  1306. */
  1307. function isBlockedGlobally( $ip = '' ) {
  1308. if( $this->mBlockedGlobally !== null ) {
  1309. return $this->mBlockedGlobally;
  1310. }
  1311. // User is already an IP?
  1312. if( IP::isIPAddress( $this->getName() ) ) {
  1313. $ip = $this->getName();
  1314. } else if( !$ip ) {
  1315. $ip = wfGetIP();
  1316. }
  1317. $blocked = false;
  1318. wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) );
  1319. $this->mBlockedGlobally = (bool)$blocked;
  1320. return $this->mBlockedGlobally;
  1321. }
  1322. /**
  1323. * Check if user account is locked
  1324. *
  1325. * @return \type{\bool} True if locked, false otherwise
  1326. */
  1327. function isLocked() {
  1328. if( $this->mLocked !== null ) {
  1329. return $this->mLocked;
  1330. }
  1331. global $wgAuth;
  1332. $authUser = $wgAuth->getUserInstance( $this );
  1333. $this->mLocked = (bool)$authUser->isLocked();
  1334. return $this->mLocked;
  1335. }
  1336. /**
  1337. * Check if user account is hidden
  1338. *
  1339. * @return \type{\bool} True if hidden, false otherwise
  1340. */
  1341. function isHidden() {
  1342. if( $this->mHideName !== null ) {
  1343. return $this->mHideName;
  1344. }
  1345. $this->getBlockedStatus();
  1346. if( !$this->mHideName ) {
  1347. global $wgAuth;
  1348. $authUser = $wgAuth->getUserInstance( $this );
  1349. $this->mHideName = (bool)$authUser->isHidden();
  1350. }
  1351. return $this->mHideName;
  1352. }
  1353. /**
  1354. * Get the user's ID.
  1355. * @return Integer The user's ID; 0 if the user is anonymous or nonexistent
  1356. */
  1357. function getId() {
  1358. if( $this->mId === null and $this->mName !== null
  1359. and User::isIP( $this->mName ) ) {
  1360. // Special case, we know the user is anonymous
  1361. return 0;
  1362. } elseif( $this->mId === null ) {
  1363. // Don't load if this was initialized from an ID
  1364. $this->load();
  1365. }
  1366. return $this->mId;
  1367. }
  1368. /**
  1369. * Set the user and reload all fields according to a given ID
  1370. * @param $v \int User ID to reload
  1371. */
  1372. function setId( $v ) {
  1373. $this->mId = $v;
  1374. $this->clearInstanceCache( 'id' );
  1375. }
  1376. /**
  1377. * Get the user name, or the IP of an anonymous user
  1378. * @return \string User's name or IP address
  1379. */
  1380. function getName() {
  1381. if ( !$this->mDataLoaded && $this->mFrom == 'name' ) {
  1382. # Special case optimisation
  1383. return $this->mName;
  1384. } else {
  1385. $this->load();
  1386. if ( $this->mName === false ) {
  1387. # Clean up IPs
  1388. $this->mName = IP::sanitizeIP( wfGetIP() );
  1389. }
  1390. return $this->mName;
  1391. }
  1392. }
  1393. /**
  1394. * Set the user name.
  1395. *
  1396. * This does not reload fields from the database according to the given
  1397. * name. Rather, it is used to create a temporary "nonexistent user" for
  1398. * later addition to the database. It can also be used to set the IP
  1399. * address for an anonymous user to something other than the current
  1400. * remote IP.
  1401. *
  1402. * @note User::newFromName() has rougly the same function, when the named user
  1403. * does not exist.
  1404. * @param $str \string New user name to set
  1405. */
  1406. function setName( $str ) {
  1407. $this->load();
  1408. $this->mName = $str;
  1409. }
  1410. /**
  1411. * Get the user's name escaped by underscores.
  1412. * @return \string Username escaped by underscores.
  1413. */
  1414. function getTitleKey() {
  1415. return str_replace( ' ', '_', $this->getName() );
  1416. }
  1417. /**
  1418. * Check if the user has new messages.
  1419. * @return \bool True if the user has new messages
  1420. */
  1421. function getNewtalk() {
  1422. $this->load();
  1423. # Load the newtalk status if it is unloaded (mNewtalk=-1)
  1424. if( $this->mNewtalk === -1 ) {
  1425. $this->mNewtalk = false; # reset talk page status
  1426. # Check memcached separately for anons, who have no
  1427. # entire User object stored in there.
  1428. if( !$this->mId ) {
  1429. global $wgMemc;
  1430. $key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
  1431. $newtalk = $wgMemc->get( $key );
  1432. if( strval( $newtalk ) !== '' ) {
  1433. $this->mNewtalk = (bool)$newtalk;
  1434. } else {
  1435. // Since we are caching this, make sure it is up to date by getting it
  1436. // from the master
  1437. $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true );
  1438. $wgMemc->set( $key, (int)$this->mNewtalk, 1800 );
  1439. }
  1440. } else {
  1441. $this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
  1442. }
  1443. }
  1444. return (bool)$this->mNewtalk;
  1445. }
  1446. /**
  1447. * Return the talk page(s) this user has new messages on.
  1448. * @return \type{\arrayof{\string}} Array of page URLs
  1449. */
  1450. function getNewMessageLinks() {
  1451. $talks = array();
  1452. if( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) )
  1453. return $talks;
  1454. if( !$this->getNewtalk() )
  1455. return array();
  1456. $up = $this->getUserPage();
  1457. $utp = $up->getTalkPage();
  1458. return array( array( 'wiki' => wfWikiID(), 'link' => $utp->getLocalURL() ) );
  1459. }
  1460. /**
  1461. * Internal uncached check for new messages
  1462. *
  1463. * @see getNewtalk()
  1464. * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise
  1465. * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise
  1466. * @param $fromMaster \bool true to fetch from the master, false for a slave
  1467. * @return \bool True if the user has new messages
  1468. * @private
  1469. */
  1470. function checkNewtalk( $field, $id, $fromMaster = false ) {
  1471. if ( $fromMaster ) {
  1472. $db = wfGetDB( DB_MASTER );
  1473. } else {
  1474. $db = wfGetDB( DB_SLAVE );
  1475. }
  1476. $ok = $db->selectField( 'user_newtalk', $field,
  1477. array( $field => $id ), __METHOD__ );
  1478. return $ok !== false;
  1479. }
  1480. /**
  1481. * Add or update the new messages flag
  1482. * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise
  1483. * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise
  1484. * @return \bool True if successful, false otherwise
  1485. * @private
  1486. */
  1487. function updateNewtalk( $field, $id ) {
  1488. $dbw = wfGetDB( DB_MASTER );
  1489. $dbw->insert( 'user_newtalk',
  1490. array( $field => $id ),
  1491. __METHOD__,
  1492. 'IGNORE' );
  1493. if ( $dbw->affectedRows() ) {
  1494. wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
  1495. return true;
  1496. } else {
  1497. wfDebug( __METHOD__ . " already set ($field, $id)\n" );
  1498. return false;
  1499. }
  1500. }
  1501. /**
  1502. * Clear the new messages flag for the given user
  1503. * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise
  1504. * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise
  1505. * @return \bool True if successful, false otherwise
  1506. * @private
  1507. */
  1508. function deleteNewtalk( $field, $id ) {
  1509. $dbw = wfGetDB( DB_MASTER );
  1510. $dbw->delete( 'user_newtalk',
  1511. array( $field => $id ),
  1512. __METHOD__ );
  1513. if ( $dbw->affectedRows() ) {
  1514. wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
  1515. return true;
  1516. } else {
  1517. wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
  1518. return false;
  1519. }
  1520. }
  1521. /**
  1522. * Update the 'You have new messages!' status.
  1523. * @param $val \bool Whether the user has new messages
  1524. */
  1525. function setNewtalk( $val ) {
  1526. if( wfReadOnly() ) {
  1527. return;
  1528. }
  1529. $this->load();
  1530. $this->mNewtalk = $val;
  1531. if( $this->isAnon() ) {
  1532. $field = 'user_ip';
  1533. $id = $this->getName();
  1534. } else {
  1535. $field = 'user_id';
  1536. $id = $this->getId();
  1537. }
  1538. global $wgMemc;
  1539. if( $val ) {
  1540. $changed = $this->updateNewtalk( $field, $id );
  1541. } else {
  1542. $changed = $this->deleteNewtalk( $field, $id );
  1543. }
  1544. if( $this->isAnon() ) {
  1545. // Anons have a separate memcached space, since
  1546. // user records aren't kept for them.
  1547. $key = wf

Large files files are truncated, but you can click here to view the full file