Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 107 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ForceRenameUsers | |
0.00% |
0 / 101 |
|
0.00% |
0 / 6 |
342 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
log | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
getCurrentRenameCount | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
rename | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
30 | |||
findUsers | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | |
3 | use MediaWiki\Extension\CentralAuth\CentralAuthServices; |
4 | use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameUserStatus; |
5 | use MediaWiki\Extension\CentralAuth\GlobalRename\LocalRenameJob\LocalRenameUserJob; |
6 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
7 | use MediaWiki\Extension\CentralAuth\UsersToRename\UsersToRenameDatabaseUpdates; |
8 | use MediaWiki\Logger\LoggerFactory; |
9 | use MediaWiki\MediaWikiServices; |
10 | use MediaWiki\Title\Title; |
11 | use MediaWiki\User\User; |
12 | use MediaWiki\User\UserNameUtils; |
13 | use MediaWiki\WikiMap\WikiMap; |
14 | use Wikimedia\Rdbms\IDatabase; |
15 | |
16 | $IP = getenv( 'MW_INSTALL_PATH' ); |
17 | if ( $IP === false ) { |
18 | $IP = __DIR__ . '/../../..'; |
19 | } |
20 | require_once "$IP/maintenance/Maintenance.php"; |
21 | |
22 | /** |
23 | * Starts the process of migrating users who have |
24 | * unattached accounts to their new names |
25 | * with globalized accounts. |
26 | * |
27 | * This script should be run on each wiki individually. |
28 | * |
29 | * Requires populateUsersToRename.php to be run first |
30 | */ |
31 | class ForceRenameUsers extends Maintenance { |
32 | |
33 | /** @var \Psr\Log\LoggerInterface */ |
34 | private $logger; |
35 | |
36 | public function __construct() { |
37 | parent::__construct(); |
38 | $this->requireExtension( 'CentralAuth' ); |
39 | $this->addDescription( 'Forcibly renames and migrates unattached accounts to global ones' ); |
40 | $this->addOption( 'reason', 'Reason to use for log summaries', true, true ); |
41 | $this->setBatchSize( 10 ); |
42 | $this->logger = LoggerFactory::getInstance( 'CentralAuth' ); |
43 | } |
44 | |
45 | /** |
46 | * @param string $msg |
47 | */ |
48 | private function log( $msg ) { |
49 | $this->logger->debug( "ForceRenameUsers: $msg" ); |
50 | $this->output( $msg . "\n" ); |
51 | } |
52 | |
53 | public function execute() { |
54 | $dbw = CentralAuthServices::getDatabaseManager()->getCentralPrimaryDB(); |
55 | while ( true ) { |
56 | $rowsToRename = $this->findUsers( WikiMap::getCurrentWikiId(), $dbw ); |
57 | if ( !$rowsToRename ) { |
58 | break; |
59 | } |
60 | |
61 | foreach ( $rowsToRename as $row ) { |
62 | $this->rename( $row, $dbw ); |
63 | } |
64 | CentralAuthServices::getDatabaseManager()->waitForReplication(); |
65 | $count = $this->getCurrentRenameCount( $dbw ); |
66 | while ( $count > 50 ) { |
67 | $this->output( "There are currently $count renames queued, pausing...\n" ); |
68 | sleep( 5 ); |
69 | $count = $this->getCurrentRenameCount( $dbw ); |
70 | } |
71 | } |
72 | } |
73 | |
74 | /** |
75 | * @param IDatabase $dbw |
76 | * |
77 | * @return int |
78 | */ |
79 | protected function getCurrentRenameCount( IDatabase $dbw ) { |
80 | $row = $dbw->newSelectQueryBuilder() |
81 | ->select( 'COUNT(*) as count' ) |
82 | ->from( 'renameuser_status' ) |
83 | ->caller( __METHOD__ ) |
84 | ->fetchRow(); |
85 | return (int)$row->count; |
86 | } |
87 | |
88 | /** |
89 | * @param stdClass $row |
90 | * @param IDatabase $dbw |
91 | */ |
92 | protected function rename( $row, IDatabase $dbw ) { |
93 | $wiki = $row->utr_wiki; |
94 | $name = $row->utr_name; |
95 | $services = MediaWikiServices::getInstance(); |
96 | $userNameUtils = $services->getUserNameUtils(); |
97 | $newNamePrefix = $userNameUtils->getCanonical( |
98 | // Some database names have _'s in them, replace with dashes - |
99 | $name . '~' . str_replace( '_', '-', $wiki ), |
100 | UserNameUtils::RIGOR_USABLE |
101 | ); |
102 | if ( !$newNamePrefix ) { |
103 | $this->log( "ERROR: New name '$name~$wiki' is not valid" ); |
104 | return; |
105 | } |
106 | $this->log( "Beginning rename of $newNamePrefix" ); |
107 | $newCAUser = new CentralAuthUser( $newNamePrefix, IDBAccessObject::READ_LATEST ); |
108 | $count = 0; |
109 | // Edge case: Someone created User:Foo~wiki manually. |
110 | // So just start appending numbers to the end of the name |
111 | // until we get one that isn't used. |
112 | while ( $newCAUser->exists() ) { |
113 | $count++; |
114 | $newCAUser = new CentralAuthUser( |
115 | $newNamePrefix . (string)$count, |
116 | IDBAccessObject::READ_LATEST |
117 | ); |
118 | } |
119 | if ( $newNamePrefix !== $newCAUser->getName() ) { |
120 | $this->log( "WARNING: New name is now {$newCAUser->getName()}" ); |
121 | } |
122 | $this->log( "Renaming $name to {$newCAUser->getName()}." ); |
123 | |
124 | $statuses = new GlobalRenameUserStatus( $name ); |
125 | $success = $statuses->setStatuses( [ [ |
126 | 'ru_wiki' => $wiki, |
127 | 'ru_oldname' => $name, |
128 | 'ru_newname' => $newCAUser->getName(), |
129 | 'ru_status' => 'queued' |
130 | ] ] ); |
131 | |
132 | if ( !$success ) { |
133 | $this->log( "WARNING: Race condition, renameuser_status already set for " . |
134 | "{$newCAUser->getName()}. Skipping." ); |
135 | return; |
136 | } |
137 | |
138 | $this->log( "Set renameuser_status for {$newCAUser->getName()}." ); |
139 | |
140 | $job = new LocalRenameUserJob( |
141 | Title::newFromText( 'Global rename job' ), |
142 | [ |
143 | 'from' => $name, |
144 | 'to' => $newCAUser->getName(), |
145 | 'renamer' => User::MAINTENANCE_SCRIPT_USER, |
146 | 'movepages' => true, |
147 | 'suppressredirects' => true, |
148 | 'promotetoglobal' => true, |
149 | 'reason' => $this->getOption( 'reason' ), |
150 | ] |
151 | ); |
152 | |
153 | $services->getJobQueueGroupFactory()->makeJobQueueGroup( $row->utr_wiki )->push( $job ); |
154 | $this->log( "Submitted job for {$newCAUser->getName()}." ); |
155 | $updates = new UsersToRenameDatabaseUpdates( $dbw ); |
156 | $updates->markRenamed( $row->utr_name, $row->utr_wiki ); |
157 | } |
158 | |
159 | /** |
160 | * @param string $wiki |
161 | * @param IDatabase $dbw |
162 | * @return stdClass[] |
163 | */ |
164 | protected function findUsers( $wiki, IDatabase $dbw ) { |
165 | $rowsToRename = []; |
166 | $updates = new UsersToRenameDatabaseUpdates( $dbw ); |
167 | $rows = $updates->findUsers( |
168 | $wiki, UsersToRenameDatabaseUpdates::NOTIFIED, $this->mBatchSize |
169 | ); |
170 | $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils(); |
171 | |
172 | foreach ( $rows as $row ) { |
173 | $user = User::newFromName( $row->utr_name ); |
174 | $caUser = new CentralAuthUser( $row->utr_name, IDBAccessObject::READ_LATEST ); |
175 | |
176 | if ( !$user->getId() ) { |
177 | $this->log( |
178 | "'{$row->utr_name}' has been renamed since the last was list generated." |
179 | ); |
180 | $updates->remove( $row->utr_name, $row->utr_wiki ); |
181 | } elseif ( $caUser->attachedOn( $row->utr_wiki ) ) { |
182 | $this->log( "'{$row->utr_name}' has become attached to a global account since " . |
183 | "the list as last generated." ); |
184 | $updates->remove( $row->utr_name, $row->utr_wiki ); |
185 | } elseif ( !$userNameUtils->isUsable( $row->utr_name ) ) { |
186 | // Reserved for a system account, ignore |
187 | $this->log( "'{$row->utr_name}' is a reserved username, skipping." ); |
188 | $updates->remove( $row->utr_name, $row->utr_wiki ); |
189 | } else { |
190 | $rowsToRename[] = $row; |
191 | } |
192 | } |
193 | |
194 | return $rowsToRename; |
195 | } |
196 | } |
197 | |
198 | $maintClass = ForceRenameUsers::class; |
199 | require_once RUN_MAINTENANCE_IF_MAIN; |