MediaWiki REL1_40
renameUsersMatchingPattern.php
Go to the documentation of this file.
1<?php
2
9
10require_once __DIR__ . '/Maintenance.php';
11
14 private $userFactory;
15
17 private $movePageFactory;
18
20 private $titleFactory;
21
23 private $performer;
24
26 private $reason;
27
29 private $dryRun;
30
32 private $suppressRedirect;
33
35 private $skipPageMoves;
36
37 public function __construct() {
38 parent::__construct();
39
40 $this->addDescription( 'Rename users with a name matching a pattern. ' .
41 'This can be used to migrate to a temporary user (IP masking) configuration.' );
42 $this->addOption( 'from', 'A username pattern where $1 is ' .
43 'the wildcard standing in for any number of characters. All users ' .
44 'matching this pattern will be renamed.', true, true );
45 $this->addOption( 'to', 'A username pattern where $1 is ' .
46 'the part of the username matched by $1 in --from. Users will be ' .
47 ' renamed to this pattern.', true, true );
48 $this->addOption( 'performer', 'Performer of the rename action', false, true );
49 $this->addOption( 'reason', 'Reason of the rename', false, true );
50 $this->addOption( 'suppress-redirect', 'Don\'t create redirects when moving pages' );
51 $this->addOption( 'skip-page-moves', 'Don\'t move associated user pages' );
52 $this->addOption( 'dry-run', 'Don\'t actually rename the ' .
53 'users, just report what it would do.' );
54 $this->setBatchSize( 1000 );
55 }
56
57 private function initServices() {
58 $services = MediaWikiServices::getInstance();
59 if ( $services->getCentralIdLookupFactory()->getNonLocalLookup() ) {
60 $this->fatalError( "This script cannot be run when CentralAuth is enabled." );
61 }
62 $this->userFactory = $services->getUserFactory();
63 $this->movePageFactory = $services->getMovePageFactory();
64 $this->titleFactory = $services->getTitleFactory();
65 }
66
67 public function execute() {
68 $this->initServices();
69
70 $fromPattern = new Pattern( 'from', $this->getOption( 'from' ) );
71 $toPattern = new Pattern( 'to', $this->getOption( 'to' ) );
72
73 if ( $this->getOption( 'performer' ) === null ) {
74 $performer = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] );
75 } else {
76 $performer = $this->userFactory->newFromName( $this->getOption( 'performer' ) );
77 }
78 if ( !$performer ) {
79 $this->error( "Unable to get performer account" );
80 return false;
81 }
82 $this->performer = $performer;
83
84 $this->reason = $this->getOption( 'reason', '' );
85 $this->dryRun = $this->getOption( 'dry-run' );
86 $this->suppressRedirect = $this->getOption( 'suppress-redirect' );
87 $this->skipPageMoves = $this->getOption( 'skip-page-moves' );
88
89 $dbr = $this->getDB( DB_REPLICA );
90 $batchConds = [];
91 $batchSize = $this->getBatchSize();
92 $numRenamed = 0;
93 do {
94 $res = $dbr->newSelectQueryBuilder()
95 ->select( [ 'user_name' ] )
96 ->from( 'user' )
97 ->where( array_merge(
98 [ 'user_name' . $fromPattern->buildLike( $dbr ) ],
99 $batchConds
100 ) )
101 ->orderBy( 'user_name' )
102 ->limit( $batchSize )
103 ->caller( __METHOD__ )
104 ->fetchResultSet();
105
106 foreach ( $res as $row ) {
107 $oldName = $row->user_name;
108 $batchConds = [ 'user_name > ' . $dbr->addQuotes( $oldName ) ];
109 $variablePart = $fromPattern->extract( $oldName );
110 if ( $variablePart === null ) {
111 $this->output( "Username \"fromName\" matched the LIKE " .
112 "but does not seem to match the pattern" );
113 continue;
114 }
115 $newName = $toPattern->generate( $variablePart );
116 $numRenamed += $this->renameUser( $oldName, $newName ) ? 1 : 0;
117 $this->waitForReplication();
118 }
119 } while ( $res->numRows() === $batchSize );
120 $this->output( "Renamed $numRenamed user(s)\n" );
121 return true;
122 }
123
129 private function renameUser( $oldName, $newName ) {
130 $id = $this->userFactory->newFromName( $oldName )->getId();
131 if ( !$id ) {
132 $this->output( "Cannot rename non-existent user \"$oldName\"" );
133 }
134
135 if ( $this->dryRun ) {
136 $this->output( "$oldName would be renamed to $newName\n" );
137 } else {
138 $renamer = new RenameuserSQL(
139 $oldName,
140 $newName,
141 $id,
142 $this->performer,
143 [
144 'reason' => $this->reason
145 ]
146 );
147
148 if ( !$renamer->rename() ) {
149 $this->output( "Unable to rename $oldName" );
150 return false;
151 } else {
152 $this->output( "$oldName was successfully renamed to $newName.\n" );
153 }
154 }
155
156 if ( $this->skipPageMoves ) {
157 return true;
158 }
159
160 $this->movePageAndSubpages( NS_USER, 'User', $oldName, $newName );
161 $this->movePageAndSubpages( NS_USER_TALK, 'User talk', $oldName, $newName );
162 return true;
163 }
164
165 private function movePageAndSubpages( $ns, $nsName, $oldName, $newName ) {
166 $oldTitle = $this->titleFactory->makeTitleSafe( $ns, $oldName );
167 if ( !$oldTitle ) {
168 $this->output( "[[$nsName:$oldName]] is an invalid title, can't move it.\n" );
169 return true;
170 }
171 $newTitle = $this->titleFactory->makeTitleSafe( $ns, $newName );
172 if ( !$newTitle ) {
173 $this->output( "[[$nsName:$newName]] is an invalid title, can't move to it.\n" );
174 return true;
175 }
176
177 $movePage = $this->movePageFactory->newMovePage( $oldTitle, $newTitle );
178 $movePage->setMaximumMovedPages( -1 );
179
180 $logMessage = wfMessage(
181 'renameuser-move-log', $oldName, $newName
182 )->inContentLanguage()->text();
183
184 if ( $this->dryRun ) {
185 if ( $oldTitle->exists() ) {
186 $this->output( "Would move [[$nsName:$oldName]] to [[$nsName:$newName]].\n" );
187 }
188 } else {
189 if ( $oldTitle->exists() ) {
190 $status = $movePage->move(
191 $this->performer, $logMessage, !$this->suppressRedirect );
192 } else {
193 $status = Status::newGood();
194 }
195 $status->merge( $movePage->moveSubpages(
196 $this->performer, $logMessage, !$this->suppressRedirect ) );
197 if ( !$status->isGood() ) {
198 $this->output( "Failed to rename user page: " .
199 $status->getWikiText( false, false, 'en' ) .
200 "\n" );
201 }
202 }
203 return true;
204 }
205}
206
207$maintClass = RenameUsersMatchingPattern::class;
208require_once RUN_MAINTENANCE_IF_MAIN;
getDB()
const NS_USER
Definition Defines.php:66
const NS_USER_TALK
Definition Defines.php:67
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
error( $err, $die=0)
Throw an error to the user.
output( $out, $channel=null)
Throw some output to the user.
waitForReplication()
Wait for replica DBs to catch up.
getBatchSize()
Returns batch size.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
setBatchSize( $s=0)
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
Service locator for MediaWiki core services.
Class which performs the actual renaming of users.
Creates Title objects.
Helper for TempUserConfig representing string patterns with "$1" indicating variable substitution.
Definition Pattern.php:13
Creates User objects.
static newGood( $value=null)
Factory function for good results.
internal since 1.36
Definition User.php:71
static newFromName( $name, $validate='valid')
Definition User.php:592
static newSystemUser( $name, $options=[])
Static factory method for creation of a "system" user from username.
Definition User.php:793
const MAINTENANCE_SCRIPT_USER
Username used for various maintenance scripts.
Definition User.php:117
Service for page rename actions.
const DB_REPLICA
Definition defines.php:26