* for rendering normal pages are set in the cookie to minimize use
* of the database.
*/
-class User {
+class User implements IDBAccessObject {
/**
* Global constants made accessible as class constants so that autoloader
* magic can be used.
'nominornewtalk',
'noratelimit',
'override-export-depth',
+ 'pagelang',
'passwordreset',
'patrol',
'patrolmarks',
* Load user and user_group data from the database.
* $this->mId must be set, this is how the user is identified.
*
+ * @param integer $flags Supports User::READ_LOCKING
* @return bool True if the user exists, false if the user is anonymous
*/
- public function loadFromDatabase() {
+ public function loadFromDatabase( $flags = 0 ) {
// Paranoia
$this->mId = intval( $this->mId );
'user',
self::selectFields(),
array( 'user_id' => $this->mId ),
- __METHOD__
+ __METHOD__,
+ ( $flags & self::READ_LOCKING == self::READ_LOCKING )
+ ? array( 'LOCK IN SHARE MODE' )
+ : array()
);
wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) );
$this->mNewpassword = $row->user_newpassword;
$this->mNewpassTime = wfTimestampOrNull( TS_MW, $row->user_newpass_time );
$this->mEmail = $row->user_email;
- if ( isset( $row->user_options ) ) {
- $this->decodeOptions( $row->user_options );
- }
$this->mTouched = wfTimestamp( TS_MW, $row->user_touched );
$this->mToken = $row->user_token;
if ( $this->mToken == '' ) {
/**
* Set the given option for a user.
*
+ * You need to call saveSettings() to actually write to the database.
+ *
* @param string $oname The option to set
* @param mixed $val New value to set
*/
return null;
}
- if ( !isset( $this->mEditCount ) ) {
+ if ( $this->mEditCount === null ) {
/* Populate the count, if it has not been populated yet */
wfProfileIn( __METHOD__ );
$dbr = wfGetDB( DB_SLAVE );
}
}
- /**
- * Set this user's options from an encoded string
- * @param string $str Encoded options to import
- *
- * @deprecated since 1.19 due to removal of user_options from the user table
- */
- private function decodeOptions( $str ) {
- wfDeprecated( __METHOD__, '1.19' );
- if ( !$str ) {
- return;
- }
-
- $this->mOptionsLoaded = true;
- $this->mOptionOverrides = array();
-
- // If an option is not set in $str, use the default value
- $this->mOptions = self::getDefaultOptions();
-
- $a = explode( "\n", $str );
- foreach ( $a as $s ) {
- $m = array();
- if ( preg_match( "/^(.[^=]*)=(.*)$/", $s, $m ) ) {
- $this->mOptions[$m[1]] = $m[2];
- $this->mOptionOverrides[$m[1]] = $m[2];
- }
- }
- }
-
/**
* Set a cookie on the user's client. Wrapper for
* WebResponse::setCookie
array( 'IGNORE' )
);
if ( !$dbw->affectedRows() ) {
- if ( !$inWrite ) {
- // XXX: Get out of REPEATABLE-READ so the SELECT below works.
- // Often this case happens early in views before any writes.
- // This shows up at least with CentralAuth.
+ // The queries below cannot happen in the same REPEATABLE-READ snapshot.
+ // Handle this by COMMIT, if possible, or by LOCK IN SHARE MODE otherwise.
+ if ( $inWrite ) {
+ // Can't commit due to pending writes that may need atomicity.
+ // This may cause some lock contention unlike the case below.
+ $options = array( 'LOCK IN SHARE MODE' );
+ $flags = self::READ_LOCKING;
+ } else {
+ // Often, this case happens early in views before any writes when
+ // using CentralAuth. It's should be OK to commit and break the snapshot.
$dbw->commit( __METHOD__, 'flush' );
+ $options = array();
+ $flags = 0;
}
$this->mId = $dbw->selectField( 'user', 'user_id',
- array( 'user_name' => $this->mName ), __METHOD__ );
+ array( 'user_name' => $this->mName ), __METHOD__, $options );
$loaded = false;
if ( $this->mId ) {
- if ( $this->loadFromDatabase() ) {
+ if ( $this->loadFromDatabase( $flags ) ) {
$loaded = true;
}
}
}
/**
- * @todo document
+ * Saves the non-default options for this user, as previously set e.g. via
+ * setOption(), in the database's "user_properties" (preferences) table.
+ * Usually used via saveSettings().
*/
protected function saveOptions() {
$this->loadOptions();
}
$userId = $this->getId();
- $insert_rows = array();
+
+ $insert_rows = array(); // all the new preference rows
foreach ( $saveOptions as $key => $value ) {
// Don't bother storing default values
$defaultOption = self::getDefaultOption( $key );
- if ( ( is_null( $defaultOption ) &&
- !( $value === false || is_null( $value ) ) ) ||
- $value != $defaultOption
+ if ( ( $defaultOption === null && $value !== false && $value !== null )
+ || $value != $defaultOption
) {
$insert_rows[] = array(
'up_user' => $userId,
}
$dbw = wfGetDB( DB_MASTER );
- // Find and delete any prior preference rows...
+
$res = $dbw->select( 'user_properties',
- array( 'up_property' ), array( 'up_user' => $userId ), __METHOD__ );
- $priorKeys = array();
+ array( 'up_property', 'up_value' ), array( 'up_user' => $userId ), __METHOD__ );
+
+ // Find prior rows that need to be removed or updated. These rows will
+ // all be deleted (the later so that INSERT IGNORE applies the new values).
+ $keysDelete = array();
foreach ( $res as $row ) {
- $priorKeys[] = $row->up_property;
+ if ( !isset( $saveOptions[$row->up_property] )
+ || strcmp( $saveOptions[$row->up_property], $row->up_value ) != 0
+ ) {
+ $keysDelete[] = $row->up_property;
+ }
}
- if ( count( $priorKeys ) ) {
+
+ if ( count( $keysDelete ) ) {
// Do the DELETE by PRIMARY KEY for prior rows.
// In the past a very large portion of calls to this function are for setting
// 'rememberpassword' for new accounts (a preference that has since been removed).
// updates would pile up on each other as they are for higher (newer) user IDs.
// It might not be necessary these days, but it shouldn't hurt either.
$dbw->delete( 'user_properties',
- array( 'up_user' => $userId, 'up_property' => $priorKeys ), __METHOD__ );
+ array( 'up_user' => $userId, 'up_property' => $keysDelete ), __METHOD__ );
}
// Insert the new preference rows
$dbw->insert( 'user_properties', $insert_rows, __METHOD__, array( 'IGNORE' ) );