/**
* 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
*
* @param $uid Int User ID to check
* @return Int the user's edit count
+ *
+ * @deprecated since 1.21 in favour of User::getEditCount
*/
public static function edits( $uid ) {
- wfProfileIn( __METHOD__ );
- $dbr = wfGetDB( DB_SLAVE );
- // check if the user_editcount field has been initialized
- $field = $dbr->selectField(
- 'user', 'user_editcount',
- array( 'user_id' => $uid ),
- __METHOD__
- );
-
- if( $field === null ) { // it has not been initialized. do so.
- $dbw = wfGetDB( DB_MASTER );
- $count = $dbr->selectField(
- 'revision', 'count(*)',
- array( 'rev_user' => $uid ),
- __METHOD__
- );
- $dbw->update(
- 'user',
- array( 'user_editcount' => $count ),
- array( 'user_id' => $uid ),
- __METHOD__
- );
- } else {
- $count = $field;
- }
- wfProfileOut( __METHOD__ );
- return $count;
+ wfDeprecated( __METHOD__, '1.21' );
+ $user = self::newFromId( $uid );
+ return $user->getEditCount();
}
/**
if( $loggedOut !== null ) {
$this->mTouched = wfTimestamp( TS_MW, $loggedOut );
} else {
- $this->mTouched = '0'; # Allow any pages to be cached
+ $this->mTouched = '1'; # Allow any pages to be cached
}
$this->mToken = null; // Don't run cryptographic functions till we need a token
}
/**
- * Clear various cached data stored in this object.
+ * Clear various cached data stored in this object. The cache of the user table
+ * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
+ *
* @param $reloadFrom bool|String Reload user and user_groups table data from a
* given source. May be "name", "id", "defaults", "session", or false for
* no reload.
$this->mEffectiveGroups = null;
$this->mImplicitGroups = null;
$this->mOptions = null;
+ $this->mOptionsLoaded = false;
$this->mEditCount = null;
if ( $reloadFrom ) {
$defOpt = $wgDefaultUserOptions;
# default language setting
- $variant = $wgContLang->getDefaultVariant();
- $defOpt['variant'] = $variant;
- $defOpt['language'] = $variant;
+ $defOpt['variant'] = $wgContLang->getCode();
+ $defOpt['language'] = $wgContLang->getCode();
foreach( SearchEngine::searchableNamespaces() as $nsnum => $nsname ) {
$defOpt['searchNs'.$nsnum] = !empty( $wgNamespacesToBeSearchedDefault[$nsnum] );
}
if( $this->getId() ) {
if ( !isset( $this->mEditCount ) ) {
/* Populate the count, if it has not been populated yet */
- $this->mEditCount = User::edits( $this->mId );
+ wfProfileIn( __METHOD__ );
+ $dbr = wfGetDB( DB_SLAVE );
+ // check if the user_editcount field has been initialized
+ $count = $dbr->selectField(
+ 'user', 'user_editcount',
+ array( 'user_id' => $this->mId ),
+ __METHOD__
+ );
+
+ if( $count === null ) {
+ // it has not been initialized. do so.
+ $count = $this->initEditCount();
+ }
+ wfProfileOut( __METHOD__ );
+ $this->mEditCount = $count;
}
return $this->mEditCount;
} else {
}
/**
- * Add this existing user object to the database
+ * Add this existing user object to the database. If the user already
+ * exists, a fatal status object is returned, and the user object is
+ * initialised with the data from the database.
+ *
+ * Previously, this function generated a DB error due to a key conflict
+ * if the user already existed. Many extension callers use this function
+ * in code along the lines of:
+ *
+ * $user = User::newFromName( $name );
+ * if ( !$user->isLoggedIn() ) {
+ * $user->addToDatabase();
+ * }
+ * // do something with $user...
+ *
+ * However, this was vulnerable to a race condition (bug 16020). By
+ * initialising the user object if the user exists, we aim to support this
+ * calling sequence as far as possible.
+ *
+ * Note that if the user exists, this function will acquire a write lock,
+ * so it is still advisable to make the call conditional on isLoggedIn(),
+ * and to commit the transaction after calling.
+ *
+ * @return Status
*/
public function addToDatabase() {
$this->load();
'user_registration' => $dbw->timestamp( $this->mRegistration ),
'user_editcount' => 0,
'user_touched' => $dbw->timestamp( $this->mTouched ),
- ), __METHOD__
+ ), __METHOD__,
+ array( 'IGNORE' )
);
+ if ( !$dbw->affectedRows() ) {
+ $this->mId = $dbw->selectField( 'user', 'user_id',
+ array( 'user_name' => $this->mName ), __METHOD__ );
+ $loaded = false;
+ if ( $this->mId ) {
+ if ( $this->loadFromDatabase() ) {
+ $loaded = true;
+ }
+ }
+ if ( !$loaded ) {
+ throw new MWException( __METHOD__. ": hit a key conflict attempting " .
+ "to insert a user row, but then it doesn't exist when we select it!" );
+ }
+ return Status::newFatal( 'userexists' );
+ }
$this->mId = $dbw->insertId();
// Clear instance cache other than user table data, which is already accurate
$this->clearInstanceCache();
$this->saveOptions();
+ return Status::newGood();
}
/**
public function incEditCount() {
if( !$this->isAnon() ) {
$dbw = wfGetDB( DB_MASTER );
- $dbw->update( 'user',
+ $dbw->update(
+ 'user',
array( 'user_editcount=user_editcount+1' ),
array( 'user_id' => $this->getId() ),
- __METHOD__ );
+ __METHOD__
+ );
// Lazy initialization check...
if( $dbw->affectedRows() == 0 ) {
- // Pull from a slave to be less cruel to servers
- // Accuracy isn't the point anyway here
- $dbr = wfGetDB( DB_SLAVE );
- $count = $dbr->selectField( 'revision',
- 'COUNT(rev_user)',
- array( 'rev_user' => $this->getId() ),
- __METHOD__ );
-
// Now here's a goddamn hack...
+ $dbr = wfGetDB( DB_SLAVE );
if( $dbr !== $dbw ) {
// If we actually have a slave server, the count is
// at least one behind because the current transaction
// has not been committed and replicated.
- $count++;
+ $this->initEditCount( 1 );
} else {
// But if DB_SLAVE is selecting the master, then the
// count we just read includes the revision that was
// just added in the working transaction.
+ $this->initEditCount();
}
-
- $dbw->update( 'user',
- array( 'user_editcount' => $count ),
- array( 'user_id' => $this->getId() ),
- __METHOD__ );
}
}
// edit count in user cache too
$this->invalidateCache();
}
+ /**
+ * Initialize user_editcount from data out of the revision table
+ *
+ * @param $add Integer Edits to add to the count from the revision table
+ * @return Integer Number of edits
+ */
+ protected function initEditCount( $add = 0 ) {
+ // Pull from a slave to be less cruel to servers
+ // Accuracy isn't the point anyway here
+ $dbr = wfGetDB( DB_SLAVE );
+ $count = $dbr->selectField(
+ 'revision',
+ 'COUNT(rev_user)',
+ array( 'rev_user' => $this->getId() ),
+ __METHOD__
+ );
+ $count = $count + $add;
+
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update(
+ 'user',
+ array( 'user_editcount' => $count ),
+ array( 'user_id' => $this->getId() ),
+ __METHOD__
+ );
+
+ return $count;
+ }
+
/**
* Get the description of a given right
*
* @todo document
*/
protected function loadOptions() {
+ global $wgContLang;
+
$this->load();
- if ( $this->mOptionsLoaded || !$this->getId() )
+
+ if ( $this->mOptionsLoaded ) {
return;
+ }
$this->mOptions = self::getDefaultOptions();
+ if ( !$this->getId() ) {
+ // For unlogged-in users, load language/variant options from request.
+ // There's no need to do it for logged-in users: they can set preferences,
+ // and handling of page content is done by $pageLang->getPreferredVariant() and such,
+ // so don't override user's choice (especially when the user chooses site default).
+ $variant = $wgContLang->getDefaultVariant();
+ $this->mOptions['variant'] = $variant;
+ $this->mOptions['language'] = $variant;
+ $this->mOptionsLoaded = true;
+ return;
+ }
+
// Maybe load from the object
if ( !is_null( $this->mOptionOverrides ) ) {
wfDebug( "User: loading options for user " . $this->getId() . " from override cache.\n" );