diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2009-02-22 13:37:51 +0100 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2009-02-22 13:37:51 +0100 |
commit | b9b85843572bf283f48285001e276ba7e61b63f6 (patch) | |
tree | 4c6f4571552ada9ccfb4030481dcf77308f8b254 /includes/User.php | |
parent | d9a20acc4e789cca747ad360d87ee3f3e7aa58c1 (diff) |
updated to MediaWiki 1.14.0
Diffstat (limited to 'includes/User.php')
-rw-r--r-- | includes/User.php | 1059 |
1 files changed, 700 insertions, 359 deletions
diff --git a/includes/User.php b/includes/User.php index 4e39d678..9fee089c 100644 --- a/includes/User.php +++ b/includes/User.php @@ -1,20 +1,29 @@ <?php /** - * See user.txt + * Implements the User class for the %MediaWiki software. * @file */ -# Number of characters in user_token field +/** + * \int Number of characters in user_token field. + * @ingroup Constants + */ define( 'USER_TOKEN_LENGTH', 32 ); -# Serialized record version +/** + * \int Serialized record version. + * @ingroup Constants + */ define( 'MW_USER_VERSION', 6 ); -# Some punctuation to prevent editing from broken text-mangling proxies. +/** + * \string Some punctuation to prevent editing from broken text-mangling proxies. + * @ingroup Constants + */ define( 'EDIT_TOKEN_SUFFIX', '+\\' ); /** - * Thrown by User::setPassword() on error + * Thrown by User::setPassword() on error. * @ingroup Exception */ class PasswordError extends MWException { @@ -34,11 +43,13 @@ class PasswordError extends MWException { class User { /** - * A list of default user toggles, i.e. boolean user preferences that are - * displayed by Special:Preferences as checkboxes. This list can be - * extended via the UserToggles hook or $wgContLang->getExtraUserToggles(). + * \type{\arrayof{\string}} A list of default user toggles, i.e., boolean user + * preferences that are displayed by Special:Preferences as checkboxes. + * This list can be extended via the UserToggles hook or by + * $wgContLang::getExtraUserToggles(). + * @showinitializer */ - static public $mToggles = array( + public static $mToggles = array( 'highlightbroken', 'justify', 'hideminor', @@ -71,21 +82,26 @@ class User { 'showjumplinks', 'uselivepreview', 'forceeditsummary', - 'watchlisthideown', - 'watchlisthidebots', 'watchlisthideminor', + 'watchlisthidebots', + 'watchlisthideown', + 'watchlisthideanons', + 'watchlisthideliu', 'ccmeonemails', 'diffonly', 'showhiddencats', + 'noconvertlink', + 'norollbackdiff', ); /** - * List of member variables which are saved to the shared cache (memcached). - * Any operation which changes the corresponding database fields must - * call a cache-clearing function. + * \type{\arrayof{\string}} List of member variables which are saved to the + * shared cache (memcached). Any operation which changes the + * corresponding database fields must call a cache-clearing function. + * @showinitializer */ static $mCacheVars = array( - # user table + // user table 'mId', 'mName', 'mRealName', @@ -101,13 +117,15 @@ class User { 'mEmailTokenExpires', 'mRegistration', 'mEditCount', - # user_group table + // user_group table 'mGroups', ); /** - * Core rights - * Each of these should have a corresponding message of the form "right-$right" + * \type{\arrayof{\string}} Core rights. + * Each of these should have a corresponding message of the form + * "right-$right". + * @showinitializer */ static $mCoreRights = array( 'apihighlimits', @@ -132,6 +150,9 @@ class User { 'markbotedits', 'minoredit', 'move', + 'movefile', + 'move-rootuserpages', + 'move-subpages', 'nominornewtalk', 'noratelimit', 'patrol', @@ -142,6 +163,7 @@ class User { 'reupload', 'reupload-shared', 'rollback', + 'siteadmin', 'suppressredirect', 'trackback', 'undelete', @@ -150,47 +172,57 @@ class User { 'upload_by_url', 'userrights', ); - static $mAllRights = false; - /** - * The cache variable declarations + * \string Cached results of getAllRights() */ + static $mAllRights = false; + + /** @name Cache variables */ + //@{ var $mId, $mName, $mRealName, $mPassword, $mNewpassword, $mNewpassTime, $mEmail, $mOptions, $mTouched, $mToken, $mEmailAuthenticated, $mEmailToken, $mEmailTokenExpires, $mRegistration, $mGroups; + //@} /** - * Whether the cache variables have been loaded + * \bool Whether the cache variables have been loaded. */ - var $mDataLoaded; + var $mDataLoaded, $mAuthLoaded; /** - * Initialisation data source if mDataLoaded==false. May be one of: - * defaults anonymous user initialised from class defaults - * name initialise from mName - * id initialise from mId - * session log in from cookies or session if possible + * \string Initialization data source if mDataLoaded==false. May be one of: + * - 'defaults' anonymous user initialised from class defaults + * - 'name' initialise from mName + * - 'id' initialise from mId + * - 'session' log in from cookies or session if possible * * Use the User::newFrom*() family of functions to set this. */ var $mFrom; - /** - * Lazy-initialised variables, invalidated with clearInstanceCache - */ + /** @name Lazy-initialized variables, invalidated with clearInstanceCache */ + //@{ var $mNewtalk, $mDatePreference, $mBlockedby, $mHash, $mSkin, $mRights, - $mBlockreason, $mBlock, $mEffectiveGroups; + $mBlockreason, $mBlock, $mEffectiveGroups, $mBlockedGlobally, + $mLocked, $mHideName; + //@} /** - * Lightweight constructor for anonymous user - * Use the User::newFrom* factory functions for other kinds of users + * Lightweight constructor for an anonymous user. + * Use the User::newFrom* factory functions for other kinds of users. + * + * @see newFromName() + * @see newFromId() + * @see newFromConfirmationCode() + * @see newFromSession() + * @see newFromRow() */ function User() { $this->clearInstanceCache( 'defaults' ); } /** - * Load the user table data for this object from the source given by mFrom + * Load the user table data for this object from the source given by mFrom. */ function load() { if ( $this->mDataLoaded ) { @@ -219,6 +251,7 @@ class User { break; case 'session': $this->loadFromSession(); + wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) ); break; default: throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" ); @@ -227,8 +260,8 @@ class User { } /** - * Load user table data given mId - * @return false if the ID does not exist, true otherwise + * Load user table data, given mId has already been set. + * @return \bool false if the ID does not exist, true otherwise * @private */ function loadFromId() { @@ -283,6 +316,10 @@ class User { global $wgMemc; $wgMemc->set( $key, $data ); } + + + /** @name newFrom*() static factory methods */ + //@{ /** * Static factory method for creation from username. @@ -290,14 +327,14 @@ class User { * This is slightly less efficient than newFromId(), so use newFromId() if * you have both an ID and a name handy. * - * @param $name String: username, validated by Title:newFromText() - * @param $validate Mixed: validate username. Takes the same parameters as + * @param $name \string Username, validated by Title::newFromText() + * @param $validate \mixed Validate username. Takes the same parameters as * User::getCanonicalName(), except that true is accepted as an alias * for 'valid', for BC. * - * @return User object, or null if the username is invalid. If the username - * is not present in the database, the result will be a user object with - * a name, zero user ID and default settings. + * @return \type{User} The User object, or null if the username is invalid. If the + * username is not present in the database, the result will be a user object + * with a name, zero user ID and default settings. */ static function newFromName( $name, $validate = 'valid' ) { if ( $validate === true ) { @@ -315,6 +352,12 @@ class User { } } + /** + * Static factory method for creation from a given user ID. + * + * @param $id \int Valid user ID + * @return \type{User} The corresponding User object + */ static function newFromId( $id ) { $u = new User; $u->mId = $id; @@ -329,8 +372,8 @@ class User { * * If the code is invalid or has expired, returns NULL. * - * @param $code string - * @return User + * @param $code \string Confirmation code + * @return \type{User} */ static function newFromConfirmationCode( $code ) { $dbr = wfGetDB( DB_SLAVE ); @@ -349,7 +392,7 @@ class User { * Create a new user object using data from session or cookies. If the * login credentials are invalid, the result is an anonymous user. * - * @return User + * @return \type{User} */ static function newFromSession() { $user = new User; @@ -360,17 +403,22 @@ class User { /** * Create a new user object from a user row. * The row should have all fields from the user table in it. + * @param $row array A row from the user table + * @return \type{User} */ static function newFromRow( $row ) { $user = new User; $user->loadFromRow( $row ); return $user; } + + //@} + /** - * Get username given an id. - * @param $id Integer: database user id - * @return string Nickname of a user + * Get the username corresponding to a given user ID + * @param $id \int User ID + * @return \string The corresponding username */ static function whoIs( $id ) { $dbr = wfGetDB( DB_SLAVE ); @@ -378,10 +426,10 @@ class User { } /** - * Get the real name of a user given their identifier + * Get the real name of a user given their user ID * - * @param $id Int: database user id - * @return string Real name of a user + * @param $id \int User ID + * @return \string The corresponding user's real name */ static function whoIsReal( $id ) { $dbr = wfGetDB( DB_SLAVE ); @@ -390,12 +438,11 @@ class User { /** * Get database id given a user name - * @param $name String: nickname of a user - * @return integer|null Database user id (null: if non existent - * @static + * @param $name \string Username + * @return \types{\int,\null} The corresponding user's ID, or null if user is nonexistent */ static function idFromName( $name ) { - $nt = Title::newFromText( $name ); + $nt = Title::makeTitleSafe( NS_USER, $name ); if( is_null( $nt ) ) { # Illegal name return null; @@ -423,8 +470,8 @@ class User { * addresses like this, if we allowed accounts like this to be created * new users could get the old edits of these anonymous users. * - * @param $name String: nickname of a user - * @return bool + * @param $name \string String to match + * @return \bool True or false */ static function isIP( $name ) { return preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}\.(?:xxx|\d{1,3})$/',$name) || IP::isIPv6($name); @@ -438,8 +485,8 @@ class User { * is longer than the maximum allowed username size or doesn't begin with * a capital letter. * - * @param $name string - * @return bool + * @param $name \string String to match + * @return \bool True or false */ static function isValidUserName( $name ) { global $wgContLang, $wgMaxNameChars; @@ -492,8 +539,8 @@ class User { * If an account already exists in this form, login will be blocked * by a failure to pass this function. * - * @param $name string - * @return bool + * @param $name \string String to match + * @return \bool True or false */ static function isUsableName( $name ) { global $wgReservedUsernames; @@ -502,8 +549,14 @@ class User { return false; } + static $reservedUsernames = false; + if ( !$reservedUsernames ) { + $reservedUsernames = $wgReservedUsernames; + wfRunHooks( 'UserGetReservedNames', array( &$reservedUsernames ) ); + } + // Certain names may be reserved for batch processes. - foreach ( $wgReservedUsernames as $reserved ) { + foreach ( $reservedUsernames as $reserved ) { if ( substr( $reserved, 0, 4 ) == 'msg:' ) { $reserved = wfMsgForContent( substr( $reserved, 4 ) ); } @@ -524,8 +577,8 @@ class User { * rather than in isValidUserName() to avoid disrupting * existing accounts. * - * @param $name string - * @return bool + * @param $name \string String to match + * @return \bool True or false */ static function isCreatableName( $name ) { return @@ -538,8 +591,8 @@ class User { /** * Is the input a valid password for this user? * - * @param $password String: desired password - * @return bool + * @param $password \string Desired password + * @return \bool True or false */ function isValidPassword( $password ) { global $wgMinimalPasswordLength, $wgContLang; @@ -556,7 +609,7 @@ class User { } /** - * Does a string look like an email address? + * Does a string look like an e-mail address? * * There used to be a regular expression here, it got removed because it * rejected valid addresses. Actually just check if there is '@' somewhere @@ -564,8 +617,8 @@ class User { * * @todo Check for RFC 2822 compilance (bug 959) * - * @param $addr String: email address - * @return bool + * @param $addr \string E-mail address + * @return \bool True or false */ public static function isValidEmailAddr( $addr ) { $result = null; @@ -579,12 +632,12 @@ class User { /** * Given unvalidated user input, return a canonical username, or false if * the username is invalid. - * @param $name string - * @param $validate Mixed: type of validation to use: - * false No validation - * 'valid' Valid for batch processes - * 'usable' Valid for batch processes and login - * 'creatable' Valid for batch processes, login and account creation + * @param $name \string User input + * @param $validate \types{\string,\bool} Type of validation to use: + * - false No validation + * - 'valid' Valid for batch processes + * - 'usable' Valid for batch processes and login + * - 'creatable' Valid for batch processes, login and account creation */ static function getCanonicalName( $name, $validate = 'valid' ) { # Force usernames to capital @@ -598,7 +651,9 @@ class User { return false; # Clean up name according to title rules - $t = Title::newFromText( $name ); + $t = ($validate === 'valid') ? + Title::newFromText( $name ) : Title::makeTitle( NS_USER, $name ); + # Check for invalid titles if( is_null( $t ) ) { return false; } @@ -634,11 +689,10 @@ class User { /** * Count the number of edits of a user + * @todo It should not be static and some day should be merged as proper member function / deprecated -- domas * - * It should not be static and some day should be merged as proper member function / deprecated -- domas - * - * @param $uid Int: the user ID to check - * @return int + * @param $uid \int User ID to check + * @return \int The user's edit count */ static function edits( $uid ) { wfProfileIn( __METHOD__ ); @@ -674,7 +728,7 @@ class User { * Return a random password. Sourced from mt_rand, so it's not particularly secure. * @todo hash random numbers to improve security, like generateToken() * - * @return string + * @return \string New random password */ static function randomPassword() { global $wgMinimalPasswordLength; @@ -691,9 +745,10 @@ class User { } /** - * Set cached properties to default. Note: this no longer clears - * uncached lazy-initialised properties. The constructor does that instead. + * Set cached properties to default. * + * @note This no longer clears uncached lazy-initialised properties; + * the constructor does that instead. * @private */ function loadDefaults( $name = false ) { @@ -728,8 +783,7 @@ class User { } /** - * Initialise php session - * @deprecated use wfSetupSession() + * @deprecated Use wfSetupSession(). */ function SetupSession() { wfDeprecated( __METHOD__ ); @@ -738,8 +792,8 @@ class User { /** * Load user data from the session or login cookie. If there are no valid - * credentials, initialises the user as an anon. - * @return true if the user is logged in, false otherwise + * credentials, initialises the user as an anonymous user. + * @return \bool True if the user is logged in, false otherwise. */ private function loadFromSession() { global $wgMemc, $wgCookiePrefix; @@ -750,20 +804,27 @@ class User { return $result; } - if ( isset( $_SESSION['wsUserID'] ) ) { - if ( 0 != $_SESSION['wsUserID'] ) { + if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) { + $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] ); + if( isset( $_SESSION['wsUserID'] ) && $sId != $_SESSION['wsUserID'] ) { + $this->loadDefaults(); // Possible collision! + wfDebugLog( 'loginSessions', "Session user ID ({$_SESSION['wsUserID']}) and + cookie user ID ($sId) don't match!" ); + return false; + } + $_SESSION['wsUserID'] = $sId; + } else if ( isset( $_SESSION['wsUserID'] ) ) { + if ( $_SESSION['wsUserID'] != 0 ) { $sId = $_SESSION['wsUserID']; } else { $this->loadDefaults(); return false; } - } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserID"] ) ) { - $sId = intval( $_COOKIE["{$wgCookiePrefix}UserID"] ); - $_SESSION['wsUserID'] = $sId; } else { $this->loadDefaults(); return false; } + if ( isset( $_SESSION['wsUserName'] ) ) { $sName = $_SESSION['wsUserName']; } else if ( isset( $_COOKIE["{$wgCookiePrefix}UserName"] ) ) { @@ -806,10 +867,10 @@ class User { } /** - * Load user and user_group data from the database - * $this->mId must be set, this is how the user is identified. + * Load user and user_group data from the database. + * $this::mId must be set, this is how the user is identified. * - * @return true if the user exists, false if the user is anonymous + * @return \bool True if the user exists, false if the user is anonymous * @private */ function loadFromDatabase() { @@ -840,7 +901,9 @@ class User { } /** - * Initialise the user object from a row from the user table + * Initialize this object from a row from the user table. + * + * @param $row \type{\arrayof{\mixed}} Row from the user table to load. */ function loadFromRow( $row ) { $this->mDataLoaded = true; @@ -865,7 +928,7 @@ class User { } /** - * Load the groups from the database if they aren't already loaded + * Load the groups from the database if they aren't already loaded. * @private */ function loadGroups() { @@ -884,8 +947,8 @@ class User { /** * Clear various cached data stored in this object. - * @param $reloadFrom String: reload user and user_groups table data from a - * given source. May be "name", "id", "defaults", "session" or false for + * @param $reloadFrom \string Reload user and user_groups table data from a + * given source. May be "name", "id", "defaults", "session", or false for * no reload. */ function clearInstanceCache( $reloadFrom = false ) { @@ -906,9 +969,8 @@ class User { /** * Combine the language default options with any site-specific options * and add the default language variants. - * Not really private cause it's called by Language class - * @return array - * @private + * + * @return \type{\arrayof{\string}} Array of options */ static function getDefaultOptions() { global $wgNamespacesToBeSearchedDefault; @@ -934,8 +996,8 @@ class User { /** * Get a given default option value. * - * @param $opt string - * @return string + * @param $opt \string Name of option to retrieve + * @return \string Default option value */ public static function getDefaultOption( $opt ) { $defOpts = self::getDefaultOptions(); @@ -948,7 +1010,7 @@ class User { /** * Get a list of user toggle names - * @return array + * @return \type{\arrayof{\string}} Array of user toggle names */ static function getToggles() { global $wgContLang; @@ -961,7 +1023,7 @@ class User { /** * Get blocking information * @private - * @param $bFromSlave Bool: specify whether to check slave or master. To + * @param $bFromSlave \bool Whether to check the slave database first. To * improve performance, non-critical checks are done * against slaves. Check when actually saving should be * done against master. @@ -986,6 +1048,7 @@ class User { $this->mBlockedby = 0; $this->mHideName = 0; + $this->mAllowUsertalk = 0; $ip = wfGetIP(); if ($this->isAllowed( 'ipblock-exempt' ) ) { @@ -1001,12 +1064,14 @@ class User { $this->mBlockedby = $this->mBlock->mBy; $this->mBlockreason = $this->mBlock->mReason; $this->mHideName = $this->mBlock->mHideName; + $this->mAllowUsertalk = $this->mBlock->mAllowUsertalk; if ( $this->isLoggedIn() ) { $this->spreadBlock(); } } else { - $this->mBlock = null; - wfDebug( __METHOD__.": No block.\n" ); + // Bug 13611: don't remove mBlock here, to allow account creation blocks to + // apply to users. Note that the existence of $this->mBlock is not used to + // check for edit blocks, $this->mBlockedby is instead. } # Proxy blocking @@ -1032,6 +1097,12 @@ class User { wfProfileOut( __METHOD__ ); } + /** + * Whether the given IP is in the SORBS blacklist. + * + * @param $ip \string IP to check + * @return \bool True if blacklisted. + */ function inSorbsBlacklist( $ip ) { global $wgEnableSorbs, $wgSorbsUrl; @@ -1039,24 +1110,27 @@ class User { $this->inDnsBlacklist( $ip, $wgSorbsUrl ); } + /** + * Whether the given IP is in a given DNS blacklist. + * + * @param $ip \string IP to check + * @param $base \string URL of the DNS blacklist + * @return \bool True if blacklisted. + */ function inDnsBlacklist( $ip, $base ) { wfProfileIn( __METHOD__ ); $found = false; $host = ''; - // FIXME: IPv6 ??? - $m = array(); - if ( preg_match( '/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $m ) ) { + // FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170) + if( IP::isIPv4($ip) ) { # Make hostname - for ( $i=4; $i>=1; $i-- ) { - $host .= $m[$i] . '.'; - } - $host .= $base; + $host = "$ip.$base"; # Send query $ipList = gethostbynamel( $host ); - if ( $ipList ) { + if( $ipList ) { wfDebug( "Hostname $host is {$ipList[0]}, it's a proxy says $base!\n" ); $found = true; } else { @@ -1071,7 +1145,7 @@ class User { /** * Is this user subject to rate limiting? * - * @return bool + * @return \bool True if rate limited */ public function isPingLimitable() { global $wgRateLimitsExcludedGroups; @@ -1086,10 +1160,11 @@ class User { * Primitive rate limits: enforce maximum actions per time period * to put a brake on flooding. * - * Note: when using a shared cache like memcached, IP-address + * @note When using a shared cache like memcached, IP-address * last-hit counters will be shared across wikis. * - * @return bool true if a rate limiter was tripped + * @param $action \string Action to enforce; 'edit' if unspecified + * @return \bool True if a rate limiter was tripped */ function pingLimiter( $action='edit' ) { @@ -1180,7 +1255,9 @@ class User { /** * Check if user is blocked - * @return bool True if blocked, false otherwise + * + * @param $bFromSlave \bool Whether to check the slave database instead of the master + * @return \bool True if blocked, false otherwise */ function isBlocked( $bFromSlave = true ) { // hacked from false due to horrible probs on site wfDebug( "User::isBlocked: enter\n" ); @@ -1190,6 +1267,10 @@ class User { /** * Check if user is blocked from editing a particular article + * + * @param $title \string Title to check + * @param $bFromSlave \bool Whether to check the slave database instead of the master + * @return \bool True if blocked, false otherwise */ function isBlockedFrom( $title, $bFromSlave = false ) { global $wgBlockAllowsUTEdit; @@ -1198,8 +1279,9 @@ class User { wfDebug( __METHOD__.": asking isBlocked()\n" ); $blocked = $this->isBlocked( $bFromSlave ); + $allowUsertalk = ($wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false); # If a user's name is suppressed, they cannot make edits anywhere - if ( !$this->mHideName && $wgBlockAllowsUTEdit && $title->getText() === $this->getName() && + if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName() && $title->getNamespace() == NS_USER_TALK ) { $blocked = false; wfDebug( __METHOD__.": self-talk page, ignoring any blocks\n" ); @@ -1209,8 +1291,8 @@ class User { } /** - * Get name of blocker - * @return string name of blocker + * If user is blocked, return the name of the user who placed the block + * @return \string name of blocker */ function blockedBy() { $this->getBlockedStatus(); @@ -1218,16 +1300,74 @@ class User { } /** - * Get blocking reason - * @return string Blocking reason + * If user is blocked, return the specified reason for the block + * @return \string Blocking reason */ function blockedFor() { $this->getBlockedStatus(); return $this->mBlockreason; } + + /** + * Check if user is blocked on all wikis. + * Do not use for actual edit permission checks! + * This is intented for quick UI checks. + * + * @param $ip \type{\string} IP address, uses current client if none given + * @return \type{\bool} True if blocked, false otherwise + */ + function isBlockedGlobally( $ip = '' ) { + if( $this->mBlockedGlobally !== null ) { + return $this->mBlockedGlobally; + } + // User is already an IP? + if( IP::isIPAddress( $this->getName() ) ) { + $ip = $this->getName(); + } else if( !$ip ) { + $ip = wfGetIP(); + } + $blocked = false; + wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) ); + $this->mBlockedGlobally = (bool)$blocked; + return $this->mBlockedGlobally; + } + + /** + * Check if user account is locked + * + * @return \type{\bool} True if locked, false otherwise + */ + function isLocked() { + if( $this->mLocked !== null ) { + return $this->mLocked; + } + global $wgAuth; + $authUser = $wgAuth->getUserInstance( $this ); + $this->mLocked = (bool)$authUser->isLocked(); + return $this->mLocked; + } + + /** + * Check if user account is hidden + * + * @return \type{\bool} True if hidden, false otherwise + */ + function isHidden() { + if( $this->mHideName !== null ) { + return $this->mHideName; + } + $this->getBlockedStatus(); + if( !$this->mHideName ) { + global $wgAuth; + $authUser = $wgAuth->getUserInstance( $this ); + $this->mHideName = (bool)$authUser->isHidden(); + } + return $this->mHideName; + } /** - * Get the user ID. Returns 0 if the user is anonymous or nonexistent. + * Get the user's ID. + * @return \int The user's ID; 0 if the user is anonymous or nonexistent */ function getId() { if( $this->mId === null and $this->mName !== null @@ -1242,7 +1382,8 @@ class User { } /** - * Set the user and reload all fields according to that ID + * Set the user and reload all fields according to a given ID + * @param $v \int User ID to reload */ function setId( $v ) { $this->mId = $v; @@ -1250,7 +1391,8 @@ class User { } /** - * Get the user name, or the IP for anons + * Get the user name, or the IP of an anonymous user + * @return \string User's name or IP address */ function getName() { if ( !$this->mDataLoaded && $this->mFrom == 'name' ) { @@ -1275,8 +1417,9 @@ class User { * address for an anonymous user to something other than the current * remote IP. * - * User::newFromName() has rougly the same function, when the named user + * @note User::newFromName() has rougly the same function, when the named user * does not exist. + * @param $str \string New user name to set */ function setName( $str ) { $this->load(); @@ -1284,13 +1427,17 @@ class User { } /** - * Return the title dbkey form of the name, for eg user pages. - * @return string + * Get the user's name escaped by underscores. + * @return \string Username escaped by underscores. */ function getTitleKey() { return str_replace( ' ', '_', $this->getName() ); } + /** + * Check if the user has new messages. + * @return \bool True if the user has new messages + */ function getNewtalk() { $this->load(); @@ -1322,6 +1469,7 @@ class User { /** * Return the talk page(s) this user has new messages on. + * @return \type{\arrayof{\string}} Array of page URLs */ function getNewMessageLinks() { $talks = array(); @@ -1337,13 +1485,13 @@ class User { /** - * Perform a user_newtalk check, uncached. - * Use getNewtalk for a cached check. + * Internal uncached check for new messages * - * @param $field string - * @param $id mixed - * @param $fromMaster Bool: true to fetch from the master, false for a slave - * @return bool + * @see getNewtalk() + * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise + * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise + * @param $fromMaster \bool true to fetch from the master, false for a slave + * @return \bool True if the user has new messages * @private */ function checkNewtalk( $field, $id, $fromMaster = false ) { @@ -1358,9 +1506,10 @@ class User { } /** - * Add or update the - * @param $field string - * @param $id mixed + * Add or update the new messages flag + * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise + * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise + * @return \bool True if successful, false otherwise * @private */ function updateNewtalk( $field, $id ) { @@ -1380,8 +1529,9 @@ class User { /** * Clear the new messages flag for the given user - * @param $field string - * @param $id mixed + * @param $field \string 'user_ip' for anonymous users, 'user_id' otherwise + * @param $id \types{\string,\int} User's IP address for anonymous users, User ID otherwise + * @return \bool True if successful, false otherwise * @private */ function deleteNewtalk( $field, $id ) { @@ -1400,7 +1550,7 @@ class User { /** * Update the 'You have new messages!' status. - * @param $val bool + * @param $val \bool Whether the user has new messages */ function setNewtalk( $val ) { if( wfReadOnly() ) { @@ -1439,6 +1589,7 @@ class User { /** * Generate a current or new-future timestamp to be stored in the * user_touched field when we update things. + * @return \string Timestamp in TS_MW format */ private static function newTouchedTimestamp() { global $wgClockSkewFudge; @@ -1453,6 +1604,7 @@ class User { * Called implicitly from invalidateCache() and saveSettings(). */ private function clearSharedCache() { + $this->load(); if( $this->mId ) { global $wgMemc; $wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) ); @@ -1479,13 +1631,25 @@ class User { } } + /** + * Validate the cache for this account. + * @param $timestamp \string A timestamp in TS_MW format + */ function validateCache( $timestamp ) { $this->load(); return ($timestamp >= $this->mTouched); } /** - * Set the password and reset the random token + * Get the user touched timestamp + */ + function getTouched() { + $this->load(); + return $this->mTouched; + } + + /** + * Set the password and reset the random token. * Calls through to authentication plugin if necessary; * will have no effect if the auth plugin refuses to * pass the change through or if the legal password @@ -1495,7 +1659,7 @@ class User { * wipes it, so the account cannot be logged in until * a new password is set, for instance via e-mail. * - * @param $str string + * @param $str \string New password to set * @throws PasswordError on failure */ function setPassword( $str ) { @@ -1523,10 +1687,9 @@ class User { } /** - * Set the password and reset the random token no matter - * what. + * Set the password and reset the random token unconditionally. * - * @param $str string + * @param $str \string New password to set */ function setInternalPassword( $str ) { $this->load(); @@ -1542,6 +1705,10 @@ class User { $this->mNewpassTime = null; } + /** + * Get the user's current token. + * @return \string Token + */ function getToken() { $this->load(); return $this->mToken; @@ -1550,6 +1717,8 @@ class User { /** * Set the random token (used for persistent authentication) * Called from loadDefaults() among other places. + * + * @param $token \string If specified, set the token to this value * @private */ function setToken( $token = false ) { @@ -1568,7 +1737,13 @@ class User { $this->mToken = $token; } } - + + /** + * Set the cookie password + * + * @param $str \string New cookie password + * @private + */ function setCookiePassword( $str ) { $this->load(); $this->mCookiePassword = md5( $str ); @@ -1576,7 +1751,9 @@ class User { /** * Set the password for a password reminder or new account email - * Sets the user_newpass_time field if $throttle is true + * + * @param $str \string New password to set + * @param $throttle \bool If true, reset the throttle timestamp to the present */ function setNewpassword( $str, $throttle = true ) { $this->load(); @@ -1587,8 +1764,9 @@ class User { } /** - * Returns true if a password reminder email has already been sent within - * the last $wgPasswordReminderResendTime hours + * Has password reminder email been sent within the last + * $wgPasswordReminderResendTime hours? + * @return \bool True or false */ function isPasswordReminderThrottled() { global $wgPasswordReminderResendTime; @@ -1600,38 +1778,62 @@ class User { return time() < $expiry; } + /** + * Get the user's e-mail address + * @return \string User's email address + */ function getEmail() { $this->load(); wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) ); return $this->mEmail; } + /** + * Get the timestamp of the user's e-mail authentication + * @return \string TS_MW timestamp + */ function getEmailAuthenticationTimestamp() { $this->load(); wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) ); return $this->mEmailAuthenticated; } + /** + * Set the user's e-mail address + * @param $str \string New e-mail address + */ function setEmail( $str ) { $this->load(); $this->mEmail = $str; wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) ); } + /** + * Get the user's real name + * @return \string User's real name + */ function getRealName() { $this->load(); return $this->mRealName; } + /** + * Set the user's real name + * @param $str \string New real name + */ function setRealName( $str ) { $this->load(); $this->mRealName = $str; } /** - * @param $oname String: the option to check - * @param $defaultOverride String: A default value returned if the option does not exist - * @return string + * Get the user's current setting for a given option. + * + * @param $oname \string The option to check + * @param $defaultOverride \string A default value returned if the option does not exist + * @return \string User's current value for the option + * @see getBoolOption() + * @see getIntOption() */ function getOption( $oname, $defaultOverride = '' ) { $this->load(); @@ -1649,46 +1851,41 @@ class User { return $defaultOverride; } } - - /** - * Get the user's date preference, including some important migration for - * old user rows. - */ - function getDatePreference() { - if ( is_null( $this->mDatePreference ) ) { - global $wgLang; - $value = $this->getOption( 'date' ); - $map = $wgLang->getDatePreferenceMigrationMap(); - if ( isset( $map[$value] ) ) { - $value = $map[$value]; - } - $this->mDatePreference = $value; - } - return $this->mDatePreference; - } - + /** - * @param $oname String: the option to check - * @return bool False if the option is not selected, true if it is + * Get the user's current setting for a given option, as a boolean value. + * + * @param $oname \string The option to check + * @return \bool User's current value for the option + * @see getOption() */ function getBoolOption( $oname ) { return (bool)$this->getOption( $oname ); } + /** - * Get an option as an integer value from the source string. - * @param $oname String: the option to check - * @param $default Int: optional value to return if option is unset/blank. - * @return int + * Get the user's current setting for a given option, as a boolean value. + * + * @param $oname \string The option to check + * @param $defaultOverride \int A default value returned if the option does not exist + * @return \int User's current value for the option + * @see getOption() */ - function getIntOption( $oname, $default=0 ) { + function getIntOption( $oname, $defaultOverride=0 ) { $val = $this->getOption( $oname ); if( $val == '' ) { - $val = $default; + $val = $defaultOverride; } return intval( $val ); } + /** + * Set the given option for a user. + * + * @param $oname \string The option to set + * @param $val \mixed New value to set + */ function setOption( $oname, $val ) { $this->load(); if ( is_null( $this->mOptions ) ) { @@ -1713,6 +1910,28 @@ class User { $this->mOptions[$oname] = $val; } + /** + * Get the user's preferred date format. + * @return \string User's preferred date format + */ + function getDatePreference() { + // Important migration for old data rows + if ( is_null( $this->mDatePreference ) ) { + global $wgLang; + $value = $this->getOption( 'date' ); + $map = $wgLang->getDatePreferenceMigrationMap(); + if ( isset( $map[$value] ) ) { + $value = $map[$value]; + } + $this->mDatePreference = $value; + } + return $this->mDatePreference; + } + + /** + * Get the permissions this user has. + * @return \type{\arrayof{\string}} Array of permission names + */ function getRights() { if ( is_null( $this->mRights ) ) { $this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() ); @@ -1726,7 +1945,7 @@ class User { /** * Get the list of explicit group memberships this user has. * The implicit * and user groups are not included. - * @return array of strings + * @return \type{\arrayof{\string}} Array of internal group names */ function getGroups() { $this->load(); @@ -1737,8 +1956,8 @@ class User { * Get the list of implicit group memberships this user has. * This includes all explicit groups, plus 'user' if logged in, * '*' for all accounts and autopromoted groups - * @param $recache Boolean: don't use the cache - * @return array of strings + * @param $recache \bool Whether to avoid the cache + * @return \type{\arrayof{\string}} Array of internal group names */ function getEffectiveGroups( $recache = false ) { if ( $recache || is_null( $this->mEffectiveGroups ) ) { @@ -1759,7 +1978,10 @@ class User { return $this->mEffectiveGroups; } - /* Return the edit count for the user. This is where User::edits should have been */ + /** + * Get the user's edit count. + * @return \int User'e edit count + */ function getEditCount() { if ($this->mId) { if ( !isset( $this->mEditCount ) ) { @@ -1776,7 +1998,7 @@ class User { /** * Add the user to the given group. * This takes immediate effect. - * @param $group string + * @param $group \string Name of the group to add */ function addGroup( $group ) { $dbw = wfGetDB( DB_MASTER ); @@ -1800,7 +2022,7 @@ class User { /** * Remove the user from the given group. * This takes immediate effect. - * @param $group string + * @param $group \string Name of the group to remove */ function removeGroup( $group ) { $this->load(); @@ -1821,27 +2043,24 @@ class User { /** - * A more legible check for non-anonymousness. - * Returns true if the user is not an anonymous visitor. - * - * @return bool + * Get whether the user is logged in + * @return \bool True or false */ function isLoggedIn() { return $this->getID() != 0; } /** - * A more legible check for anonymousness. - * Returns true if the user is an anonymous visitor. - * - * @return bool + * Get whether the user is anonymous + * @return \bool True or false */ function isAnon() { return !$this->isLoggedIn(); } /** - * Whether the user is a bot + * Get whether the user is a bot + * @return \bool True or false * @deprecated */ function isBot() { @@ -1851,8 +2070,8 @@ class User { /** * Check if user is allowed to access a feature / make an action - * @param $action String: action to be checked - * @return boolean True: action is allowed, False: action should not be allowed + * @param $action \string action to be checked + * @return \bool True if action is allowed, else false */ function isAllowed($action='') { if ( $action === '' ) @@ -1866,7 +2085,7 @@ class User { /** * Check whether to enable recent changes patrol features for this user - * @return bool + * @return \bool True or false */ public function useRCPatrol() { global $wgUseRCPatrol; @@ -1874,8 +2093,8 @@ class User { } /** - * Check whether to enable recent changes patrol features for this user - * @return bool + * Check whether to enable new pages patrol features for this user + * @return \bool True or false */ public function useNPPatrol() { global $wgUseRCPatrol, $wgUseNPPatrol; @@ -1883,31 +2102,34 @@ class User { } /** - * Load a skin if it doesn't exist or return it + * Get the current skin, loading it if required + * @return \type{Skin} Current skin * @todo FIXME : need to check the old failback system [AV] */ function &getSkin() { - global $wgRequest; + global $wgRequest, $wgAllowUserSkin, $wgDefaultSkin; if ( ! isset( $this->mSkin ) ) { wfProfileIn( __METHOD__ ); - # get the user skin - $userSkin = $this->getOption( 'skin' ); - $userSkin = $wgRequest->getVal('useskin', $userSkin); - + if( $wgAllowUserSkin ) { + # get the user skin + $userSkin = $this->getOption( 'skin' ); + $userSkin = $wgRequest->getVal('useskin', $userSkin); + } else { + # if we're not allowing users to override, then use the default + $userSkin = $wgDefaultSkin; + } + $this->mSkin =& Skin::newFromKey( $userSkin ); wfProfileOut( __METHOD__ ); } return $this->mSkin; } - /**#@+ - * @param $title Title: article title to look at - */ - /** - * Check watched status of an article - * @return bool True if article is watched + * Check the watched status of an article. + * @param $title \type{Title} Title of the article to look at + * @return \bool True if article is watched */ function isWatched( $title ) { $wl = WatchedItem::fromUserTitle( $this, $title ); @@ -1915,7 +2137,8 @@ class User { } /** - * Watch an article + * Watch an article. + * @param $title \type{Title} Title of the article to look at */ function addWatch( $title ) { $wl = WatchedItem::fromUserTitle( $this, $title ); @@ -1924,7 +2147,8 @@ class User { } /** - * Stop watching an article + * Stop watching an article. + * @param $title \type{Title} Title of the article to look at */ function removeWatch( $title ) { $wl = WatchedItem::fromUserTitle( $this, $title ); @@ -1936,6 +2160,7 @@ class User { * Clear the user's notification timestamp for the given title. * If e-notif e-mails are on, they will receive notification mails on * the next change of the page if it's watched etc. + * @param $title \type{Title} Title of the article to look at */ function clearNotification( &$title ) { global $wgUser, $wgUseEnotif, $wgShowUpdatedMarker; @@ -1991,14 +2216,12 @@ class User { } } - /**#@-*/ - /** * Resets all of the given user's page-change notification timestamps. * If e-notif e-mails are on, they will receive notification mails on * the next change of any watched page. * - * @param $currentUser Int: user ID number + * @param $currentUser \int User ID */ function clearAllNotifications( $currentUser ) { global $wgUseEnotif, $wgShowUpdatedMarker; @@ -2021,8 +2244,9 @@ class User { } /** + * Encode this user's options as a string + * @return \string Encoded options * @private - * @return string Encoding options */ function encodeOptions() { $this->load(); @@ -2038,6 +2262,8 @@ class User { } /** + * Set this user's options from an encoded string + * @param $str \string Encoded options to import * @private */ function decodeOptions( $str ) { @@ -2051,46 +2277,30 @@ class User { } } + /** + * Set a cookie on the user's client. Wrapper for + * WebResponse::setCookie + * @param $name \string Name of the cookie to set + * @param $value \string Value to set + * @param $exp \int Expiration time, as a UNIX time value; + * if 0 or not specified, use the default $wgCookieExpiration + */ protected function setCookie( $name, $value, $exp=0 ) { - global $wgCookiePrefix,$wgCookieDomain,$wgCookieSecure,$wgCookieExpiration, $wgCookieHttpOnly; - if( $exp == 0 ) { - $exp = time() + $wgCookieExpiration; - } - $httpOnlySafe = wfHttpOnlySafe(); - wfDebugLog( 'cookie', - 'setcookie: "' . implode( '", "', - array( - $wgCookiePrefix . $name, - $value, - $exp, - '/', - $wgCookieDomain, - $wgCookieSecure, - $httpOnlySafe && $wgCookieHttpOnly ) ) . '"' ); - if( $httpOnlySafe && isset( $wgCookieHttpOnly ) ) { - setcookie( $wgCookiePrefix . $name, - $value, - $exp, - '/', - $wgCookieDomain, - $wgCookieSecure, - $wgCookieHttpOnly ); - } else { - // setcookie() fails on PHP 5.1 if you give it future-compat paramters. - // stab stab! - setcookie( $wgCookiePrefix . $name, - $value, - $exp, - '/', - $wgCookieDomain, - $wgCookieSecure ); - } + global $wgRequest; + $wgRequest->response()->setcookie( $name, $value, $exp ); } + /** + * Clear a cookie on the user's client + * @param $name \string Name of the cookie to clear + */ protected function clearCookie( $name ) { $this->setCookie( $name, '', time() - 86400 ); } + /** + * Set the default cookies for this session on the user's client. + */ function setCookies() { $this->load(); if ( 0 == $this->mId ) return; @@ -2110,7 +2320,10 @@ class User { } wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) ); - $_SESSION = $session + $_SESSION; + #check for null, since the hook could cause a null value + if ( !is_null( $session ) && isset( $_SESSION ) ){ + $_SESSION = $session + $_SESSION; + } foreach ( $cookies as $name => $value ) { if ( $value === false ) { $this->clearCookie( $name ); @@ -2121,7 +2334,7 @@ class User { } /** - * Logout user. + * Log this user out. */ function logout() { global $wgUser; @@ -2131,8 +2344,9 @@ class User { } /** - * Really logout user - * Clears the cookies and session, resets the instance cache + * Clear the user's cookies and session, and reset the instance cache. + * @private + * @see logout() */ function doLogout() { $this->clearInstanceCache( 'defaults' ); @@ -2147,7 +2361,7 @@ class User { } /** - * Save object settings into database + * Save this user's settings into the database. * @todo Only rarely do all these fields need to be set! */ function saveSettings() { @@ -2178,10 +2392,11 @@ class User { ); wfRunHooks( 'UserSaveSettings', array( $this ) ); $this->clearSharedCache(); + $this->getUserPage()->invalidateCache(); } /** - * Checks if a user with the given name exists, returns the ID. + * If only this user's username is known, and it exists, return the user ID. */ function idForName() { $s = trim( $this->getName() ); @@ -2198,18 +2413,18 @@ class User { /** * Add a user to the database, return the user object * - * @param $name String: the user's name - * @param $params Associative array of non-default parameters to save to the database: - * password The user's password. Password logins will be disabled if this is omitted. - * newpassword A temporary password mailed to the user - * email The user's email address - * email_authenticated The email authentication timestamp - * real_name The user's real name - * options An associative array of non-default options - * token Random authentication token. Do not set. - * registration Registration timestamp. Do not set. + * @param $name \string Username to add + * @param $params \type{\arrayof{\string}} Non-default parameters to save to the database: + * - password The user's password. Password logins will be disabled if this is omitted. + * - newpassword A temporary password mailed to the user + * - email The user's email address + * - email_authenticated The email authentication timestamp + * - real_name The user's real name + * - options An associative array of non-default options + * - token Random authentication token. Do not set. + * - registration Registration timestamp. Do not set. * - * @return User object, or null if the username already exists + * @return \type{User} A new User object, or null if the username already exists */ static function createNew( $name, $params = array() ) { $user = new User; @@ -2247,7 +2462,7 @@ class User { } /** - * Add an existing user object to the database + * Add this existing user object to the database */ function addToDatabase() { $this->load(); @@ -2271,13 +2486,13 @@ class User { ); $this->mId = $dbw->insertId(); - # Clear instance cache other than user table data, which is already accurate + // Clear instance cache other than user table data, which is already accurate $this->clearInstanceCache(); } /** - * If the (non-anonymous) user is blocked, this function will block any IP address - * that they successfully log on from. + * If this (non-anonymous) user is blocked, block any IP address + * they've successfully logged in from. */ function spreadBlock() { wfDebug( __METHOD__."()\n" ); @@ -2306,10 +2521,10 @@ class User { * which will give them a chance to modify this key based on their own * settings. * - * @return string + * @return \string Page rendering hash */ function getPageRenderingHash() { - global $wgContLang, $wgUseDynamicDates, $wgLang; + global $wgUseDynamicDates, $wgRenderHashAppend, $wgLang, $wgContLang; if( $this->mHash ){ return $this->mHash; } @@ -2329,6 +2544,8 @@ class User { $extra = $wgContLang->getExtraHashOptions(); $confstr .= $extra; + $confstr .= $wgRenderHashAppend; + // Give a chance for extensions to modify the hash, if they have // extra options or other effects on the parser cache. wfRunHooks( 'PageRenderingHash', array( &$confstr ) ); @@ -2339,21 +2556,28 @@ class User { return $confstr; } + /** + * Get whether the user is explicitly blocked from account creation. + * @return \bool True if blocked + */ function isBlockedFromCreateAccount() { $this->getBlockedStatus(); return $this->mBlock && $this->mBlock->mCreateAccount; } /** - * Determine if the user is blocked from using Special:Emailuser. - * - * @return boolean + * Get whether the user is blocked from using Special:Emailuser. + * @return \bool True if blocked */ function isBlockedFromEmailuser() { $this->getBlockedStatus(); return $this->mBlock && $this->mBlock->mBlockEmail; } + /** + * Get whether the user is allowed to create an account. + * @return \bool True if allowed + */ function isAllowedToCreateAccount() { return $this->isAllowed( 'createaccount' ) && !$this->isBlockedFromCreateAccount(); } @@ -2368,7 +2592,7 @@ class User { /** * Get this user's personal page title. * - * @return Title + * @return \type{Title} User's personal page title */ function getUserPage() { return Title::makeTitle( NS_USER, $this->getName() ); @@ -2377,7 +2601,7 @@ class User { /** * Get this user's talk page title. * - * @return Title + * @return \type{Title} User's talk page title */ function getTalkPage() { $title = $this->getUserPage(); @@ -2385,6 +2609,8 @@ class User { } /** + * Get the maximum valid user ID. + * @return \int User ID * @static */ function getMaxID() { @@ -2401,7 +2627,7 @@ class User { /** * Determine whether the user is a newbie. Newbies are either * anonymous IPs, or the most recently created accounts. - * @return bool True if it is a newbie. + * @return \bool True if the user is a newbie */ function isNewbie() { return !$this->isAllowed( 'autoconfirmed' ); @@ -2411,7 +2637,7 @@ class User { * Is the user active? We check to see if they've made at least * X number of edits in the last Y days. * - * @return bool true if the user is active, false if not + * @return \bool True if the user is active, false if not. */ public function isActiveEditor() { global $wgActiveUserEditCount, $wgActiveUserDays; @@ -2435,8 +2661,8 @@ class User { /** * Check to see if the given clear-text password is one of the accepted passwords - * @param $password String: user password. - * @return bool True if the given password is correct otherwise False. + * @param $password \string user password. + * @return \bool True if the given password is correct, otherwise False. */ function checkPassword( $password ) { global $wgAuth; @@ -2476,7 +2702,7 @@ class User { /** * Check if the given clear-text password matches the temporary password * sent by e-mail for password reset operations. - * @return bool + * @return \bool True if matches, false otherwise */ function checkTemporaryPassword( $plaintext ) { return self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ); @@ -2488,9 +2714,8 @@ class User { * login credentials aren't being hijacked with a foreign form * submission. * - * @param $salt Mixed: optional function-specific data for hash. - * Use a string or an array of strings. - * @return string + * @param $salt \types{\string,\arrayof{\string}} Optional function-specific data for hashing + * @return \string The new edit token */ function editToken( $salt = '' ) { if ( $this->isAnon() ) { @@ -2510,9 +2735,10 @@ class User { } /** - * Generate a hex-y looking random token for various uses. - * Could be made more cryptographically sure if someone cares. - * @return string + * Generate a looking random token for various uses. + * + * @param $salt \string Optional salt value + * @return \string The new random token */ function generateToken( $salt = '' ) { $token = dechex( mt_rand() ) . dechex( mt_rand() ); @@ -2525,9 +2751,9 @@ class User { * user's own login session, not a form submission from a third-party * site. * - * @param $val String: the input value to compare - * @param $salt String: optional function-specific data for hash - * @return bool + * @param $val \string Input value to compare + * @param $salt \string Optional function-specific data for hashing + * @return \bool Whether the token matches */ function matchEditToken( $val, $salt = '' ) { $sessionToken = $this->editToken( $salt ); @@ -2538,7 +2764,12 @@ class User { } /** - * Check whether the edit token is fine except for the suffix + * Check given value against the token value stored in the session, + * ignoring the suffix. + * + * @param $val \string Input value to compare + * @param $salt \string Optional function-specific data for hashing + * @return \bool Whether the token matches */ function matchEditTokenNoSuffix( $val, $salt = '' ) { $sessionToken = $this->editToken( $salt ); @@ -2549,10 +2780,7 @@ class User { * Generate a new e-mail confirmation token and send a confirmation/invalidation * mail to the user's given address. * - * Calls saveSettings() internally; as it has side effects, not committing changes - * would be pretty silly. - * - * @return mixed True on success, a WikiError object on failure. + * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure. */ function sendConfirmationMail() { global $wgLang; @@ -2575,11 +2803,11 @@ class User { * Send an e-mail to this user's account. Does not check for * confirmed status or validity. * - * @param $subject string - * @param $body string - * @param $from string: optional from address; default $wgPasswordSender will be used otherwise. - * @param $replyto string - * @return mixed True on success, a WikiError object on failure. + * @param $subject \string Message subject + * @param $body \string Message body + * @param $from \string Optional From address; if unspecified, default $wgPasswordSender will be used + * @param $replyto \string Reply-To address + * @return \types{\bool,\type{WikiError}} True on success, a WikiError object on failure */ function sendMail( $subject, $body, $from = null, $replyto = null ) { if( is_null( $from ) ) { @@ -2594,13 +2822,13 @@ class User { /** * Generate, store, and return a new e-mail confirmation code. - * A hash (unsalted since it's used as a key) is stored. + * A hash (unsalted, since it's used as a key) is stored. * - * Call saveSettings() after calling this function to commit + * @note Call saveSettings() after calling this function to commit * this change to the database. * - * @param &$expiration mixed output: accepts the expiration time - * @return string + * @param[out] &$expiration \mixed Accepts the expiration time + * @return \string New token * @private */ function confirmationToken( &$expiration ) { @@ -2617,8 +2845,8 @@ class User { /** * Return a URL the user can use to confirm their email address. - * @param $token accepts the email confirmation token - * @return string + * @param $token \string Accepts the email confirmation token + * @return \string New token URL * @private */ function confirmationTokenUrl( $token ) { @@ -2626,8 +2854,8 @@ class User { } /** * Return a URL the user can use to invalidate their email address. - * @param $token accepts the email confirmation token - * @return string + * @param $token \string Accepts the email confirmation token + * @return \string New token URL * @private */ function invalidationTokenUrl( $token ) { @@ -2639,10 +2867,14 @@ class User { * This uses $wgArticlePath directly as a quickie hack to use the * hardcoded English names of the Special: pages, for ASCII safety. * - * Since these URLs get dropped directly into emails, using the + * @note Since these URLs get dropped directly into emails, using the * short English names avoids insanely long URL-encoded links, which * also sometimes can get corrupted in some browsers/mailers * (bug 6957 with Gmail and Internet Explorer). + * + * @param $page \string Special page + * @param $token \string Token + * @return \string Formatted URL */ protected function getTokenUrl( $page, $token ) { global $wgArticlePath; @@ -2656,7 +2888,7 @@ class User { /** * Mark the e-mail address confirmed. * - * Call saveSettings() after calling this function to commit the change. + * @note Call saveSettings() after calling this function to commit the change. */ function confirmEmail() { $this->setEmailAuthenticationTimestamp( wfTimestampNow() ); @@ -2664,10 +2896,10 @@ class User { } /** - * Invalidate the user's email confirmation, unauthenticate the email - * if it was already confirmed. + * Invalidate the user's e-mail confirmation, and unauthenticate the e-mail + * address if it was already confirmed. * - * Call saveSettings() after calling this function to commit the change. + * @note Call saveSettings() after calling this function to commit the change. */ function invalidateEmail() { $this->load(); @@ -2677,6 +2909,10 @@ class User { return true; } + /** + * Set the e-mail authentication timestamp. + * @param $timestamp \string TS_MW timestamp + */ function setEmailAuthenticationTimestamp( $timestamp ) { $this->load(); $this->mEmailAuthenticated = $timestamp; @@ -2686,9 +2922,13 @@ class User { /** * Is this user allowed to send e-mails within limits of current * site configuration? - * @return bool + * @return \bool True if allowed */ function canSendEmail() { + global $wgEnableEmail, $wgEnableUserEmail; + if( !$wgEnableEmail || !$wgEnableUserEmail ) { + return false; + } $canSend = $this->isEmailConfirmed(); wfRunHooks( 'UserCanSendEmail', array( &$this, &$canSend ) ); return $canSend; @@ -2697,7 +2937,7 @@ class User { /** * Is this user allowed to receive e-mails within limits of current * site configuration? - * @return bool + * @return \bool True if allowed */ function canReceiveEmail() { return $this->isEmailConfirmed() && !$this->getOption( 'disablemail' ); @@ -2707,11 +2947,11 @@ class User { * Is this user's e-mail address valid-looking and confirmed within * limits of the current site configuration? * - * If $wgEmailAuthentication is on, this may require the user to have + * @note If $wgEmailAuthentication is on, this may require the user to have * confirmed their address by returning a code or using a password * sent to the address from the wiki. * - * @return bool + * @return \bool True if confirmed */ function isEmailConfirmed() { global $wgEmailAuthentication; @@ -2731,8 +2971,8 @@ class User { } /** - * Return true if there is an outstanding request for e-mail confirmation. - * @return bool + * Check whether there is an outstanding request for e-mail confirmation. + * @return \bool True if pending */ function isEmailConfirmationPending() { global $wgEmailAuthentication; @@ -2743,20 +2983,40 @@ class User { } /** - * Get the timestamp of account creation, or false for - * non-existent/anonymous user accounts + * Get the timestamp of account creation. * - * @return mixed + * @return \types{\string,\bool} string Timestamp of account creation, or false for + * non-existent/anonymous user accounts. */ public function getRegistration() { - return $this->mId > 0 + return $this->getId() > 0 ? $this->mRegistration : false; } + + /** + * Get the timestamp of the first edit + * + * @return \types{\string,\bool} string Timestamp of first edit, or false for + * non-existent/anonymous user accounts. + */ + public function getFirstEditTimestamp() { + if( $this->getId() == 0 ) return false; // anons + $dbr = wfGetDB( DB_SLAVE ); + $time = $dbr->selectField( 'revision', 'rev_timestamp', + array( 'rev_user' => $this->getId() ), + __METHOD__, + array( 'ORDER BY' => 'rev_timestamp ASC' ) + ); + if( !$time ) return false; // no edits + return wfTimestamp( TS_MW, $time ); + } /** - * @param $groups Array: list of groups - * @return array list of permission key names for given groups combined + * Get the permissions associated with a given list of groups + * + * @param $groups \type{\arrayof{\string}} List of internal group names + * @return \type{\arrayof{\string}} List of permission key names for given groups combined */ static function getGroupPermissions( $groups ) { global $wgGroupPermissions; @@ -2764,15 +3024,35 @@ class User { foreach( $groups as $group ) { if( isset( $wgGroupPermissions[$group] ) ) { $rights = array_merge( $rights, + // array_filter removes empty items array_keys( array_filter( $wgGroupPermissions[$group] ) ) ); } } - return $rights; + return array_unique($rights); + } + + /** + * Get all the groups who have a given permission + * + * @param $role \string Role to check + * @return \type{\arrayof{\string}} List of internal group names with the given permission + */ + static function getGroupsWithPermission( $role ) { + global $wgGroupPermissions; + $allowedGroups = array(); + foreach ( $wgGroupPermissions as $group => $rights ) { + if ( isset( $rights[$role] ) && $rights[$role] ) { + $allowedGroups[] = $group; + } + } + return $allowedGroups; } /** - * @param $group String: key name - * @return string localized descriptive name for group, if provided + * Get the localized descriptive name for a group, if it exists + * + * @param $group \string Internal group name + * @return \string Localized descriptive group name */ static function getGroupName( $group ) { global $wgMessageCache; @@ -2785,8 +3065,10 @@ class User { } /** - * @param $group String: key name - * @return string localized descriptive name for member of a group, if provided + * Get the localized descriptive name for a member of a group, if it exists + * + * @param $group \string Internal group name + * @return \string Localized name for group member */ static function getGroupMember( $group ) { global $wgMessageCache; @@ -2801,9 +3083,8 @@ class User { /** * Return the set of defined explicit groups. * The implicit groups (by default *, 'user' and 'autoconfirmed') - * are not included, as they are defined automatically, - * not in the database. - * @return array + * are not included, as they are defined automatically, not in the database. + * @return \type{\arrayof{\string}} Array of internal group names */ static function getAllGroups() { global $wgGroupPermissions; @@ -2814,7 +3095,8 @@ class User { } /** - * Get a list of all available permissions + * Get a list of all available permissions. + * @return \type{\arrayof{\string}} Array of permission names */ static function getAllRights() { if ( self::$mAllRights === false ) { @@ -2831,8 +3113,7 @@ class User { /** * Get a list of implicit groups - * - * @return array + * @return \type{\arrayof{\string}} Array of internal group names */ public static function getImplicitGroups() { global $wgImplicitGroups; @@ -2844,8 +3125,8 @@ class User { /** * Get the title of a page describing a particular group * - * @param $group Name of the group - * @return mixed + * @param $group \string Internal group name + * @return \types{\type{Title},\bool} Title of the page if it exists, false otherwise */ static function getGroupPage( $group ) { global $wgMessageCache; @@ -2860,11 +3141,12 @@ class User { } /** - * Create a link to the group in HTML, if available + * Create a link to the group in HTML, if available; + * else return the group name. * - * @param $group Name of the group - * @param $text The text of the link - * @return mixed + * @param $group \string Internal name of the group + * @param $text \string The text of the link + * @return \string HTML link to the group */ static function makeGroupLinkHTML( $group, $text = '' ) { if( $text == '' ) { @@ -2881,11 +3163,12 @@ class User { } /** - * Create a link to the group in Wikitext, if available + * Create a link to the group in Wikitext, if available; + * else return the group name. * - * @param $group Name of the group - * @param $text The text of the link (by default, the name of the group) - * @return mixed + * @param $group \string Internal name of the group + * @param $text \string The text of the link + * @return \string Wikilink to the group */ static function makeGroupLinkWiki( $group, $text = '' ) { if( $text == '' ) { @@ -2944,6 +3227,12 @@ class User { $this->invalidateCache(); } + /** + * Get the description of a given right + * + * @param $right \string Right to query + * @return \string Localized description of the right + */ static function getRightDescription( $right ) { global $wgMessageCache; $wgMessageCache->loadAllMessages(); @@ -2957,8 +3246,9 @@ class User { /** * Make an old-style password hash * - * @param $password String: plain-text password - * @param $userId String: user ID + * @param $password \string Plain-text password + * @param $userId \string User ID + * @return \string Password hash */ static function oldCrypt( $password, $userId ) { global $wgPasswordSalt; @@ -2972,19 +3262,26 @@ class User { /** * Make a new-style password hash * - * @param $password String: plain-text password - * @param $salt String: salt, may be random or the user ID. False to generate a salt. + * @param $password \string Plain-text password + * @param $salt \string Optional salt, may be random or the user ID. + * If unspecified or false, will generate one automatically + * @return \string Password hash */ static function crypt( $password, $salt = false ) { global $wgPasswordSalt; - if($wgPasswordSalt) { + $hash = ''; + if( !wfRunHooks( 'UserCryptPassword', array( &$password, &$salt, &$wgPasswordSalt, &$hash ) ) ) { + return $hash; + } + + if( $wgPasswordSalt ) { if ( $salt === false ) { $salt = substr( wfGenerateToken(), 0, 8 ); } return ':B:' . $salt . ':' . md5( $salt . '-' . md5( $password ) ); } else { - return ':A:' . md5( $password); + return ':A:' . md5( $password ); } } @@ -2992,13 +3289,20 @@ class User { * Compare a password hash with a plain-text password. Requires the user * ID if there's a chance that the hash is an old-style hash. * - * @param $hash String: password hash - * @param $password String: plain-text password to compare - * @param $userId String: user ID for old-style password salt + * @param $hash \string Password hash + * @param $password \string Plain-text password to compare + * @param $userId \string User ID for old-style password salt + * @return \bool */ static function comparePasswords( $hash, $password, $userId = false ) { $m = false; $type = substr( $hash, 0, 3 ); + + $result = false; + if( !wfRunHooks( 'UserComparePasswords', array( &$hash, &$password, &$userId, &$result ) ) ) { + return $result; + } + if ( $type == ':A:' ) { # Unsalted return md5( $password ) === substr( $hash, 3 ); @@ -3011,4 +3315,41 @@ class User { return self::oldCrypt( $password, $userId ) === $hash; } } + + /** + * Add a newuser log entry for this user + * @param $byEmail Boolean: account made by email? + */ + public function addNewUserLogEntry( $byEmail = false ) { + global $wgUser, $wgContLang, $wgNewUserLog; + if( empty($wgNewUserLog) ) { + return true; // disabled + } + $talk = $wgContLang->getFormattedNsText( NS_TALK ); + if( $this->getName() == $wgUser->getName() ) { + $action = 'create'; + $message = ''; + } else { + $action = 'create2'; + $message = $byEmail ? wfMsgForContent( 'newuserlog-byemail' ) : ''; + } + $log = new LogPage( 'newusers' ); + $log->addEntry( $action, $this->getUserPage(), $message, array( $this->getId() ) ); + return true; + } + + /** + * Add an autocreate newuser log entry for this user + * Used by things like CentralAuth and perhaps other authplugins. + */ + public function addNewUserLogEntryAutoCreate() { + global $wgNewUserLog; + if( empty($wgNewUserLog) ) { + return true; // disabled + } + $log = new LogPage( 'newusers', false ); + $log->addEntry( 'autocreate', $this->getUserPage(), '', array( $this->getId() ) ); + return true; + } + } |