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