MediaWiki REL1_33
RenameuserSQL.php
Go to the documentation of this file.
1<?php
2
4
15 public $old;
16
23 public $new;
24
31 public $uid;
32
39 public $tables;
40
46 public $tablesJob;
47
56
62 private $renamer;
63
69 private $reason = '';
70
76 private $debugPrefix = '';
77
82 const CONTRIB_JOB = 500;
83
84 // B/C constants for tablesJob field
85 const NAME_COL = 0;
86 const UID_COL = 1;
87 const TIME_COL = 2;
88
101 public function __construct( $old, $new, $uid, User $renamer, $options = [] ) {
102 $this->old = $old;
103 $this->new = $new;
104 $this->uid = $uid;
105 $this->renamer = $renamer;
106 $this->checkIfUserExists = true;
107
108 if ( isset( $options['checkIfUserExists'] ) ) {
109 $this->checkIfUserExists = $options['checkIfUserExists'];
110 }
111
112 if ( isset( $options['debugPrefix'] ) ) {
113 $this->debugPrefix = $options['debugPrefix'];
114 }
115
116 if ( isset( $options['reason'] ) ) {
117 $this->reason = $options['reason'];
118 }
119
120 $this->tables = []; // Immediate updates
121 $this->tablesJob = []; // Slow updates
122
123 if ( self::actorMigrationWriteOld() ) {
124 // If this user has a large number of edits, use the jobqueue
125 // T134136: if this is for user_id=0, then use the queue as the edit count is unknown.
126 if ( !$uid || User::newFromId( $uid )->getEditCount() > self::CONTRIB_JOB ) {
127 $this->tablesJob['revision'] = [
128 self::NAME_COL => 'rev_user_text',
129 self::UID_COL => 'rev_user',
130 self::TIME_COL => 'rev_timestamp',
131 'uniqueKey' => 'rev_id'
132 ];
133 $this->tablesJob['archive'] = [
134 self::NAME_COL => 'ar_user_text',
135 self::UID_COL => 'ar_user',
136 self::TIME_COL => 'ar_timestamp',
137 'uniqueKey' => 'ar_id'
138 ];
139 $this->tablesJob['logging'] = [
140 self::NAME_COL => 'log_user_text',
141 self::UID_COL => 'log_user',
142 self::TIME_COL => 'log_timestamp',
143 'uniqueKey' => 'log_id'
144 ];
145 $this->tablesJob['image'] = [
146 self::NAME_COL => 'img_user_text',
147 self::UID_COL => 'img_user',
148 self::TIME_COL => 'img_timestamp',
149 'uniqueKey' => 'img_name'
150 ];
151 $this->tablesJob['oldimage'] = [
152 self::NAME_COL => 'oi_user_text',
153 self::UID_COL => 'oi_user',
154 self::TIME_COL => 'oi_timestamp'
155 ];
156 $this->tablesJob['filearchive'] = [
157 self::NAME_COL => 'fa_user_text',
158 self::UID_COL => 'fa_user',
159 self::TIME_COL => 'fa_timestamp',
160 'uniqueKey' => 'fa_id'
161 ];
162 } else {
163 $this->tables['revision'] = [ 'rev_user_text', 'rev_user' ];
164 $this->tables['archive'] = [ 'ar_user_text', 'ar_user' ];
165 $this->tables['logging'] = [ 'log_user_text', 'log_user' ];
166 $this->tables['image'] = [ 'img_user_text', 'img_user' ];
167 $this->tables['oldimage'] = [ 'oi_user_text', 'oi_user' ];
168 $this->tables['filearchive'] = [ 'fa_user_text', 'fa_user' ];
169 }
170
171 // Recent changes is pretty hot, deadlocks occur if done all at once
172 if ( wfQueriesMustScale() ) {
173 $this->tablesJob['recentchanges'] = [ 'rc_user_text', 'rc_user', 'rc_timestamp' ];
174 } else {
175 $this->tables['recentchanges'] = [ 'rc_user_text', 'rc_user' ];
176 }
177 }
178
179 Hooks::run( 'RenameUserSQL', [ $this ] );
180 }
181
182 protected function debug( $msg ) {
183 if ( $this->debugPrefix ) {
184 $msg = "{$this->debugPrefix}: $msg";
185 }
186 wfDebugLog( 'Renameuser', $msg );
187 }
188
193 public function rename() {
194 global $wgUpdateRowsPerJob;
195
196 // Grab the user's edit count first, used in log entry
197 $contribs = User::newFromId( $this->uid )->getEditCount();
198
199 $dbw = wfGetDB( DB_MASTER );
200 $atomicId = $dbw->startAtomic( __METHOD__, $dbw::ATOMIC_CANCELABLE );
201
202 Hooks::run( 'RenameUserPreRename', [ $this->uid, $this->old, $this->new ] );
203
204 // Make sure the user exists if needed
205 if ( $this->checkIfUserExists && !self::lockUserAndGetId( $this->old ) ) {
206 $this->debug( "User {$this->old} does not exist, bailing out" );
207 $dbw->cancelAtomic( __METHOD__, $atomicId );
208
209 return false;
210 }
211
212 // Rename and touch the user before re-attributing edits to avoid users still being
213 // logged in and making new edits (under the old name) while being renamed.
214 $this->debug( "Starting rename of {$this->old} to {$this->new}" );
215 $dbw->update( 'user',
216 [ 'user_name' => $this->new, 'user_touched' => $dbw->timestamp() ],
217 [ 'user_name' => $this->old, 'user_id' => $this->uid ],
218 __METHOD__
219 );
220 if ( self::actorMigrationWriteNew() ) {
221 $dbw->update( 'actor',
222 [ 'actor_name' => $this->new ],
223 [ 'actor_name' => $this->old, 'actor_user' => $this->uid ],
224 __METHOD__
225 );
226 }
227
228 // Reset token to break login with central auth systems.
229 // Again, avoids user being logged in with old name.
230 $user = User::newFromId( $this->uid );
231
232 $user->load( User::READ_LATEST );
233 SessionManager::singleton()->invalidateSessionsForUser( $user );
234
235 // Purge user cache
236 $user->invalidateCache();
237
238 // Update ipblock list if this user has a block in there.
239 $dbw->update( 'ipblocks',
240 [ 'ipb_address' => $this->new ],
241 [ 'ipb_user' => $this->uid, 'ipb_address' => $this->old ],
242 __METHOD__
243 );
244 // Update this users block/rights log. Ideally, the logs would be historical,
245 // but it is really annoying when users have "clean" block logs by virtue of
246 // being renamed, which makes admin tasks more of a pain...
247 $oldTitle = Title::makeTitle( NS_USER, $this->old );
248 $newTitle = Title::makeTitle( NS_USER, $this->new );
249 $this->debug( "Updating logging table for {$this->old} to {$this->new}" );
250
251 $logTypesOnUser = SpecialLog::getLogTypesOnUser();
252
253 $dbw->update( 'logging',
254 [ 'log_title' => $newTitle->getDBkey() ],
255 [ 'log_type' => $logTypesOnUser,
256 'log_namespace' => NS_USER,
257 'log_title' => $oldTitle->getDBkey() ],
258 __METHOD__
259 );
260
261 // Do immediate re-attribution table updates...
262 foreach ( $this->tables as $table => $fieldSet ) {
263 list( $nameCol, $userCol ) = $fieldSet;
264 $dbw->update( $table,
265 [ $nameCol => $this->new ],
266 [ $nameCol => $this->old, $userCol => $this->uid ],
267 __METHOD__
268 );
269 }
270
272 $jobs = []; // jobs for all tables
273 // Construct jobqueue updates...
274 // FIXME: if a bureaucrat renames a user in error, he/she
275 // must be careful to wait until the rename finishes before
276 // renaming back. This is due to the fact the job "queue"
277 // is not really FIFO, so we might end up with a bunch of edits
278 // randomly mixed between the two new names. Some sort of rename
279 // lock might be in order...
280 foreach ( $this->tablesJob as $table => $params ) {
281 $userTextC = $params[self::NAME_COL]; // some *_user_text column
282 $userIDC = $params[self::UID_COL]; // some *_user column
283 $timestampC = $params[self::TIME_COL]; // some *_timestamp column
284
285 $res = $dbw->select( $table,
286 [ $timestampC ],
287 [ $userTextC => $this->old, $userIDC => $this->uid ],
288 __METHOD__,
289 [ 'ORDER BY' => "$timestampC ASC" ]
290 );
291
292 $jobParams = [];
293 $jobParams['table'] = $table;
294 $jobParams['column'] = $userTextC;
295 $jobParams['uidColumn'] = $userIDC;
296 $jobParams['timestampColumn'] = $timestampC;
297 $jobParams['oldname'] = $this->old;
298 $jobParams['newname'] = $this->new;
299 $jobParams['userID'] = $this->uid;
300 // Timestamp column data for index optimizations
301 $jobParams['minTimestamp'] = '0';
302 $jobParams['maxTimestamp'] = '0';
303 $jobParams['count'] = 0;
304 // Unique column for slave lag avoidance
305 if ( isset( $params['uniqueKey'] ) ) {
306 $jobParams['uniqueKey'] = $params['uniqueKey'];
307 }
308
309 // Insert jobs into queue!
310 while ( true ) {
311 $row = $dbw->fetchObject( $res );
312 if ( !$row ) {
313 # If there are any job rows left, add it to the queue as one job
314 if ( $jobParams['count'] > 0 ) {
315 $jobs[] = Job::factory( 'renameUser', $oldTitle, $jobParams );
316 }
317 break;
318 }
319 # Since the ORDER BY is ASC, set the min timestamp with first row
320 if ( $jobParams['count'] === 0 ) {
321 $jobParams['minTimestamp'] = $row->$timestampC;
322 }
323 # Keep updating the last timestamp, so it should be correct
324 # when the last item is added.
325 $jobParams['maxTimestamp'] = $row->$timestampC;
326 # Update row counter
327 $jobParams['count']++;
328 # Once a job has $wgUpdateRowsPerJob rows, add it to the queue
329 if ( $jobParams['count'] >= $wgUpdateRowsPerJob ) {
330 $jobs[] = Job::factory( 'renameUser', $oldTitle, $jobParams );
331 $jobParams['minTimestamp'] = '0';
332 $jobParams['maxTimestamp'] = '0';
333 $jobParams['count'] = 0;
334 }
335 }
336 }
337
338 // Log it!
339 $logEntry = new ManualLogEntry( 'renameuser', 'renameuser' );
340 $logEntry->setPerformer( $this->renamer );
341 $logEntry->setTarget( $oldTitle );
342 $logEntry->setComment( $this->reason );
343 $logEntry->setParameters( [
344 '4::olduser' => $this->old,
345 '5::newuser' => $this->new,
346 '6::edits' => $contribs
347 ] );
348 $logid = $logEntry->insert();
349 // Include the log_id in the jobs as a DB commit marker
350 foreach ( $jobs as $job ) {
351 $job->params['logId'] = $logid;
352 }
353
354 // Insert any jobs as needed. If this fails, then an exception will be thrown and the
355 // DB transaction will be rolled back. If it succeeds but the DB commit fails, then the
356 // jobs will see that the transaction was not committed and will cancel themselves.
357 $count = count( $jobs );
358 if ( $count > 0 ) {
359 JobQueueGroup::singleton()->push( $jobs );
360 $this->debug( "Queued $count jobs for {$this->old} to {$this->new}" );
361 }
362
363 // Commit the transaction
364 $dbw->endAtomic( __METHOD__ );
365
366 $that = $this;
368 $dbw->onTransactionIdle( function () use ( $that, $dbw, $logEntry, $logid, $fname ) {
369 $dbw->startAtomic( $fname );
370 // Clear caches and inform authentication plugins
371 $user = User::newFromId( $that->uid );
372 $user->load( User::READ_LATEST );
373 // Trigger the UserSaveSettings hook
374 $user->saveSettings();
375 Hooks::run( 'RenameUserComplete', [ $that->uid, $that->old, $that->new ] );
376 // Publish to RC
377 $logEntry->publish( $logid );
378 $dbw->endAtomic( $fname );
379 } );
380
381 $this->debug( "Finished rename for {$this->old} to {$this->new}" );
382
383 return true;
384 }
385
390 private static function lockUserAndGetId( $name ) {
391 return (int)wfGetDB( DB_MASTER )->selectField(
392 'user',
393 'user_id',
394 [ 'user_name' => $name ],
395 __METHOD__,
396 [ 'FOR UPDATE' ]
397 );
398 }
399
404 public static function actorMigrationWriteOld() {
406
407 if ( !is_callable( User::class, 'getActorId' ) ) {
408 return true;
409 }
411 return false;
412 }
413
414 if ( defined( 'ActorMigration::MIGRATION_STAGE_SCHEMA_COMPAT' ) ) {
416 } else {
417 // Return true even for MIGRATION_WRITE_NEW because reads might still be falling back
419 }
420 }
421
426 public static function actorMigrationWriteNew() {
428
429 if ( !is_callable( User::class, 'getActorId' ) ) {
430 return false;
431 }
433 return true;
434 }
435
436 if ( defined( 'ActorMigration::MIGRATION_STAGE_SCHEMA_COMPAT' ) ) {
438 } else {
440 }
441 }
442}
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
$wgUpdateRowsPerJob
Number of rows to update per job.
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:123
static factory( $command, $params=[])
Create the appropriate object to handle a specific job.
Definition Job.php:72
Class for creating new log entries and inserting them into the database.
Definition LogEntry.php:441
This serves as the entry point to the MediaWiki session handling system.
Class which performs the actual renaming of users.
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.
static actorMigrationWriteOld()
Indicate whether we should still write old user fields.
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.
static actorMigrationWriteNew()
Indicate whether we should write new actor fields.
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:48
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:609
$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:1999
versus $oldTitle
Definition globals.txt:16
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:293
const MIGRATION_NEW
Definition Defines.php:327
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:295
const MIGRATION_OLD
Definition Defines.php:324
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