MediaWiki REL1_32
RenameuserSQL.php
Go to the documentation of this file.
1<?php
2
5
16 public $old;
17
24 public $new;
25
32 public $uid;
33
40 public $tables;
41
47 public $tablesJob;
48
57
63 private $renamer;
64
70 private $reason = '';
71
77 private $debugPrefix = '';
78
83 const CONTRIB_JOB = 500;
84
85 // B/C constants for tablesJob field
86 const NAME_COL = 0;
87 const UID_COL = 1;
88 const TIME_COL = 2;
89
102 public function __construct( $old, $new, $uid, User $renamer, $options = [] ) {
103 $this->old = $old;
104 $this->new = $new;
105 $this->uid = $uid;
106 $this->renamer = $renamer;
107 $this->checkIfUserExists = true;
108
109 if ( isset( $options['checkIfUserExists'] ) ) {
110 $this->checkIfUserExists = $options['checkIfUserExists'];
111 }
112
113 if ( isset( $options['debugPrefix'] ) ) {
114 $this->debugPrefix = $options['debugPrefix'];
115 }
116
117 if ( isset( $options['reason'] ) ) {
118 $this->reason = $options['reason'];
119 }
120
121 $this->tables = []; // Immediate updates
122 $this->tablesJob = []; // Slow updates
123
124 // We still do the table updates here for MIGRATION_WRITE_NEW because reads might
125 // still be falling back.
126 if ( self::getActorMigrationStage() < MIGRATION_NEW ) {
127 $this->tables['image'] = [ 'img_user_text', 'img_user' ];
128 $this->tables['oldimage'] = [ 'oi_user_text', 'oi_user' ];
129 $this->tables['filearchive'] = [ 'fa_user_text', 'fa_user' ];
130
131 // If this user has a large number of edits, use the jobqueue
132 // T134136: if this is for user_id=0, then use the queue as the edit count is unknown.
133 if ( !$uid || User::newFromId( $uid )->getEditCount() > self::CONTRIB_JOB ) {
134 $this->tablesJob['revision'] = [
135 self::NAME_COL => 'rev_user_text',
136 self::UID_COL => 'rev_user',
137 self::TIME_COL => 'rev_timestamp',
138 'uniqueKey' => 'rev_id'
139 ];
140 $this->tablesJob['archive'] = [
141 self::NAME_COL => 'ar_user_text',
142 self::UID_COL => 'ar_user',
143 self::TIME_COL => 'ar_timestamp',
144 'uniqueKey' => 'ar_id'
145 ];
146 $this->tablesJob['logging'] = [
147 self::NAME_COL => 'log_user_text',
148 self::UID_COL => 'log_user',
149 self::TIME_COL => 'log_timestamp',
150 'uniqueKey' => 'log_id'
151 ];
152 } else {
153 $this->tables['revision'] = [ 'rev_user_text', 'rev_user' ];
154 $this->tables['archive'] = [ 'ar_user_text', 'ar_user' ];
155 $this->tables['logging'] = [ 'log_user_text', 'log_user' ];
156 }
157 // Recent changes is pretty hot, deadlocks occur if done all at once
158 if ( wfQueriesMustScale() ) {
159 $this->tablesJob['recentchanges'] = [ 'rc_user_text', 'rc_user', 'rc_timestamp' ];
160 } else {
161 $this->tables['recentchanges'] = [ 'rc_user_text', 'rc_user' ];
162 }
163 }
164
165 Hooks::run( 'RenameUserSQL', [ $this ] );
166 }
167
168 protected function debug( $msg ) {
169 if ( $this->debugPrefix ) {
170 $msg = "{$this->debugPrefix}: $msg";
171 }
172 wfDebugLog( 'Renameuser', $msg );
173 }
174
179 public function rename() {
181
182 // Grab the user's edit count first, used in log entry
183 $contribs = User::newFromId( $this->uid )->getEditCount();
184
185 $dbw = wfGetDB( DB_MASTER );
186 $dbw->startAtomic( __METHOD__ );
187
188 Hooks::run( 'RenameUserPreRename', [ $this->uid, $this->old, $this->new ] );
189
190 // Make sure the user exists if needed
191 if ( $this->checkIfUserExists && !self::lockUserAndGetId( $this->old ) ) {
192 $this->debug( "User {$this->old} does not exist, bailing out" );
193
194 return false;
195 }
196
197 // Rename and touch the user before re-attributing edits to avoid users still being
198 // logged in and making new edits (under the old name) while being renamed.
199 $this->debug( "Starting rename of {$this->old} to {$this->new}" );
200 $dbw->update( 'user',
201 [ 'user_name' => $this->new, 'user_touched' => $dbw->timestamp() ],
202 [ 'user_name' => $this->old, 'user_id' => $this->uid ],
203 __METHOD__
204 );
205 if ( self::getActorMigrationStage() >= MIGRATION_WRITE_BOTH ) {
206 $dbw->update( 'actor',
207 [ 'actor_name' => $this->new ],
208 [ 'actor_name' => $this->old, 'actor_user' => $this->uid ],
209 __METHOD__
210 );
211 }
212
213 // Reset token to break login with central auth systems.
214 // Again, avoids user being logged in with old name.
215 $user = User::newFromId( $this->uid );
216
217 if ( class_exists( SessionManager::class ) &&
218 is_callable( [ SessionManager::singleton(), 'invalidateSessionsForUser' ] )
219 ) {
220 $user->load( User::READ_LATEST );
221 SessionManager::singleton()->invalidateSessionsForUser( $user );
222 } else {
223 $authUser = $wgAuth->getUserInstance( $user );
224 $authUser->resetAuthToken();
225 }
226
227 // Purge user cache
228 $user->invalidateCache();
229
230 // Update ipblock list if this user has a block in there.
231 $dbw->update( 'ipblocks',
232 [ 'ipb_address' => $this->new ],
233 [ 'ipb_user' => $this->uid, 'ipb_address' => $this->old ],
234 __METHOD__
235 );
236 // Update this users block/rights log. Ideally, the logs would be historical,
237 // but it is really annoying when users have "clean" block logs by virtue of
238 // being renamed, which makes admin tasks more of a pain...
239 $oldTitle = Title::makeTitle( NS_USER, $this->old );
240 $newTitle = Title::makeTitle( NS_USER, $this->new );
241 $this->debug( "Updating logging table for {$this->old} to {$this->new}" );
242
243 $logTypesOnUser = SpecialLog::getLogTypesOnUser();
244
245 $dbw->update( 'logging',
246 [ 'log_title' => $newTitle->getDBkey() ],
247 [ 'log_type' => $logTypesOnUser,
248 'log_namespace' => NS_USER,
249 'log_title' => $oldTitle->getDBkey() ],
250 __METHOD__
251 );
252
253 // Do immediate re-attribution table updates...
254 foreach ( $this->tables as $table => $fieldSet ) {
255 list( $nameCol, $userCol ) = $fieldSet;
256 $dbw->update( $table,
257 [ $nameCol => $this->new ],
258 [ $nameCol => $this->old, $userCol => $this->uid ],
259 __METHOD__
260 );
261 }
262
264 $jobs = []; // jobs for all tables
265 // Construct jobqueue updates...
266 // FIXME: if a bureaucrat renames a user in error, he/she
267 // must be careful to wait until the rename finishes before
268 // renaming back. This is due to the fact the job "queue"
269 // is not really FIFO, so we might end up with a bunch of edits
270 // randomly mixed between the two new names. Some sort of rename
271 // lock might be in order...
272 foreach ( $this->tablesJob as $table => $params ) {
273 $userTextC = $params[self::NAME_COL]; // some *_user_text column
274 $userIDC = $params[self::UID_COL]; // some *_user column
275 $timestampC = $params[self::TIME_COL]; // some *_timestamp column
276
277 $res = $dbw->select( $table,
278 [ $timestampC ],
279 [ $userTextC => $this->old, $userIDC => $this->uid ],
280 __METHOD__,
281 [ 'ORDER BY' => "$timestampC ASC" ]
282 );
283
284 $jobParams = [];
285 $jobParams['table'] = $table;
286 $jobParams['column'] = $userTextC;
287 $jobParams['uidColumn'] = $userIDC;
288 $jobParams['timestampColumn'] = $timestampC;
289 $jobParams['oldname'] = $this->old;
290 $jobParams['newname'] = $this->new;
291 $jobParams['userID'] = $this->uid;
292 // Timestamp column data for index optimizations
293 $jobParams['minTimestamp'] = '0';
294 $jobParams['maxTimestamp'] = '0';
295 $jobParams['count'] = 0;
296 // Unique column for slave lag avoidance
297 if ( isset( $params['uniqueKey'] ) ) {
298 $jobParams['uniqueKey'] = $params['uniqueKey'];
299 }
300
301 // Insert jobs into queue!
302 while ( true ) {
303 $row = $dbw->fetchObject( $res );
304 if ( !$row ) {
305 # If there are any job rows left, add it to the queue as one job
306 if ( $jobParams['count'] > 0 ) {
307 $jobs[] = Job::factory( 'renameUser', $oldTitle, $jobParams );
308 }
309 break;
310 }
311 # Since the ORDER BY is ASC, set the min timestamp with first row
312 if ( $jobParams['count'] === 0 ) {
313 $jobParams['minTimestamp'] = $row->$timestampC;
314 }
315 # Keep updating the last timestamp, so it should be correct
316 # when the last item is added.
317 $jobParams['maxTimestamp'] = $row->$timestampC;
318 # Update row counter
319 $jobParams['count']++;
320 # Once a job has $wgUpdateRowsPerJob rows, add it to the queue
321 if ( $jobParams['count'] >= $wgUpdateRowsPerJob ) {
322 $jobs[] = Job::factory( 'renameUser', $oldTitle, $jobParams );
323 $jobParams['minTimestamp'] = '0';
324 $jobParams['maxTimestamp'] = '0';
325 $jobParams['count'] = 0;
326 }
327 }
328 $dbw->freeResult( $res );
329 }
330
331 // Log it!
332 $logEntry = new ManualLogEntry( 'renameuser', 'renameuser' );
333 $logEntry->setPerformer( $this->renamer );
334 $logEntry->setTarget( $oldTitle );
335 $logEntry->setComment( $this->reason );
336 $logEntry->setParameters( [
337 '4::olduser' => $this->old,
338 '5::newuser' => $this->new,
339 '6::edits' => $contribs
340 ] );
341 $logid = $logEntry->insert();
342 // Include the log_id in the jobs as a DB commit marker
343 foreach ( $jobs as $job ) {
344 $job->params['logId'] = $logid;
345 }
346
347 // Insert any jobs as needed. If this fails, then an exception will be thrown and the
348 // DB transaction will be rolled back. If it succeeds but the DB commit fails, then the
349 // jobs will see that the transaction was not committed and will cancel themselves.
350 $count = count( $jobs );
351 if ( $count > 0 ) {
352 JobQueueGroup::singleton()->push( $jobs );
353 $this->debug( "Queued $count jobs for {$this->old} to {$this->new}" );
354 }
355
356 // Commit the transaction
357 $dbw->endAtomic( __METHOD__ );
358
359 $that = $this;
360 $fname = __METHOD__;
361 $dbw->onTransactionIdle( function () use ( $that, $dbw, $logEntry, $logid, $fname ) {
362 $dbw->startAtomic( $fname );
363 // Clear caches and inform authentication plugins
364 $user = User::newFromId( $that->uid );
365 $user->load( User::READ_LATEST );
366 // Call $wgAuth for backwards compatibility
367 if ( class_exists( AuthManager::class ) ) {
368 AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] );
369 } else {
370 global $wgAuth;
371 $wgAuth->updateExternalDB( $user );
372 }
373 // Trigger the UserSaveSettings hook, which is the replacement for
374 // $wgAuth->updateExternalDB()
375 $user->saveSettings();
376 Hooks::run( 'RenameUserComplete', [ $that->uid, $that->old, $that->new ] );
377 // Publish to RC
378 $logEntry->publish( $logid );
379 $dbw->endAtomic( $fname );
380 } );
381
382 $this->debug( "Finished rename for {$this->old} to {$this->new}" );
383
384 return true;
385 }
386
391 private static function lockUserAndGetId( $name ) {
392 return (int)wfGetDB( DB_MASTER )->selectField(
393 'user',
394 'user_id',
395 [ 'user_name' => $name ],
396 __METHOD__,
397 [ 'FOR UPDATE' ]
398 );
399 }
400
405 public static function getActorMigrationStage() {
407
410 : ( is_callable( User::class, 'getActorId' ) ? MIGRATION_NEW : MIGRATION_OLD );
411 }
412}
$wgUpdateRowsPerJob
Number of rows to update per job.
$wgAuth $wgAuth
Authentication plugin.
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfQueriesMustScale()
Should low-performance queries be disabled?
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings,...
Definition Setup.php:121
static factory( $command, Title $title, $params=[])
Create the appropriate object to handle a specific job.
Definition Job.php:73
Class for creating new log entries and inserting them into the database.
Definition LogEntry.php:437
This serves as the entry point to the authentication system.
This serves as the entry point to the MediaWiki session handling system.
Class which performs the actual renaming of users.
static getActorMigrationStage()
Fetch the core actor table schema migration stage.
integer $uid
The user ID.
array $tablesJob
tables => fields to be updated in a deferred job
array $tables
The tables => fields to be updated.
string $old
The old username.
User $renamer
User object of the user performing the rename, for logging purposes.
static lockUserAndGetId( $name)
string $reason
Reason to be used in the log entry.
string $debugPrefix
A prefix to use in all debug log messages.
rename()
Do the rename operation.
const CONTRIB_JOB
Users with more than this number of edits will have their rename operation deferred via the job queue...
bool $checkIfUserExists
Flag that can be set to false, in case another process has already started the updates and the old us...
string $new
The new username.
__construct( $old, $new, $uid, User $renamer, $options=[])
Constructor.
static getLogTypesOnUser()
List log type for which the target is a user Thus if the given target is in NS_MAIN we can alter it t...
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:615
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
versus $oldTitle
Definition globals.txt:16
const MIGRATION_NEW
Definition Defines.php:318
const MIGRATION_WRITE_BOTH
Definition Defines.php:316
const MIGRATION_OLD
Definition Defines.php:315
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
In both all secondary updates will be triggered handle like object that caches derived data representing a and can trigger updates of cached copies of that e g in the links tables
const DB_MASTER
Definition defines.php:26
if(count( $args)< 1) $job
$params