MediaWiki 1.40.4
RenameuserSQL.php
Go to the documentation of this file.
1<?php
2
4
15use Psr\Log\LoggerInterface;
17use SpecialLog;
18use User;
20
30 public $old;
31
37 public $new;
38
44 public $uid;
45
51 public $tables;
52
58 public $tablesJob;
59
67
73 private $renamer;
74
80 private $reason = '';
81
87 private $debugPrefix = '';
88
89 // B/C constants for tablesJob field
90 public const NAME_COL = 0;
91 public const UID_COL = 1;
92 public const TIME_COL = 2;
93
95 private $hookRunner;
96
98 private $loadBalancer;
99
101 private $userFactory;
102
104 private $jobQueueGroup;
105
107 private $titleFactory;
108
110 private $logger;
111
113 private $updateRowsPerJob;
114
127 public function __construct( $old, $new, $uid, User $renamer, $options = [] ) {
128 $services = MediaWikiServices::getInstance();
129 $this->hookRunner = new HookRunner( $services->getHookContainer() );
130 $this->loadBalancer = $services->getDBLoadBalancer();
131 $this->userFactory = $services->getUserFactory();
132 $this->jobQueueGroup = $services->getJobQueueGroup();
133 $this->titleFactory = $services->getTitleFactory();
134 $this->updateRowsPerJob = $services->getMainConfig()->get( MainConfigNames::UpdateRowsPerJob );
135 $this->logger = LoggerFactory::getInstance( 'Renameuser' );
136
137 $this->old = $old;
138 $this->new = $new;
139 $this->uid = $uid;
140 $this->renamer = $renamer;
141 $this->checkIfUserExists = true;
142
143 if ( isset( $options['checkIfUserExists'] ) ) {
144 $this->checkIfUserExists = $options['checkIfUserExists'];
145 }
146
147 if ( isset( $options['debugPrefix'] ) ) {
148 $this->debugPrefix = $options['debugPrefix'];
149 }
150
151 if ( isset( $options['reason'] ) ) {
152 $this->reason = $options['reason'];
153 }
154
155 $this->tables = []; // Immediate updates
156 $this->tablesJob = []; // Slow updates
157
158 $this->hookRunner->onRenameUserSQL( $this );
159 }
160
161 protected function debug( $msg ) {
162 if ( $this->debugPrefix ) {
163 $msg = "{$this->debugPrefix}: $msg";
164 }
165 $this->logger->debug( $msg );
166 }
167
172 public function rename() {
173 // Grab the user's edit count first, used in log entry
174 $contribs = $this->userFactory->newFromId( $this->uid )->getEditCount();
175
176 $dbw = $this->loadBalancer->getConnection( DB_PRIMARY );
177 $atomicId = $dbw->startAtomic( __METHOD__, $dbw::ATOMIC_CANCELABLE );
178
179 $this->hookRunner->onRenameUserPreRename( $this->uid, $this->old, $this->new );
180
181 // Make sure the user exists if needed
182 if ( $this->checkIfUserExists && !$this->lockUserAndGetId( $this->old ) ) {
183 $this->debug( "User {$this->old} does not exist, bailing out" );
184 $dbw->cancelAtomic( __METHOD__, $atomicId );
185
186 return false;
187 }
188
189 // Rename and touch the user before re-attributing edits to avoid users still being
190 // logged in and making new edits (under the old name) while being renamed.
191 $this->debug( "Starting rename of {$this->old} to {$this->new}" );
192 $dbw->update( 'user',
193 [ 'user_name' => $this->new, 'user_touched' => $dbw->timestamp() ],
194 [ 'user_name' => $this->old, 'user_id' => $this->uid ],
195 __METHOD__
196 );
197 $dbw->update( 'actor',
198 [ 'actor_name' => $this->new ],
199 [ 'actor_name' => $this->old, 'actor_user' => $this->uid ],
200 __METHOD__
201 );
202
203 // Reset token to break login with central auth systems.
204 // Again, avoids user being logged in with old name.
205 $user = $this->userFactory->newFromId( $this->uid );
206
207 $user->load( User::READ_LATEST );
208 SessionManager::singleton()->invalidateSessionsForUser( $user );
209
210 // Purge user cache
211 $user->invalidateCache();
212
213 // Update ipblock list if this user has a block in there.
214 $dbw->update( 'ipblocks',
215 [ 'ipb_address' => $this->new ],
216 [ 'ipb_user' => $this->uid, 'ipb_address' => $this->old ],
217 __METHOD__
218 );
219 // Update this users block/rights log. Ideally, the logs would be historical,
220 // but it is really annoying when users have "clean" block logs by virtue of
221 // being renamed, which makes admin tasks more of a pain...
222 $oldTitle = $this->titleFactory->makeTitle( NS_USER, $this->old );
223 $newTitle = $this->titleFactory->makeTitle( NS_USER, $this->new );
224 $this->debug( "Updating logging table for {$this->old} to {$this->new}" );
225
226 // Exclude user renames per T200731
227 $logTypesOnUser = array_diff( SpecialLog::getLogTypesOnUser(), [ 'renameuser' ] );
228
229 $dbw->update( 'logging',
230 [ 'log_title' => $newTitle->getDBkey() ],
231 [
232 'log_type' => $logTypesOnUser,
233 'log_namespace' => NS_USER,
234 'log_title' => $oldTitle->getDBkey()
235 ],
236 __METHOD__
237 );
238
239 $this->debug( "Updating recentchanges table for {$this->old} to {$this->new}" );
240 $dbw->update( 'recentchanges',
241 [ 'rc_title' => $newTitle->getDBkey() ],
242 [
243 'rc_type' => RC_LOG,
244 'rc_log_type' => $logTypesOnUser,
245 'rc_namespace' => NS_USER,
246 'rc_title' => $oldTitle->getDBkey()
247 ],
248 __METHOD__
249 );
250
251 // Do immediate re-attribution table updates...
252 foreach ( $this->tables as $table => $fieldSet ) {
253 list( $nameCol, $userCol ) = $fieldSet;
254 $dbw->update( $table,
255 [ $nameCol => $this->new ],
256 [ $nameCol => $this->old, $userCol => $this->uid ],
257 __METHOD__
258 );
259 }
260
262 $jobs = []; // jobs for all tables
263 // Construct jobqueue updates...
264 // FIXME: if a bureaucrat renames a user in error, he/she
265 // must be careful to wait until the rename finishes before
266 // renaming back. This is due to the fact the job "queue"
267 // is not really FIFO, so we might end up with a bunch of edits
268 // randomly mixed between the two new names. Some sort of rename
269 // lock might be in order...
270 foreach ( $this->tablesJob as $table => $params ) {
271 $userTextC = $params[self::NAME_COL]; // some *_user_text column
272 $userIDC = $params[self::UID_COL]; // some *_user column
273 $timestampC = $params[self::TIME_COL]; // some *_timestamp column
274
275 $res = $dbw->select( $table,
276 [ $timestampC ],
277 [ $userTextC => $this->old, $userIDC => $this->uid ],
278 __METHOD__,
279 [ 'ORDER BY' => "$timestampC ASC" ]
280 );
281
282 $jobParams = [];
283 $jobParams['table'] = $table;
284 $jobParams['column'] = $userTextC;
285 $jobParams['uidColumn'] = $userIDC;
286 $jobParams['timestampColumn'] = $timestampC;
287 $jobParams['oldname'] = $this->old;
288 $jobParams['newname'] = $this->new;
289 $jobParams['userID'] = $this->uid;
290 // Timestamp column data for index optimizations
291 $jobParams['minTimestamp'] = '0';
292 $jobParams['maxTimestamp'] = '0';
293 $jobParams['count'] = 0;
294 // Unique column for replica lag avoidance
295 if ( isset( $params['uniqueKey'] ) ) {
296 $jobParams['uniqueKey'] = $params['uniqueKey'];
297 }
298
299 // Insert jobs into queue!
300 foreach ( $res as $row ) {
301 # Since the ORDER BY is ASC, set the min timestamp with first row
302 if ( $jobParams['count'] === 0 ) {
303 $jobParams['minTimestamp'] = $row->$timestampC;
304 }
305 # Keep updating the last timestamp, so it should be correct
306 # when the last item is added.
307 $jobParams['maxTimestamp'] = $row->$timestampC;
308 # Update row counter
309 $jobParams['count']++;
310 # Once a job has $wgUpdateRowsPerJob rows, add it to the queue
311 if ( $jobParams['count'] >= $this->updateRowsPerJob ) {
312 $jobs[] = new JobSpecification( 'renameUser', $jobParams, [], $oldTitle );
313 $jobParams['minTimestamp'] = '0';
314 $jobParams['maxTimestamp'] = '0';
315 $jobParams['count'] = 0;
316 }
317 }
318 # If there are any job rows left, add it to the queue as one job
319 if ( $jobParams['count'] > 0 ) {
320 $jobs[] = new JobSpecification( 'renameUser', $jobParams, [], $oldTitle );
321 }
322 }
323
324 // Log it!
325 $logEntry = new ManualLogEntry( 'renameuser', 'renameuser' );
326 $logEntry->setPerformer( $this->renamer );
327 $logEntry->setTarget( $oldTitle );
328 $logEntry->setComment( $this->reason );
329 $logEntry->setParameters( [
330 '4::olduser' => $this->old,
331 '5::newuser' => $this->new,
332 '6::edits' => $contribs
333 ] );
334 $logid = $logEntry->insert();
335
336 // Insert any jobs as needed. If this fails, then an exception will be thrown and the
337 // DB transaction will be rolled back. If it succeeds but the DB commit fails, then the
338 // jobs will see that the transaction was not committed and will cancel themselves.
339 $count = count( $jobs );
340 if ( $count > 0 ) {
341 $this->jobQueueGroup->push( $jobs );
342 $this->debug( "Queued $count jobs for {$this->old} to {$this->new}" );
343 }
344
345 // Commit the transaction
346 $dbw->endAtomic( __METHOD__ );
347
348 $fname = __METHOD__;
349 $dbw->onTransactionCommitOrIdle(
350 function () use ( $dbw, $logEntry, $logid, $fname ) {
351 $dbw->startAtomic( $fname );
352 // Clear caches and inform authentication plugins
353 $user = $this->userFactory->newFromId( $this->uid );
354 $user->load( User::READ_LATEST );
355 // Trigger the UserSaveSettings hook
356 $user->saveSettings();
357 $this->hookRunner->onRenameUserComplete( $this->uid, $this->old, $this->new );
358 // Publish to RC
359 $logEntry->publish( $logid );
360 $dbw->endAtomic( $fname );
361 },
362 $fname
363 );
364
365 $this->debug( "Finished rename for {$this->old} to {$this->new}" );
366
367 return true;
368 }
369
374 private function lockUserAndGetId( $name ) {
375 return (int)$this->loadBalancer->getConnection( DB_PRIMARY )->selectField(
376 'user',
377 'user_id',
378 [ 'user_name' => $name ],
379 __METHOD__,
380 [ 'FOR UPDATE' ]
381 );
382 }
383}
const NS_USER
Definition Defines.php:66
const RC_LOG
Definition Defines.php:118
Handle enqueueing of background jobs.
Job queue task description base code.
Class for creating new log entries and inserting them into the database.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
PSR-3 logger instance factory.
A class containing constants representing the names of configuration variables.
const UpdateRowsPerJob
Name constant for the UpdateRowsPerJob setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Class which performs the actual renaming of users.
bool $checkIfUserExists
Flag that can be set to false, in case another process has already started the updates and the old us...
array $tables
The tables => fields to be updated.
__construct( $old, $new, $uid, User $renamer, $options=[])
Constructor.
array[] $tablesJob
tables => fields to be updated in a deferred job
rename()
Do the rename operation.
This serves as the entry point to the MediaWiki session handling system.
Creates Title objects.
Creates User objects.
Custom job to perform updates on tables in busier environments.
A special page that lists log entries.
static getLogTypesOnUser(HookRunner $runner=null)
List log type for which the target is a user Thus if the given target is in NS_MAIN we can alter it t...
internal since 1.36
Definition User.php:71
This class is a delegate to ILBFactory for a given database cluster.
const DB_PRIMARY
Definition defines.php:28