summaryrefslogtreecommitdiff
path: root/includes/User.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/User.php')
-rw-r--r--includes/User.php636
1 files changed, 424 insertions, 212 deletions
diff --git a/includes/User.php b/includes/User.php
index a925a3c4..3cd69fdc 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -59,6 +59,12 @@ class User implements IDBAccessObject {
const MAX_WATCHED_ITEMS_CACHE = 100;
/**
+ * Exclude user options that are set to their default value.
+ * @since 1.25
+ */
+ const GETOPTIONS_EXCLUDE_DEFAULTS = 1;
+
+ /**
* @var PasswordFactory Lazily loaded factory object for passwords
*/
private static $mPasswordFactory = null;
@@ -96,6 +102,7 @@ class User implements IDBAccessObject {
*/
protected static $mCoreRights = array(
'apihighlimits',
+ 'applychangetags',
'autoconfirmed',
'autopatrol',
'bigdelete',
@@ -103,6 +110,7 @@ class User implements IDBAccessObject {
'blockemail',
'bot',
'browsearchive',
+ 'changetags',
'createaccount',
'createpage',
'createtalk',
@@ -128,6 +136,7 @@ class User implements IDBAccessObject {
'import',
'importupload',
'ipblock-exempt',
+ 'managechangetags',
'markbotedits',
'mergehistory',
'minoredit',
@@ -197,8 +206,10 @@ class User implements IDBAccessObject {
public $mNewpassTime;
public $mEmail;
-
+ /** @var string TS_MW timestamp from the DB */
public $mTouched;
+ /** @var string TS_MW timestamp from cache */
+ protected $mQuickTouched;
protected $mToken;
@@ -288,6 +299,9 @@ class User implements IDBAccessObject {
/** @var array */
private $mWatchedItems = array();
+ /** @var integer User::READ_* constant bitfield used to load data */
+ protected $queryFlagsUsed = self::READ_NORMAL;
+
public static $idCacheByName = array();
/**
@@ -313,104 +327,144 @@ class User implements IDBAccessObject {
/**
* Load the user table data for this object from the source given by mFrom.
+ *
+ * @param integer $flags User::READ_* constant bitfield
*/
- public function load() {
+ public function load( $flags = self::READ_LATEST ) {
if ( $this->mLoadedItems === true ) {
return;
}
- wfProfileIn( __METHOD__ );
// Set it now to avoid infinite recursion in accessors
$this->mLoadedItems = true;
+ $this->queryFlagsUsed = $flags;
switch ( $this->mFrom ) {
case 'defaults':
$this->loadDefaults();
break;
case 'name':
+ // @TODO: this gets the ID from a slave, assuming renames
+ // are rare. This should be controllable and more consistent.
$this->mId = self::idFromName( $this->mName );
if ( !$this->mId ) {
// Nonexistent user placeholder object
$this->loadDefaults( $this->mName );
} else {
- $this->loadFromId();
+ $this->loadFromId( $flags );
}
break;
case 'id':
- $this->loadFromId();
+ $this->loadFromId( $flags );
break;
case 'session':
if ( !$this->loadFromSession() ) {
// Loading from session failed. Load defaults.
$this->loadDefaults();
}
- wfRunHooks( 'UserLoadAfterLoadFromSession', array( $this ) );
+ Hooks::run( 'UserLoadAfterLoadFromSession', array( $this ) );
break;
default:
- wfProfileOut( __METHOD__ );
throw new MWException( "Unrecognised value for User->mFrom: \"{$this->mFrom}\"" );
}
- wfProfileOut( __METHOD__ );
}
/**
* Load user table data, given mId has already been set.
+ * @param integer $flags User::READ_* constant bitfield
* @return bool False if the ID does not exist, true otherwise
*/
- public function loadFromId() {
- global $wgMemc;
+ public function loadFromId( $flags = self::READ_LATEST ) {
if ( $this->mId == 0 ) {
$this->loadDefaults();
return false;
}
// Try cache
- $key = wfMemcKey( 'user', 'id', $this->mId );
- $data = $wgMemc->get( $key );
- if ( !is_array( $data ) || $data['mVersion'] != self::VERSION ) {
- // Object is expired, load from DB
- $data = false;
- }
-
- if ( !$data ) {
+ $cache = $this->loadFromCache();
+ if ( !$cache ) {
wfDebug( "User: cache miss for user {$this->mId}\n" );
// Load from DB
- if ( !$this->loadFromDatabase() ) {
+ if ( !$this->loadFromDatabase( $flags ) ) {
// Can't load from ID, user is anonymous
return false;
}
- $this->saveToCache();
- } else {
- wfDebug( "User: got user {$this->mId} from cache\n" );
- // Restore from cache
- foreach ( self::$mCacheVars as $name ) {
- $this->$name = $data[$name];
+ if ( $flags & self::READ_LATEST ) {
+ // Only save master data back to the cache to keep it consistent.
+ // @TODO: save it anyway and have callers specifiy $flags and have
+ // load() called as needed. That requires updating MANY callers...
+ $this->saveToCache();
}
}
$this->mLoadedItems = true;
+ $this->queryFlagsUsed = $flags;
+
+ return true;
+ }
+
+ /**
+ * Load user data from shared cache, given mId has already been set.
+ *
+ * @return bool false if the ID does not exist or data is invalid, true otherwise
+ * @since 1.25
+ */
+ protected function loadFromCache() {
+ global $wgMemc;
+
+ if ( $this->mId == 0 ) {
+ $this->loadDefaults();
+ return false;
+ }
+
+ $key = wfMemcKey( 'user', 'id', $this->mId );
+ $data = $wgMemc->get( $key );
+ if ( !is_array( $data ) || $data['mVersion'] < self::VERSION ) {
+ // Object is expired
+ return false;
+ }
+
+ wfDebug( "User: got user {$this->mId} from cache\n" );
+
+ // Restore from cache
+ foreach ( self::$mCacheVars as $name ) {
+ $this->$name = $data[$name];
+ }
return true;
}
/**
* Save user data to the shared cache
+ *
+ * This method should not be called outside the User class
*/
public function saveToCache() {
+ global $wgMemc;
+
$this->load();
$this->loadGroups();
$this->loadOptions();
+
if ( $this->isAnon() ) {
// Anonymous users are uncached
return;
}
+
+ // The cache needs good consistency due to its high TTL, so the user
+ // should have been loaded from the master to avoid lag amplification.
+ if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
+ wfWarn( "Cannot cache slave-loaded User object with ID '{$this->mId}'." );
+ return;
+ }
+
$data = array();
foreach ( self::$mCacheVars as $name ) {
$data[$name] = $this->$name;
}
$data['mVersion'] = self::VERSION;
$key = wfMemcKey( 'user', 'id', $this->mId );
- global $wgMemc;
+
$wgMemc->set( $key, $data );
}
@@ -624,10 +678,11 @@ class User implements IDBAccessObject {
global $wgContLang, $wgMaxNameChars;
if ( $name == ''
- || User::isIP( $name )
- || strpos( $name, '/' ) !== false
- || strlen( $name ) > $wgMaxNameChars
- || $name != $wgContLang->ucfirst( $name ) ) {
+ || User::isIP( $name )
+ || strpos( $name, '/' ) !== false
+ || strlen( $name ) > $wgMaxNameChars
+ || $name != $wgContLang->ucfirst( $name )
+ ) {
wfDebugLog( 'username', __METHOD__ .
": '$name' invalid due to empty, IP, slash, length, or lowercase" );
return false;
@@ -684,7 +739,7 @@ class User implements IDBAccessObject {
static $reservedUsernames = false;
if ( !$reservedUsernames ) {
$reservedUsernames = $wgReservedUsernames;
- wfRunHooks( 'UserGetReservedNames', array( &$reservedUsernames ) );
+ Hooks::run( 'UserGetReservedNames', array( &$reservedUsernames ) );
}
// Certain names may be reserved for batch processes.
@@ -801,7 +856,7 @@ class User implements IDBAccessObject {
$result = false; //init $result to false for the internal checks
- if ( !wfRunHooks( 'isValidPassword', array( $password, &$result, $this ) ) ) {
+ if ( !Hooks::run( 'isValidPassword', array( $password, &$result, $this ) ) ) {
$status->error( $result );
return $status;
}
@@ -867,7 +922,7 @@ class User implements IDBAccessObject {
);
}
// Give extensions a chance to force an expiration
- wfRunHooks( 'ResetPasswordExpiration', array( $this, &$newExpire ) );
+ Hooks::run( 'ResetPasswordExpiration', array( $this, &$newExpire ) );
$this->mPasswordExpires = $newExpire;
}
@@ -1007,7 +1062,6 @@ class User implements IDBAccessObject {
* @param string|bool $name
*/
public function loadDefaults( $name = false ) {
- wfProfileIn( __METHOD__ );
$passwordFactory = self::getPasswordFactory();
@@ -1037,9 +1091,7 @@ class User implements IDBAccessObject {
$this->mRegistration = wfTimestamp( TS_MW );
$this->mGroups = array();
- wfRunHooks( 'UserLoadDefaults', array( $this, $name ) );
-
- wfProfileOut( __METHOD__ );
+ Hooks::run( 'UserLoadDefaults', array( $this, $name ) );
}
/**
@@ -1072,11 +1124,12 @@ class User implements IDBAccessObject {
/**
* Load user data from the session or login cookie.
+ *
* @return bool True if the user is logged in, false otherwise.
*/
private function loadFromSession() {
$result = null;
- wfRunHooks( 'UserLoadFromSession', array( $this, &$result ) );
+ Hooks::run( 'UserLoadFromSession', array( $this, &$result ) );
if ( $result !== null ) {
return $result;
}
@@ -1110,6 +1163,7 @@ class User implements IDBAccessObject {
}
$proposedUser = User::newFromId( $sId );
+ $proposedUser->load( self::READ_LATEST );
if ( !$proposedUser->isLoggedIn() ) {
// Not a valid ID
return false;
@@ -1154,10 +1208,10 @@ class User implements IDBAccessObject {
* Load user and user_group data from the database.
* $this->mId must be set, this is how the user is identified.
*
- * @param int $flags Supports User::READ_LOCKING
+ * @param integer $flags User::READ_* constant bitfield
* @return bool True if the user exists, false if the user is anonymous
*/
- public function loadFromDatabase( $flags = 0 ) {
+ public function loadFromDatabase( $flags = self::READ_LATEST ) {
// Paranoia
$this->mId = intval( $this->mId );
@@ -1167,8 +1221,11 @@ class User implements IDBAccessObject {
return false;
}
- $dbr = wfGetDB( DB_MASTER );
- $s = $dbr->selectRow(
+ $db = ( $flags & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+
+ $s = $db->selectRow(
'user',
self::selectFields(),
array( 'user_id' => $this->mId ),
@@ -1178,7 +1235,8 @@ class User implements IDBAccessObject {
: array()
);
- wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
+ $this->queryFlagsUsed = $flags;
+ Hooks::run( 'UserLoadFromDatabase', array( $this, &$s ) );
if ( $s !== false ) {
// Initialise user table data
@@ -1203,7 +1261,7 @@ class User implements IDBAccessObject {
* user_groups Array with groups out of the user_groups table
* user_properties Array with properties out of the user_properties table
*/
- public function loadFromRow( $row, $data = null ) {
+ protected function loadFromRow( $row, $data = null ) {
$all = true;
$passwordFactory = self::getPasswordFactory();
@@ -1232,6 +1290,10 @@ class User implements IDBAccessObject {
$all = false;
}
+ if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
+ self::$idCacheByName[$row->user_name] = $row->user_id;
+ }
+
if ( isset( $row->user_editcount ) ) {
$this->mEditCount = $row->user_editcount;
} else {
@@ -1311,8 +1373,10 @@ class User implements IDBAccessObject {
*/
private function loadGroups() {
if ( is_null( $this->mGroups ) ) {
- $dbr = wfGetDB( DB_MASTER );
- $res = $dbr->select( 'user_groups',
+ $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+ $res = $db->select( 'user_groups',
array( 'ug_group' ),
array( 'ug_user' => $this->mId ),
__METHOD__ );
@@ -1333,13 +1397,20 @@ class User implements IDBAccessObject {
* @since 1.24
*/
private function loadPasswords() {
- if ( $this->getId() !== 0 && ( $this->mPassword === null || $this->mNewpassword === null ) ) {
- $this->loadFromRow( wfGetDB( DB_MASTER )->selectRow(
- 'user',
- array( 'user_password', 'user_newpassword', 'user_newpass_time', 'user_password_expires' ),
- array( 'user_id' => $this->getId() ),
- __METHOD__
- ) );
+ if ( $this->getId() !== 0 &&
+ ( $this->mPassword === null || $this->mNewpassword === null )
+ ) {
+ $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+
+ $this->loadFromRow( $db->selectRow(
+ 'user',
+ array( 'user_password', 'user_newpassword',
+ 'user_newpass_time', 'user_password_expires' ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__
+ ) );
}
}
@@ -1361,7 +1432,7 @@ class User implements IDBAccessObject {
global $wgAutopromoteOnceLogInRC, $wgAuth;
$toPromote = array();
- if ( $this->getId() ) {
+ if ( !wfReadOnly() && $this->getId() ) {
$toPromote = Autopromote::getAutopromoteOnceGroups( $this, $event );
if ( count( $toPromote ) ) {
$oldGroups = $this->getGroups(); // previous groups
@@ -1387,6 +1458,7 @@ class User implements IDBAccessObject {
}
}
}
+
return $toPromote;
}
@@ -1444,7 +1516,7 @@ class User implements IDBAccessObject {
}
$defOpt['skin'] = Skin::normalizeKey( $wgDefaultSkin );
- wfRunHooks( 'UserGetDefaultOptions', array( &$defOpt ) );
+ Hooks::run( 'UserGetDefaultOptions', array( &$defOpt ) );
return $defOpt;
}
@@ -1477,7 +1549,6 @@ class User implements IDBAccessObject {
return;
}
- wfProfileIn( __METHOD__ );
wfDebug( __METHOD__ . ": checking...\n" );
// Initialize data...
@@ -1550,9 +1621,8 @@ class User implements IDBAccessObject {
}
// Extensions
- wfRunHooks( 'GetBlockedStatus', array( &$this ) );
+ Hooks::run( 'GetBlockedStatus', array( &$this ) );
- wfProfileOut( __METHOD__ );
}
/**
@@ -1584,7 +1654,6 @@ class User implements IDBAccessObject {
* @return bool True if blacklisted.
*/
public function inDnsBlacklist( $ip, $bases ) {
- wfProfileIn( __METHOD__ );
$found = false;
// @todo FIXME: IPv6 ??? (http://bugs.php.net/bug.php?id=33170)
@@ -1619,7 +1688,6 @@ class User implements IDBAccessObject {
}
}
- wfProfileOut( __METHOD__ );
return $found;
}
@@ -1636,7 +1704,6 @@ class User implements IDBAccessObject {
if ( !$wgProxyList ) {
return false;
}
- wfProfileIn( __METHOD__ );
if ( !is_array( $wgProxyList ) ) {
// Load from the specified file
@@ -1653,7 +1720,6 @@ class User implements IDBAccessObject {
} else {
$ret = false;
}
- wfProfileOut( __METHOD__ );
return $ret;
}
@@ -1690,7 +1756,7 @@ class User implements IDBAccessObject {
public function pingLimiter( $action = 'edit', $incrBy = 1 ) {
// Call the 'PingLimiter' hook
$result = false;
- if ( !wfRunHooks( 'PingLimiter', array( &$this, $action, &$result, $incrBy ) ) ) {
+ if ( !Hooks::run( 'PingLimiter', array( &$this, $action, &$result, $incrBy ) ) ) {
return $result;
}
@@ -1705,8 +1771,6 @@ class User implements IDBAccessObject {
}
global $wgMemc;
- wfProfileIn( __METHOD__ );
- wfProfileIn( __METHOD__ . '-' . $action );
$limits = $wgRateLimits[$action];
$keys = array();
@@ -1748,7 +1812,9 @@ class User implements IDBAccessObject {
// If more than one group applies, use the group with the highest limit
foreach ( $this->getGroups() as $group ) {
if ( isset( $limits[$group] ) ) {
- if ( $userLimit === false || $limits[$group] > $userLimit ) {
+ if ( $userLimit === false
+ || $limits[$group][0] / $limits[$group][1] > $userLimit[0] / $userLimit[1]
+ ) {
$userLimit = $limits[$group];
}
}
@@ -1785,8 +1851,6 @@ class User implements IDBAccessObject {
}
}
- wfProfileOut( __METHOD__ . '-' . $action );
- wfProfileOut( __METHOD__ );
return $triggered;
}
@@ -1821,7 +1885,6 @@ class User implements IDBAccessObject {
*/
public function isBlockedFrom( $title, $bFromSlave = false ) {
global $wgBlockAllowsUTEdit;
- wfProfileIn( __METHOD__ );
$blocked = $this->isBlocked( $bFromSlave );
$allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
@@ -1832,9 +1895,8 @@ class User implements IDBAccessObject {
wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
}
- wfRunHooks( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) );
+ Hooks::run( 'UserIsBlockedFrom', array( $this, $title, &$blocked, &$allowUsertalk ) );
- wfProfileOut( __METHOD__ );
return $blocked;
}
@@ -1884,7 +1946,7 @@ class User implements IDBAccessObject {
$ip = $this->getRequest()->getIP();
}
$blocked = false;
- wfRunHooks( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) );
+ Hooks::run( 'UserIsBlockedGlobally', array( &$this, $ip, &$blocked ) );
$this->mBlockedGlobally = (bool)$blocked;
return $this->mBlockedGlobally;
}
@@ -2009,17 +2071,7 @@ class User implements IDBAccessObject {
// Anon newtalk disabled by configuration.
$this->mNewtalk = false;
} else {
- global $wgMemc;
- $key = wfMemcKey( 'newtalk', 'ip', $this->getName() );
- $newtalk = $wgMemc->get( $key );
- if ( strval( $newtalk ) !== '' ) {
- $this->mNewtalk = (bool)$newtalk;
- } else {
- // Since we are caching this, make sure it is up to date by getting it
- // from the master
- $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName(), true );
- $wgMemc->set( $key, (int)$this->mNewtalk, 1800 );
- }
+ $this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
}
} else {
$this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
@@ -2044,7 +2096,7 @@ class User implements IDBAccessObject {
*/
public function getNewMessageLinks() {
$talks = array();
- if ( !wfRunHooks( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
+ if ( !Hooks::run( 'UserRetrieveNewTalks', array( &$this, &$talks ) ) ) {
return $talks;
} elseif ( !$this->getNewtalk() ) {
return array();
@@ -2089,17 +2141,13 @@ class User implements IDBAccessObject {
* @see getNewtalk()
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
* @param string|int $id User's IP address for anonymous users, User ID otherwise
- * @param bool $fromMaster True to fetch from the master, false for a slave
* @return bool True if the user has new messages
*/
- protected function checkNewtalk( $field, $id, $fromMaster = false ) {
- if ( $fromMaster ) {
- $db = wfGetDB( DB_MASTER );
- } else {
- $db = wfGetDB( DB_SLAVE );
- }
- $ok = $db->selectField( 'user_newtalk', $field,
- array( $field => $id ), __METHOD__ );
+ protected function checkNewtalk( $field, $id ) {
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $ok = $dbr->selectField( 'user_newtalk', $field, array( $field => $id ), __METHOD__ );
+
return $ok !== false;
}
@@ -2194,9 +2242,15 @@ class User implements IDBAccessObject {
* user_touched field when we update things.
* @return string Timestamp in TS_MW format
*/
- private static function newTouchedTimestamp() {
+ private function newTouchedTimestamp() {
global $wgClockSkewFudge;
- return wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
+
+ $time = wfTimestamp( TS_MW, time() + $wgClockSkewFudge );
+ if ( $this->mTouched && $time <= $this->mTouched ) {
+ $time = wfTimestamp( TS_MW, wfTimestamp( TS_UNIX, $this->mTouched ) + 1 );
+ }
+
+ return $time;
}
/**
@@ -2207,9 +2261,10 @@ class User implements IDBAccessObject {
* Called implicitly from invalidateCache() and saveSettings().
*/
public function clearSharedCache() {
+ global $wgMemc;
+
$this->load();
if ( $this->mId ) {
- global $wgMemc;
$wgMemc->delete( wfMemcKey( 'user', 'id', $this->mId ) );
}
}
@@ -2225,7 +2280,7 @@ class User implements IDBAccessObject {
}
$this->load();
if ( $this->mId ) {
- $this->mTouched = self::newTouchedTimestamp();
+ $this->mTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
$userid = $this->mId;
@@ -2249,21 +2304,63 @@ class User implements IDBAccessObject {
}
/**
+ * Update the "touched" timestamp for the user
+ *
+ * This is useful on various login/logout events when making sure that
+ * a browser or proxy that has multiple tenants does not suffer cache
+ * pollution where the new user sees the old users content. The value
+ * of getTouched() is checked when determining 304 vs 200 responses.
+ * Unlike invalidateCache(), this preserves the User object cache and
+ * avoids database writes.
+ *
+ * @since 1.25
+ */
+ public function touch() {
+ global $wgMemc;
+
+ $this->load();
+
+ if ( $this->mId ) {
+ $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
+ $timestamp = $this->newTouchedTimestamp();
+ $wgMemc->set( $key, $timestamp );
+ $this->mQuickTouched = $timestamp;
+ }
+ }
+
+ /**
* Validate the cache for this account.
* @param string $timestamp A timestamp in TS_MW format
* @return bool
*/
public function validateCache( $timestamp ) {
- $this->load();
- return ( $timestamp >= $this->mTouched );
+ return ( $timestamp >= $this->getTouched() );
}
/**
* Get the user touched timestamp
- * @return string Timestamp
+ * @return string TS_MW Timestamp
*/
public function getTouched() {
+ global $wgMemc;
+
$this->load();
+
+ if ( $this->mId ) {
+ if ( $this->mQuickTouched === null ) {
+ $key = wfMemcKey( 'user-quicktouched', 'id', $this->mId );
+ $timestamp = $wgMemc->get( $key );
+ if ( $timestamp ) {
+ $this->mQuickTouched = $timestamp;
+ } else {
+ # Set the timestamp to get HTTP 304 cache hits
+ $this->touch();
+ }
+ }
+
+ return max( $this->mTouched, $this->mQuickTouched );
+ }
+
return $this->mTouched;
}
@@ -2339,11 +2436,7 @@ class User implements IDBAccessObject {
$this->setToken();
$passwordFactory = self::getPasswordFactory();
- if ( $str === null ) {
- $this->mPassword = $passwordFactory->newFromCiphertext( null );
- } else {
- $this->mPassword = $passwordFactory->newFromPlaintext( $str );
- }
+ $this->mPassword = $passwordFactory->newFromPlaintext( $str );
$this->mNewpassword = $passwordFactory->newFromCiphertext( null );
$this->mNewpassTime = null;
@@ -2388,14 +2481,11 @@ class User implements IDBAccessObject {
public function setNewpassword( $str, $throttle = true ) {
$this->loadPasswords();
+ $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str );
if ( $str === null ) {
- $this->mNewpassword = '';
$this->mNewpassTime = null;
- } else {
- $this->mNewpassword = self::getPasswordFactory()->newFromPlaintext( $str );
- if ( $throttle ) {
- $this->mNewpassTime = wfTimestampNow();
- }
+ } elseif ( $throttle ) {
+ $this->mNewpassTime = wfTimestampNow();
}
}
@@ -2420,7 +2510,7 @@ class User implements IDBAccessObject {
*/
public function getEmail() {
$this->load();
- wfRunHooks( 'UserGetEmail', array( $this, &$this->mEmail ) );
+ Hooks::run( 'UserGetEmail', array( $this, &$this->mEmail ) );
return $this->mEmail;
}
@@ -2430,7 +2520,7 @@ class User implements IDBAccessObject {
*/
public function getEmailAuthenticationTimestamp() {
$this->load();
- wfRunHooks( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
+ Hooks::run( 'UserGetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
return $this->mEmailAuthenticated;
}
@@ -2445,7 +2535,7 @@ class User implements IDBAccessObject {
}
$this->invalidateEmail();
$this->mEmail = $str;
- wfRunHooks( 'UserSetEmail', array( $this, &$this->mEmail ) );
+ Hooks::run( 'UserSetEmail', array( $this, &$this->mEmail ) );
}
/**
@@ -2474,7 +2564,7 @@ class User implements IDBAccessObject {
$type = $oldaddr != '' ? 'changed' : 'set';
$result = $this->sendConfirmationMail( $type );
if ( $result->isGood() ) {
- // Say the the caller that a confirmation mail has been sent
+ // Say to the caller that a confirmation mail has been sent
$result->value = 'eauth';
}
} else {
@@ -2538,9 +2628,12 @@ class User implements IDBAccessObject {
/**
* Get all user's options
*
+ * @param int $flags Bitwise combination of:
+ * User::GETOPTIONS_EXCLUDE_DEFAULTS Exclude user options that are set
+ * to the default value. (Since 1.25)
* @return array
*/
- public function getOptions() {
+ public function getOptions( $flags = 0 ) {
global $wgHiddenPrefs;
$this->loadOptions();
$options = $this->mOptions;
@@ -2557,6 +2650,10 @@ class User implements IDBAccessObject {
}
}
+ if ( $flags & self::GETOPTIONS_EXCLUDE_DEFAULTS ) {
+ $options = array_diff_assoc( $options, self::getDefaultOptions() );
+ }
+
return $options;
}
@@ -2624,7 +2721,9 @@ class User implements IDBAccessObject {
$token = $this->getOption( $oname );
if ( !$token ) {
$token = $this->resetTokenFromOption( $oname );
- $this->saveSettings();
+ if ( !wfReadOnly() ) {
+ $this->saveSettings();
+ }
}
return $token;
}
@@ -2814,7 +2913,7 @@ class User implements IDBAccessObject {
}
}
- wfRunHooks( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) );
+ Hooks::run( 'UserResetAllOptions', array( $this, &$newOptions, $this->mOptions, $resetKinds ) );
$this->mOptions = $newOptions;
$this->mOptionsLoaded = true;
@@ -2850,7 +2949,7 @@ class User implements IDBAccessObject {
return false;
} else {
$https = $this->getBoolOption( 'prefershttps' );
- wfRunHooks( 'UserRequiresHTTPS', array( $this, &$https ) );
+ Hooks::run( 'UserRequiresHTTPS', array( $this, &$https ) );
if ( $https ) {
$https = wfCanIPUseHTTPS( $this->getRequest()->getIP() );
}
@@ -2881,7 +2980,7 @@ class User implements IDBAccessObject {
public function getRights() {
if ( is_null( $this->mRights ) ) {
$this->mRights = self::getGroupPermissions( $this->getEffectiveGroups() );
- wfRunHooks( 'UserGetRights', array( $this, &$this->mRights ) );
+ Hooks::run( 'UserGetRights', array( $this, &$this->mRights ) );
// Force reindexation of rights when a hook has unset one of them
$this->mRights = array_values( array_unique( $this->mRights ) );
}
@@ -2908,16 +3007,14 @@ class User implements IDBAccessObject {
*/
public function getEffectiveGroups( $recache = false ) {
if ( $recache || is_null( $this->mEffectiveGroups ) ) {
- wfProfileIn( __METHOD__ );
$this->mEffectiveGroups = array_unique( array_merge(
$this->getGroups(), // explicit groups
$this->getAutomaticGroups( $recache ) // implicit groups
) );
// Hook for additional groups
- wfRunHooks( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
+ Hooks::run( 'UserEffectiveGroups', array( &$this, &$this->mEffectiveGroups ) );
// Force reindexation of groups when a hook has unset one of them
$this->mEffectiveGroups = array_values( array_unique( $this->mEffectiveGroups ) );
- wfProfileOut( __METHOD__ );
}
return $this->mEffectiveGroups;
}
@@ -2931,7 +3028,6 @@ class User implements IDBAccessObject {
*/
public function getAutomaticGroups( $recache = false ) {
if ( $recache || is_null( $this->mImplicitGroups ) ) {
- wfProfileIn( __METHOD__ );
$this->mImplicitGroups = array( '*' );
if ( $this->getId() ) {
$this->mImplicitGroups[] = 'user';
@@ -2946,7 +3042,6 @@ class User implements IDBAccessObject {
// as getEffectiveGroups() depends on this function
$this->mEffectiveGroups = null;
}
- wfProfileOut( __METHOD__ );
}
return $this->mImplicitGroups;
}
@@ -2961,9 +3056,13 @@ class User implements IDBAccessObject {
* @return array Names of the groups the user has belonged to.
*/
public function getFormerGroups() {
+ $this->load();
+
if ( is_null( $this->mFormerGroups ) ) {
- $dbr = wfGetDB( DB_MASTER );
- $res = $dbr->select( 'user_former_groups',
+ $db = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+ $res = $db->select( 'user_former_groups',
array( 'ufg_group' ),
array( 'ufg_user' => $this->mId ),
__METHOD__ );
@@ -2972,6 +3071,7 @@ class User implements IDBAccessObject {
$this->mFormerGroups[] = $row->ufg_group;
}
}
+
return $this->mFormerGroups;
}
@@ -2986,7 +3086,6 @@ class User implements IDBAccessObject {
if ( $this->mEditCount === null ) {
/* Populate the count, if it has not been populated yet */
- wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
// check if the user_editcount field has been initialized
$count = $dbr->selectField(
@@ -3000,7 +3099,6 @@ class User implements IDBAccessObject {
$count = $this->initEditCount();
}
$this->mEditCount = $count;
- wfProfileOut( __METHOD__ );
}
return (int)$this->mEditCount;
}
@@ -3009,20 +3107,26 @@ class User implements IDBAccessObject {
* Add the user to the given group.
* This takes immediate effect.
* @param string $group Name of the group to add
+ * @return bool
*/
public function addGroup( $group ) {
- if ( wfRunHooks( 'UserAddGroup', array( $this, &$group ) ) ) {
- $dbw = wfGetDB( DB_MASTER );
- if ( $this->getId() ) {
- $dbw->insert( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ),
- __METHOD__,
- array( 'IGNORE' ) );
- }
+ $this->load();
+
+ if ( !Hooks::run( 'UserAddGroup', array( $this, &$group ) ) ) {
+ return false;
+ }
+
+ $dbw = wfGetDB( DB_MASTER );
+ if ( $this->getId() ) {
+ $dbw->insert( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ),
+ __METHOD__,
+ array( 'IGNORE' ) );
}
+
$this->loadGroups();
$this->mGroups[] = $group;
// In case loadGroups was not called before, we now have the right twice.
@@ -3035,31 +3139,39 @@ class User implements IDBAccessObject {
$this->mRights = null;
$this->invalidateCache();
+
+ return true;
}
/**
* Remove the user from the given group.
* This takes immediate effect.
* @param string $group Name of the group to remove
+ * @return bool
*/
public function removeGroup( $group ) {
$this->load();
- if ( wfRunHooks( 'UserRemoveGroup', array( $this, &$group ) ) ) {
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'user_groups',
- array(
- 'ug_user' => $this->getID(),
- 'ug_group' => $group,
- ), __METHOD__ );
- // Remember that the user was in this group
- $dbw->insert( 'user_former_groups',
- array(
- 'ufg_user' => $this->getID(),
- 'ufg_group' => $group,
- ),
- __METHOD__,
- array( 'IGNORE' ) );
+ if ( !Hooks::run( 'UserRemoveGroup', array( $this, &$group ) ) ) {
+ return false;
}
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'user_groups',
+ array(
+ 'ug_user' => $this->getID(),
+ 'ug_group' => $group,
+ ), __METHOD__
+ );
+ // Remember that the user was in this group
+ $dbw->insert( 'user_former_groups',
+ array(
+ 'ufg_user' => $this->getID(),
+ 'ufg_group' => $group,
+ ),
+ __METHOD__,
+ array( 'IGNORE' )
+ );
+
$this->loadGroups();
$this->mGroups = array_diff( $this->mGroups, array( $group ) );
@@ -3069,6 +3181,8 @@ class User implements IDBAccessObject {
$this->mRights = null;
$this->invalidateCache();
+
+ return true;
}
/**
@@ -3268,7 +3382,7 @@ class User implements IDBAccessObject {
// If we're working on user's talk page, we should update the talk page message indicator
if ( $title->getNamespace() == NS_USER_TALK && $title->getText() == $this->getName() ) {
- if ( !wfRunHooks( 'UserClearNewTalkNotification', array( &$this, $oldid ) ) ) {
+ if ( !Hooks::run( 'UserClearNewTalkNotification', array( &$this, $oldid ) ) ) {
return;
}
@@ -3354,10 +3468,17 @@ class User implements IDBAccessObject {
* false: Force NOT setting the secure attribute when setting the cookie
* null (default): Use the default ($wgCookieSecure) to set the secure attribute
* @param array $params Array of options sent passed to WebResponse::setcookie()
+ * @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null
+ * is passed.
*/
- protected function setCookie( $name, $value, $exp = 0, $secure = null, $params = array() ) {
+ protected function setCookie(
+ $name, $value, $exp = 0, $secure = null, $params = array(), $request = null
+ ) {
+ if ( $request === null ) {
+ $request = $this->getRequest();
+ }
$params['secure'] = $secure;
- $this->getRequest()->response()->setcookie( $name, $value, $exp, $params );
+ $request->response()->setcookie( $name, $value, $exp, $params );
}
/**
@@ -3396,7 +3517,9 @@ class User implements IDBAccessObject {
// Simply by setting every cell in the user_token column to NULL and letting them be
// regenerated as users log back into the wiki.
$this->setToken();
- $this->saveSettings();
+ if ( !wfReadOnly() ) {
+ $this->saveSettings();
+ }
}
$session = array(
'wsUserID' => $this->mId,
@@ -3413,7 +3536,7 @@ class User implements IDBAccessObject {
$cookies['Token'] = false;
}
- wfRunHooks( 'UserSetCookies', array( $this, &$session, &$cookies ) );
+ Hooks::run( 'UserSetCookies', array( $this, &$session, &$cookies ) );
foreach ( $session as $name => $value ) {
$request->setSessionData( $name, $value );
@@ -3422,7 +3545,7 @@ class User implements IDBAccessObject {
if ( $value === false ) {
$this->clearCookie( $name );
} else {
- $this->setCookie( $name, $value, 0, $secure );
+ $this->setCookie( $name, $value, 0, $secure, array(), $request );
}
}
@@ -3448,7 +3571,7 @@ class User implements IDBAccessObject {
* Log this user out.
*/
public function logout() {
- if ( wfRunHooks( 'UserLogout', array( &$this ) ) ) {
+ if ( Hooks::run( 'UserLogout', array( &$this ) ) ) {
$this->doLogout();
}
}
@@ -3477,16 +3600,34 @@ class User implements IDBAccessObject {
public function saveSettings() {
global $wgAuth;
- $this->load();
- $this->loadPasswords();
if ( wfReadOnly() ) {
+ // @TODO: caller should deal with this instead!
+ // This should really just be an exception.
+ MWExceptionHandler::logException( new DBExpectedError(
+ null,
+ "Could not update user with ID '{$this->mId}'; DB is read-only."
+ ) );
return;
}
+
+ $this->load();
+ $this->loadPasswords();
if ( 0 == $this->mId ) {
- return;
+ return; // anon
}
- $this->mTouched = self::newTouchedTimestamp();
+ // This method is for updating existing users, so the user should
+ // have been loaded from the master to begin with to avoid problems.
+ if ( !( $this->queryFlagsUsed & self::READ_LATEST ) ) {
+ wfWarn( "Attempting to save slave-loaded User object with ID '{$this->mId}'." );
+ }
+
+ // Get a new user_touched that is higher than the old one.
+ // This will be used for a CAS check as a last-resort safety
+ // check against race conditions and slave lag.
+ $oldTouched = $this->mTouched;
+ $this->mTouched = $this->newTouchedTimestamp();
+
if ( !$wgAuth->allowSetLocalPassword() ) {
$this->mPassword = self::getPasswordFactory()->newFromCiphertext( null );
}
@@ -3507,13 +3648,25 @@ class User implements IDBAccessObject {
'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
'user_password_expires' => $dbw->timestampOrNull( $this->mPasswordExpires ),
), array( /* WHERE */
- 'user_id' => $this->mId
+ 'user_id' => $this->mId,
+ 'user_touched' => $dbw->timestamp( $oldTouched ) // CAS check
), __METHOD__
);
+ if ( !$dbw->affectedRows() ) {
+ // User was changed in the meantime or loaded with stale data
+ MWExceptionHandler::logException( new MWException(
+ "CAS update failed on user_touched for user ID '{$this->mId}'."
+ ) );
+ // Maybe the problem was a missed cache update; clear it to be safe
+ $this->clearSharedCache();
+
+ return;
+ }
+
$this->saveOptions();
- wfRunHooks( 'UserSaveSettings', array( $this ) );
+ Hooks::run( 'UserSaveSettings', array( $this ) );
$this->clearSharedCache();
$this->getUserPage()->invalidateCache();
}
@@ -3579,7 +3732,7 @@ class User implements IDBAccessObject {
'user_token' => strval( $user->mToken ),
'user_registration' => $dbw->timestamp( $user->mRegistration ),
'user_editcount' => 0,
- 'user_touched' => $dbw->timestamp( self::newTouchedTimestamp() ),
+ 'user_touched' => $dbw->timestamp( $user->newTouchedTimestamp() ),
);
foreach ( $params as $name => $value ) {
$fields["user_$name"] = $value;
@@ -3626,7 +3779,7 @@ class User implements IDBAccessObject {
$this->setToken(); // init token
}
- $this->mTouched = self::newTouchedTimestamp();
+ $this->mTouched = $this->newTouchedTimestamp();
$dbw = wfGetDB( DB_MASTER );
$inWrite = $dbw->writesOrCallbacksPending();
@@ -3661,7 +3814,7 @@ class User implements IDBAccessObject {
// using CentralAuth. It's should be OK to commit and break the snapshot.
$dbw->commit( __METHOD__, 'flush' );
$options = array();
- $flags = 0;
+ $flags = self::READ_LATEST;
}
$this->mId = $dbw->selectField( 'user', 'user_id',
array( 'user_name' => $this->mName ), __METHOD__, $options );
@@ -3793,8 +3946,6 @@ class User implements IDBAccessObject {
public function checkPassword( $password ) {
global $wgAuth, $wgLegacyEncoding;
- $section = new ProfileSection( __METHOD__ );
-
$this->loadPasswords();
// Some passwords will give a fatal Status, which means there is
@@ -3817,7 +3968,6 @@ class User implements IDBAccessObject {
return false;
}
- $passwordFactory = self::getPasswordFactory();
if ( !$this->mPassword->equals( $password ) ) {
if ( $wgLegacyEncoding ) {
// Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
@@ -3831,7 +3981,8 @@ class User implements IDBAccessObject {
}
}
- if ( $passwordFactory->needsUpdate( $this->mPassword ) ) {
+ $passwordFactory = self::getPasswordFactory();
+ if ( $passwordFactory->needsUpdate( $this->mPassword ) && !wfReadOnly() ) {
$this->mPassword = $passwordFactory->newFromPlaintext( $password );
$this->saveSettings();
}
@@ -3877,22 +4028,15 @@ class User implements IDBAccessObject {
}
/**
- * Initialize (if necessary) and return a session token value
- * which can be used in edit forms to show that the user's
- * login credentials aren't being hijacked with a foreign form
- * submission.
- *
- * @since 1.19
+ * Internal implementation for self::getEditToken() and
+ * self::matchEditToken().
*
- * @param string|array $salt Array of Strings Optional function-specific data for hashing
- * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
- * @return string The new edit token
+ * @param string|array $salt
+ * @param WebRequest $request
+ * @param string|int $timestamp
+ * @return string
*/
- public function getEditToken( $salt = '', $request = null ) {
- if ( $request == null ) {
- $request = $this->getRequest();
- }
-
+ private function getEditTokenAtTimestamp( $salt, $request, $timestamp ) {
if ( $this->isAnon() ) {
return self::EDIT_TOKEN_SUFFIX;
} else {
@@ -3904,11 +4048,31 @@ class User implements IDBAccessObject {
if ( is_array( $salt ) ) {
$salt = implode( '|', $salt );
}
- return md5( $token . $salt ) . self::EDIT_TOKEN_SUFFIX;
+ return hash_hmac( 'md5', $timestamp . $salt, $token, false ) .
+ dechex( $timestamp ) .
+ self::EDIT_TOKEN_SUFFIX;
}
}
/**
+ * Initialize (if necessary) and return a session token value
+ * which can be used in edit forms to show that the user's
+ * login credentials aren't being hijacked with a foreign form
+ * submission.
+ *
+ * @since 1.19
+ *
+ * @param string|array $salt Array of Strings Optional function-specific data for hashing
+ * @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
+ * @return string The new edit token
+ */
+ public function getEditToken( $salt = '', $request = null ) {
+ return $this->getEditTokenAtTimestamp(
+ $salt, $request ?: $this->getRequest(), wfTimestamp()
+ );
+ }
+
+ /**
* Generate a looking random token for various uses.
*
* @return string The new random token
@@ -3920,6 +4084,20 @@ class User implements IDBAccessObject {
}
/**
+ * Get the embedded timestamp from a token.
+ * @param string $val Input token
+ * @return int|null
+ */
+ public static function getEditTokenTimestamp( $val ) {
+ $suffixLen = strlen( self::EDIT_TOKEN_SUFFIX );
+ if ( strlen( $val ) <= 32 + $suffixLen ) {
+ return null;
+ }
+
+ return hexdec( substr( $val, 32, -$suffixLen ) );
+ }
+
+ /**
* Check given value against the token value stored in the session.
* A match should confirm that the form was submitted from the
* user's own login session, not a form submission from a third-party
@@ -3928,15 +4106,32 @@ class User implements IDBAccessObject {
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
* @param WebRequest|null $request Object to use or null to use $wgRequest
+ * @param int $maxage Fail tokens older than this, in seconds
* @return bool Whether the token matches
*/
- public function matchEditToken( $val, $salt = '', $request = null ) {
- $sessionToken = $this->getEditToken( $salt, $request );
+ public function matchEditToken( $val, $salt = '', $request = null, $maxage = null ) {
+ if ( $this->isAnon() ) {
+ return $val === self::EDIT_TOKEN_SUFFIX;
+ }
+
+ $timestamp = self::getEditTokenTimestamp( $val );
+ if ( $timestamp === null ) {
+ return false;
+ }
+ if ( $maxage !== null && $timestamp < wfTimestamp() - $maxage ) {
+ // Expired token
+ return false;
+ }
+
+ $sessionToken = $this->getEditTokenAtTimestamp(
+ $salt, $request ?: $this->getRequest(), $timestamp
+ );
+
if ( $val != $sessionToken ) {
wfDebug( "User::matchEditToken: broken session data\n" );
}
- return $val == $sessionToken;
+ return hash_equals( $sessionToken, $val );
}
/**
@@ -3946,11 +4141,12 @@ class User implements IDBAccessObject {
* @param string $val Input value to compare
* @param string $salt Optional function-specific data for hashing
* @param WebRequest|null $request Object to use or null to use $wgRequest
+ * @param int $maxage Fail tokens older than this, in seconds
* @return bool Whether the token matches
*/
- public function matchEditTokenNoSuffix( $val, $salt = '', $request = null ) {
- $sessionToken = $this->getEditToken( $salt, $request );
- return substr( $sessionToken, 0, 32 ) == substr( $val, 0, 32 );
+ public function matchEditTokenNoSuffix( $val, $salt = '', $request = null, $maxage = null ) {
+ $val = substr( $val, 0, strspn( $val, '0123456789abcdef' ) ) . self::EDIT_TOKEN_SUFFIX;
+ return $this->matchEditToken( $val, $salt, $request, $maxage );
}
/**
@@ -4085,7 +4281,7 @@ class User implements IDBAccessObject {
// and fire the ConfirmEmailComplete hook on redundant confirmations.
if ( !$this->isEmailConfirmed() ) {
$this->setEmailAuthenticationTimestamp( wfTimestampNow() );
- wfRunHooks( 'ConfirmEmailComplete', array( $this ) );
+ Hooks::run( 'ConfirmEmailComplete', array( $this ) );
}
return true;
}
@@ -4103,7 +4299,7 @@ class User implements IDBAccessObject {
$this->mEmailTokenExpires = null;
$this->setEmailAuthenticationTimestamp( null );
$this->mEmail = '';
- wfRunHooks( 'InvalidateEmailComplete', array( $this ) );
+ Hooks::run( 'InvalidateEmailComplete', array( $this ) );
return true;
}
@@ -4114,7 +4310,7 @@ class User implements IDBAccessObject {
public function setEmailAuthenticationTimestamp( $timestamp ) {
$this->load();
$this->mEmailAuthenticated = $timestamp;
- wfRunHooks( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
+ Hooks::run( 'UserSetEmailAuthenticationTimestamp', array( $this, &$this->mEmailAuthenticated ) );
}
/**
@@ -4128,7 +4324,7 @@ class User implements IDBAccessObject {
return false;
}
$canSend = $this->isEmailConfirmed();
- wfRunHooks( 'UserCanSendEmail', array( &$this, &$canSend ) );
+ Hooks::run( 'UserCanSendEmail', array( &$this, &$canSend ) );
return $canSend;
}
@@ -4155,7 +4351,7 @@ class User implements IDBAccessObject {
global $wgEmailAuthentication;
$this->load();
$confirmed = true;
- if ( wfRunHooks( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
+ if ( Hooks::run( 'EmailConfirmed', array( &$this, &$confirmed ) ) ) {
if ( $this->isAnon() ) {
return false;
}
@@ -4313,7 +4509,7 @@ class User implements IDBAccessObject {
}
// Allow extensions (e.g. OAuth) to say false
- if ( !wfRunHooks( 'UserIsEveryoneAllowed', array( $right ) ) ) {
+ if ( !Hooks::run( 'UserIsEveryoneAllowed', array( $right ) ) ) {
$cache[$right] = false;
return false;
}
@@ -4361,7 +4557,7 @@ class User implements IDBAccessObject {
/**
* Get a list of all available permissions.
- * @return array Array of permission names
+ * @return string[] Array of permission names
*/
public static function getAllRights() {
if ( self::$mAllRights === false ) {
@@ -4371,7 +4567,7 @@ class User implements IDBAccessObject {
} else {
self::$mAllRights = self::$mCoreRights;
}
- wfRunHooks( 'UserGetAllRights', array( &self::$mAllRights ) );
+ Hooks::run( 'UserGetAllRights', array( &self::$mAllRights ) );
}
return self::$mAllRights;
}
@@ -4384,8 +4580,8 @@ class User implements IDBAccessObject {
global $wgImplicitGroups;
$groups = $wgImplicitGroups;
- # Deprecated, use $wgImplictGroups instead
- wfRunHooks( 'UserGetImplicitGroups', array( &$groups ) );
+ # Deprecated, use $wgImplicitGroups instead
+ Hooks::run( 'UserGetImplicitGroups', array( &$groups ), '1.25' );
return $groups;
}
@@ -4423,7 +4619,7 @@ class User implements IDBAccessObject {
if ( $title ) {
return Linker::link( $title, htmlspecialchars( $text ) );
} else {
- return $text;
+ return htmlspecialchars( $text );
}
}
@@ -4441,7 +4637,7 @@ class User implements IDBAccessObject {
}
$title = self::getGroupPage( $group );
if ( $title ) {
- $page = $title->getPrefixedText();
+ $page = $title->getFullText();
return "[[$page|$text]]";
} else {
return $text;
@@ -4478,6 +4674,7 @@ class User implements IDBAccessObject {
// Same thing for remove
if ( empty( $wgRemoveGroups[$group] ) ) {
+ // Do nothing
} elseif ( $wgRemoveGroups[$group] === true ) {
$groups['remove'] = self::getAllGroups();
} elseif ( is_array( $wgRemoveGroups[$group] ) ) {
@@ -4503,6 +4700,7 @@ class User implements IDBAccessObject {
// Now figure out what groups the user can add to him/herself
if ( empty( $wgGroupsAddToSelf[$group] ) ) {
+ // Do nothing
} elseif ( $wgGroupsAddToSelf[$group] === true ) {
// No idea WHY this would be used, but it's there
$groups['add-self'] = User::getAllGroups();
@@ -4511,6 +4709,7 @@ class User implements IDBAccessObject {
}
if ( empty( $wgGroupsRemoveFromSelf[$group] ) ) {
+ // Do nothing
} elseif ( $wgGroupsRemoveFromSelf[$group] === true ) {
$groups['remove-self'] = User::getAllGroups();
} elseif ( is_array( $wgGroupsRemoveFromSelf[$group] ) ) {
@@ -4713,7 +4912,7 @@ class User implements IDBAccessObject {
if ( $action === true ) {
$action = 'byemail';
} elseif ( $action === false ) {
- if ( $this->getName() == $wgUser->getName() ) {
+ if ( $this->equals( $wgUser ) ) {
$action = 'create';
} else {
$action = 'create2';
@@ -4793,7 +4992,9 @@ class User implements IDBAccessObject {
if ( !is_array( $data ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from database.\n" );
// Load from database
- $dbr = wfGetDB( DB_SLAVE );
+ $dbr = ( $this->queryFlagsUsed & self::READ_LATEST )
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
$res = $dbr->select(
'user_properties',
@@ -4816,7 +5017,7 @@ class User implements IDBAccessObject {
$this->mOptionsLoaded = true;
- wfRunHooks( 'UserLoadOptions', array( $this, &$this->mOptions ) );
+ Hooks::run( 'UserLoadOptions', array( $this, &$this->mOptions ) );
}
/**
@@ -4832,7 +5033,7 @@ class User implements IDBAccessObject {
// Allow hooks to abort, for instance to save to a global profile.
// Reset options to default state before saving.
- if ( !wfRunHooks( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
+ if ( !Hooks::run( 'UserSaveOptions', array( $this, &$saveOptions ) ) ) {
return;
}
@@ -4998,4 +5199,15 @@ class User implements IDBAccessObject {
return Status::newFatal( 'badaccess-group0' );
}
}
+
+ /**
+ * Checks if two user objects point to the same user.
+ *
+ * @since 1.25
+ * @param User $user
+ * @return bool
+ */
+ public function equals( User $user ) {
+ return $this->getName() === $user->getName();
+ }
}