X-Git-Url: https://git.cyclocoop.org/%242?a=blobdiff_plain;f=includes%2Fuser%2FUser.php;h=47d75338bbf905af3c9519ddc0979348b199418f;hb=f34423130fee55974a5aec783cd34a213802bd1c;hp=5bab87642f17e454365240bdfa730d621190098a;hpb=9a6b2a4fffb82840d0bf780eb4ecb873ad64fa54;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/user/User.php b/includes/user/User.php index 5bab87642f..47d75338bb 100644 --- a/includes/user/User.php +++ b/includes/user/User.php @@ -31,6 +31,7 @@ use Wikimedia\IPSet; use Wikimedia\ScopedCallback; use Wikimedia\Rdbms\Database; use Wikimedia\Rdbms\DBExpectedError; +use Wikimedia\Rdbms\IDatabase; /** * String Some punctuation to prevent editing from broken text-mangling proxies. @@ -70,7 +71,7 @@ class User implements IDBAccessObject, UserIdentity { /** * @const int Serialized record version. */ - const VERSION = 11; + const VERSION = 12; /** * Exclude user options that are set to their default value. @@ -111,6 +112,8 @@ class User implements IDBAccessObject, UserIdentity { 'mGroupMemberships', // user_properties table 'mOptionOverrides', + // actor table + 'mActorId', ]; /** @@ -207,6 +210,8 @@ class User implements IDBAccessObject, UserIdentity { public $mId; /** @var string */ public $mName; + /** @var int|null */ + protected $mActorId; /** @var string */ public $mRealName; @@ -251,6 +256,7 @@ class User implements IDBAccessObject, UserIdentity { * - 'defaults' anonymous user initialised from class defaults * - 'name' initialise from mName * - 'id' initialise from mId + * - 'actor' initialise from mActorId * - 'session' log in from session if possible * * Use the User::newFrom*() family of functions to set this. @@ -309,6 +315,7 @@ class User implements IDBAccessObject, UserIdentity { * * @see newFromName() * @see newFromId() + * @see newFromActorId() * @see newFromConfirmationCode() * @see newFromSession() * @see newFromRow() @@ -399,8 +406,43 @@ class User implements IDBAccessObject, UserIdentity { } break; case 'id': + // Make sure this thread sees its own changes, if the ID isn't 0 + if ( $this->mId != 0 ) { + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + if ( $lb->hasOrMadeRecentMasterChanges() ) { + $flags |= self::READ_LATEST; + $this->queryFlagsUsed = $flags; + } + } + $this->loadFromId( $flags ); break; + case 'actor': + // Make sure this thread sees its own changes + if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) { + $flags |= self::READ_LATEST; + $this->queryFlagsUsed = $flags; + } + + list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags ); + $row = wfGetDB( $index )->selectRow( + 'actor', + [ 'actor_user', 'actor_name' ], + [ 'actor_id' => $this->mActorId ], + __METHOD__, + $options + ); + + if ( !$row ) { + // Ugh. + $this->loadDefaults(); + } elseif ( $row->actor_user ) { + $this->mId = $row->actor_user; + $this->loadFromId( $flags ); + } else { + $this->loadDefaults( $row->actor_name ); + } + break; case 'session': if ( !$this->loadFromSession() ) { // Loading from session failed. Load defaults. @@ -575,6 +617,78 @@ class User implements IDBAccessObject, UserIdentity { return $u; } + /** + * Static factory method for creation from a given actor ID. + * + * @since 1.31 + * @param int $id Valid actor ID + * @return User The corresponding User object + */ + public static function newFromActorId( $id ) { + global $wgActorTableSchemaMigrationStage; + + if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) { + throw new BadMethodCallException( + 'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage is MIGRATION_OLD' + ); + } + + $u = new User; + $u->mActorId = $id; + $u->mFrom = 'actor'; + $u->setItemLoaded( 'actor' ); + return $u; + } + + /** + * Static factory method for creation from an ID, name, and/or actor ID + * + * This does not check that the ID, name, and actor ID all correspond to + * the same user. + * + * @since 1.31 + * @param int|null $userId User ID, if known + * @param string|null $userName User name, if known + * @param int|null $actorId Actor ID, if known + * @return User + */ + public static function newFromAnyId( $userId, $userName, $actorId ) { + global $wgActorTableSchemaMigrationStage; + + $user = new User; + $user->mFrom = 'defaults'; + + if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD && $actorId !== null ) { + $user->mActorId = (int)$actorId; + if ( $user->mActorId !== 0 ) { + $user->mFrom = 'actor'; + } + $user->setItemLoaded( 'actor' ); + } + + if ( $userName !== null && $userName !== '' ) { + $user->mName = $userName; + $user->mFrom = 'name'; + $user->setItemLoaded( 'name' ); + } + + if ( $userId !== null ) { + $user->mId = (int)$userId; + if ( $user->mId !== 0 ) { + $user->mFrom = 'id'; + } + $user->setItemLoaded( 'id' ); + } + + if ( $user->mFrom === 'defaults' ) { + throw new InvalidArgumentException( + 'Cannot create a user with no name, no ID, and no actor ID' + ); + } + + return $user; + } + /** * Factory method to fetch whichever user has a given email confirmation code. * This code is generated when an account is created or its e-mail address @@ -771,7 +885,7 @@ class User implements IDBAccessObject, UserIdentity { return null; } - if ( !( $flags & self::READ_LATEST ) && isset( self::$idCacheByName[$name] ) ) { + if ( !( $flags & self::READ_LATEST ) && array_key_exists( $name, self::$idCacheByName ) ) { return self::$idCacheByName[$name]; } @@ -1164,6 +1278,7 @@ class User implements IDBAccessObject, UserIdentity { public function loadDefaults( $name = false ) { $this->mId = 0; $this->mName = $name; + $this->mActorId = null; $this->mRealName = ''; $this->mEmail = ''; $this->mOptionOverrides = null; @@ -1320,11 +1435,29 @@ class User implements IDBAccessObject, UserIdentity { * user_properties Array with properties out of the user_properties table */ protected function loadFromRow( $row, $data = null ) { + global $wgActorTableSchemaMigrationStage; + + if ( !is_object( $row ) ) { + throw new InvalidArgumentException( '$row must be an object' ); + } + $all = true; $this->mGroupMemberships = null; // deferred - if ( isset( $row->user_name ) ) { + if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) { + if ( isset( $row->actor_id ) ) { + $this->mActorId = (int)$row->actor_id; + if ( $this->mActorId !== 0 ) { + $this->mFrom = 'actor'; + } + $this->setItemLoaded( 'actor' ); + } else { + $all = false; + } + } + + if ( isset( $row->user_name ) && $row->user_name !== '' ) { $this->mName = $row->user_name; $this->mFrom = 'name'; $this->setItemLoaded( 'name' ); @@ -1341,13 +1474,15 @@ class User implements IDBAccessObject, UserIdentity { if ( isset( $row->user_id ) ) { $this->mId = intval( $row->user_id ); - $this->mFrom = 'id'; + if ( $this->mId !== 0 ) { + $this->mFrom = 'id'; + } $this->setItemLoaded( 'id' ); } else { $all = false; } - if ( isset( $row->user_id ) && isset( $row->user_name ) ) { + if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) { self::$idCacheByName[$row->user_name] = $row->user_id; } @@ -1555,7 +1690,7 @@ class User implements IDBAccessObject, UserIdentity { * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given. * * @param bool|string $reloadFrom Reload user and user_groups table data from a - * given source. May be "name", "id", "defaults", "session", or false for no reload. + * given source. May be "name", "id", "actor", "defaults", "session", or false for no reload. */ public function clearInstanceCache( $reloadFrom = false ) { $this->mNewtalk = -1; @@ -1736,7 +1871,9 @@ class User implements IDBAccessObject, UserIdentity { $this->mHideName = $block->mHideName; $this->mAllowUsertalk = !$block->prevents( 'editownusertalk' ); } else { + $this->mBlock = null; $this->mBlockedby = ''; + $this->mBlockreason = ''; $this->mHideName = 0; $this->mAllowUsertalk = false; } @@ -2284,6 +2421,67 @@ class User implements IDBAccessObject, UserIdentity { $this->mName = $str; } + /** + * Get the user's actor ID. + * @since 1.31 + * @param IDatabase|null $dbw Assign a new actor ID, using this DB handle, if none exists + * @return int The actor's ID, or 0 if no actor ID exists and $dbw was null + */ + public function getActorId( IDatabase $dbw = null ) { + global $wgActorTableSchemaMigrationStage; + + if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) { + return 0; + } + + if ( !$this->isItemLoaded( 'actor' ) ) { + $this->load(); + } + + // Currently $this->mActorId might be null if $this was loaded from a + // cache entry that was written when $wgActorTableSchemaMigrationStage + // was MIGRATION_OLD. Once that is no longer a possibility (i.e. when + // User::VERSION is incremented after $wgActorTableSchemaMigrationStage + // has been removed), that condition may be removed. + if ( $this->mActorId === null || !$this->mActorId && $dbw ) { + $q = [ + 'actor_user' => $this->getId() ?: null, + 'actor_name' => (string)$this->getName(), + ]; + if ( $dbw ) { + if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) { + throw new CannotCreateActorException( + 'Cannot create an actor for a usable name that is not an existing user' + ); + } + if ( $q['actor_name'] === '' ) { + throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' ); + } + $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] ); + if ( $dbw->affectedRows() ) { + $this->mActorId = (int)$dbw->insertId(); + } else { + // Outdated cache? + list( , $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed ); + $this->mActorId = (int)$dbw->selectField( 'actor', 'actor_id', $q, __METHOD__, $options ); + if ( !$this->mActorId ) { + throw new CannotCreateActorException( + "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}" + ); + } + } + $this->invalidateCache(); + } else { + list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed ); + $db = wfGetDB( $index ); + $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options ); + } + $this->setItemLoaded( 'actor' ); + } + + return (int)$this->mActorId; + } + /** * Get the user's name escaped by underscores. * @return string Username escaped by underscores. @@ -2509,12 +2707,17 @@ class User implements IDBAccessObject, UserIdentity { if ( $mode === 'refresh' ) { $cache->delete( $key, 1 ); } else { - wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( - function () use ( $cache, $key ) { - $cache->delete( $key ); - }, - __METHOD__ - ); + $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); + if ( $lb->hasOrMadeRecentMasterChanges() ) { + $lb->getConnection( DB_MASTER )->onTransactionPreCommitOrIdle( + function () use ( $cache, $key ) { + $cache->delete( $key ); + }, + __METHOD__ + ); + } else { + $cache->delete( $key ); + } } } @@ -3100,7 +3303,7 @@ class User implements IDBAccessObject, UserIdentity { $multiselectOptions = []; foreach ( $prefs as $name => $info ) { if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) || - ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) { + ( isset( $info['class'] ) && $info['class'] == HTMLMultiSelectField::class ) ) { $opts = HTMLFormField::flattenOptions( $info['options'] ); $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name; @@ -3114,7 +3317,7 @@ class User implements IDBAccessObject, UserIdentity { $checkmatrixOptions = []; foreach ( $prefs as $name => $info ) { if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) || - ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) { + ( isset( $info['class'] ) && $info['class'] == HTMLCheckMatrix::class ) ) { $columns = HTMLFormField::flattenOptions( $info['columns'] ); $rows = HTMLFormField::flattenOptions( $info['rows'] ); $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name; @@ -3851,76 +4054,6 @@ class User implements IDBAccessObject, UserIdentity { } } - /** - * Set a cookie on the user's client. Wrapper for - * WebResponse::setCookie - * @deprecated since 1.27 - * @param string $name Name of the cookie to set - * @param string $value Value to set - * @param int $exp Expiration time, as a UNIX time value; - * if 0 or not specified, use the default $wgCookieExpiration - * @param bool $secure - * true: Force setting the secure attribute when setting the cookie - * 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 = [], $request = null - ) { - wfDeprecated( __METHOD__, '1.27' ); - if ( $request === null ) { - $request = $this->getRequest(); - } - $params['secure'] = $secure; - $request->response()->setCookie( $name, $value, $exp, $params ); - } - - /** - * Clear a cookie on the user's client - * @deprecated since 1.27 - * @param string $name Name of the cookie to clear - * @param bool $secure - * true: Force setting the secure attribute when setting the cookie - * 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() - */ - protected function clearCookie( $name, $secure = null, $params = [] ) { - wfDeprecated( __METHOD__, '1.27' ); - $this->setCookie( $name, '', time() - 86400, $secure, $params ); - } - - /** - * Set an extended login cookie on the user's client. The expiry of the cookie - * is controlled by the $wgExtendedLoginCookieExpiration configuration - * variable. - * - * @see User::setCookie - * - * @deprecated since 1.27 - * @param string $name Name of the cookie to set - * @param string $value Value to set - * @param bool $secure - * true: Force setting the secure attribute when setting the cookie - * false: Force NOT setting the secure attribute when setting the cookie - * null (default): Use the default ($wgCookieSecure) to set the secure attribute - */ - protected function setExtendedLoginCookie( $name, $value, $secure ) { - global $wgExtendedLoginCookieExpiration, $wgCookieExpiration; - - wfDeprecated( __METHOD__, '1.27' ); - - $exp = time(); - $exp += $wgExtendedLoginCookieExpiration !== null - ? $wgExtendedLoginCookieExpiration - : $wgCookieExpiration; - - $this->setCookie( $name, $value, $exp, $secure ); - } - /** * Persist this user's session (e.g. set cookies) * @@ -4035,31 +4168,44 @@ class User implements IDBAccessObject, UserIdentity { $newTouched = $this->newTouchedTimestamp(); $dbw = wfGetDB( DB_MASTER ); - $dbw->update( 'user', - [ /* SET */ - 'user_name' => $this->mName, - 'user_real_name' => $this->mRealName, - 'user_email' => $this->mEmail, - 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), - 'user_touched' => $dbw->timestamp( $newTouched ), - 'user_token' => strval( $this->mToken ), - 'user_email_token' => $this->mEmailToken, - 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ), - ], $this->makeUpdateConditions( $dbw, [ /* WHERE */ - 'user_id' => $this->mId, - ] ), __METHOD__ - ); - - if ( !$dbw->affectedRows() ) { - // Maybe the problem was a missed cache update; clear it to be safe - $this->clearSharedCache( 'refresh' ); - // User was changed in the meantime or loaded with stale data - $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica'; - throw new MWException( - "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" . - " the version of the user to be saved is older than the current version." + $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) { + global $wgActorTableSchemaMigrationStage; + + $dbw->update( 'user', + [ /* SET */ + 'user_name' => $this->mName, + 'user_real_name' => $this->mRealName, + 'user_email' => $this->mEmail, + 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), + 'user_touched' => $dbw->timestamp( $newTouched ), + 'user_token' => strval( $this->mToken ), + 'user_email_token' => $this->mEmailToken, + 'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ), + ], $this->makeUpdateConditions( $dbw, [ /* WHERE */ + 'user_id' => $this->mId, + ] ), $fname ); - } + + if ( !$dbw->affectedRows() ) { + // Maybe the problem was a missed cache update; clear it to be safe + $this->clearSharedCache( 'refresh' ); + // User was changed in the meantime or loaded with stale data + $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica'; + throw new MWException( + "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" . + " the version of the user to be saved is older than the current version." + ); + } + + if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) { + $dbw->update( + 'actor', + [ 'actor_name' => $this->mName ], + [ 'actor_user' => $this->mId ], + $fname + ); + } + } ); $this->mTouched = $newTouched; $this->saveOptions(); @@ -4144,13 +4290,19 @@ class User implements IDBAccessObject, UserIdentity { foreach ( $params as $name => $value ) { $fields["user_$name"] = $value; } - $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] ); - if ( $dbw->affectedRows() ) { - $newUser = self::newFromId( $dbw->insertId() ); - } else { - $newUser = null; - } - return $newUser; + + return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) { + $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] ); + if ( $dbw->affectedRows() ) { + $newUser = self::newFromId( $dbw->insertId() ); + // Load the user from master to avoid replica lag + $newUser->load( self::READ_LATEST ); + $newUser->updateActorId( $dbw ); + } else { + $newUser = null; + } + return $newUser; + } ); } /** @@ -4191,55 +4343,79 @@ class User implements IDBAccessObject, UserIdentity { $this->mTouched = $this->newTouchedTimestamp(); - $noPass = PasswordFactory::newInvalidPassword()->toString(); - $dbw = wfGetDB( DB_MASTER ); - $dbw->insert( 'user', - [ - 'user_name' => $this->mName, - 'user_password' => $noPass, - 'user_newpassword' => $noPass, - 'user_email' => $this->mEmail, - 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), - 'user_real_name' => $this->mRealName, - 'user_token' => strval( $this->mToken ), - 'user_registration' => $dbw->timestamp( $this->mRegistration ), - 'user_editcount' => 0, - 'user_touched' => $dbw->timestamp( $this->mTouched ), - ], __METHOD__, - [ 'IGNORE' ] - ); - if ( !$dbw->affectedRows() ) { - // Use locking reads to bypass any REPEATABLE-READ snapshot. - $this->mId = $dbw->selectField( - 'user', - 'user_id', - [ 'user_name' => $this->mName ], - __METHOD__, - [ 'LOCK IN SHARE MODE' ] + $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) { + $noPass = PasswordFactory::newInvalidPassword()->toString(); + $dbw->insert( 'user', + [ + 'user_name' => $this->mName, + 'user_password' => $noPass, + 'user_newpassword' => $noPass, + 'user_email' => $this->mEmail, + 'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ), + 'user_real_name' => $this->mRealName, + 'user_token' => strval( $this->mToken ), + 'user_registration' => $dbw->timestamp( $this->mRegistration ), + 'user_editcount' => 0, + 'user_touched' => $dbw->timestamp( $this->mTouched ), + ], $fname, + [ 'IGNORE' ] ); - $loaded = false; - if ( $this->mId ) { - if ( $this->loadFromDatabase( self::READ_LOCKING ) ) { - $loaded = true; + if ( !$dbw->affectedRows() ) { + // Use locking reads to bypass any REPEATABLE-READ snapshot. + $this->mId = $dbw->selectField( + 'user', + 'user_id', + [ 'user_name' => $this->mName ], + __METHOD__, + [ 'LOCK IN SHARE MODE' ] + ); + $loaded = false; + if ( $this->mId ) { + if ( $this->loadFromDatabase( self::READ_LOCKING ) ) { + $loaded = true; + } } + if ( !$loaded ) { + throw new MWException( __METHOD__ . ": hit a key conflict attempting " . + "to insert user '{$this->mName}' row, but it was not present in select!" ); + } + return Status::newFatal( 'userexists' ); } - if ( !$loaded ) { - throw new MWException( __METHOD__ . ": hit a key conflict attempting " . - "to insert user '{$this->mName}' row, but it was not present in select!" ); - } - return Status::newFatal( 'userexists' ); + $this->mId = $dbw->insertId(); + self::$idCacheByName[$this->mName] = $this->mId; + $this->updateActorId( $dbw ); + + return Status::newGood(); + } ); + if ( !$status->isGood() ) { + return $status; } - $this->mId = $dbw->insertId(); - self::$idCacheByName[$this->mName] = $this->mId; - // Clear instance cache other than user table data, which is already accurate + // Clear instance cache other than user table data and actor, which is already accurate $this->clearInstanceCache(); $this->saveOptions(); return Status::newGood(); } + /** + * Update the actor ID after an insert + * @param IDatabase $dbw Writable database handle + */ + private function updateActorId( IDatabase $dbw ) { + global $wgActorTableSchemaMigrationStage; + + if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) { + $dbw->insert( + 'actor', + [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ], + __METHOD__ + ); + $this->mActorId = (int)$dbw->insertId(); + } + } + /** * If this user is logged-in and blocked, * block any IP address they've successfully logged in from. @@ -4728,10 +4904,14 @@ class User implements IDBAccessObject, UserIdentity { return false; // anons } $dbr = wfGetDB( DB_REPLICA ); - $time = $dbr->selectField( 'revision', 'rev_timestamp', - [ 'rev_user' => $this->getId() ], + $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this ); + $time = $dbr->selectField( + [ 'revision' ] + $actorWhere['tables'], + 'rev_timestamp', + [ $actorWhere['conds'] ], __METHOD__, - [ 'ORDER BY' => 'rev_timestamp ASC' ] + [ 'ORDER BY' => 'rev_timestamp ASC' ], + $actorWhere['joins'] ); if ( !$time ) { return false; // no edits @@ -5179,11 +5359,14 @@ class User implements IDBAccessObject, UserIdentity { // Pull from a replica DB to be less cruel to servers // Accuracy isn't the point anyway here $dbr = wfGetDB( DB_REPLICA ); + $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this ); $count = (int)$dbr->selectField( - 'revision', - 'COUNT(rev_user)', - [ 'rev_user' => $this->getId() ], - __METHOD__ + [ 'revision' ] + $actorWhere['tables'], + 'COUNT(*)', + [ $actorWhere['conds'] ], + __METHOD__, + [], + $actorWhere['joins'] ); $count = $count + $add; @@ -5532,7 +5715,9 @@ class User implements IDBAccessObject, UserIdentity { * - joins: (array) to include in the `$join_conds` to `IDatabase->select()` */ public static function getQueryInfo() { - return [ + global $wgActorTableSchemaMigrationStage; + + $ret = [ 'tables' => [ 'user' ], 'fields' => [ 'user_id', @@ -5549,6 +5734,15 @@ class User implements IDBAccessObject, UserIdentity { ], 'joins' => [], ]; + if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) { + $ret['tables']['user_actor'] = 'actor'; + $ret['fields'][] = 'user_actor.actor_id'; + $ret['joins']['user_actor'] = [ + $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN', + [ 'user_actor.actor_user = user_id' ] + ]; + } + return $ret; } /**