wiki.techinc.nl/maintenance/renameUsersMatchingPattern.php
Amir Sarabadani f4e68e055f Reorg: Move Status to MediaWiki\Status\
This class is used heavily basically everywhere, moving it to Utils
wouldn't make much sense. Also with this change, we can move
StatusValue to MediaWiki\Status as well.

Bug: T321882
Depends-On: I5f89ecf27ce1471a74f31c6018806461781213c3
Change-Id: I04c1dcf5129df437589149f0f3e284974d7c98fa
2023-08-25 15:44:17 +02:00

227 lines
6.8 KiB
PHP

<?php
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\RenameUser\RenameuserSQL;
use MediaWiki\Status\Status;
use MediaWiki\Title\TitleFactory;
use MediaWiki\User\TempUser\Pattern;
use MediaWiki\User\UserFactory;
require_once __DIR__ . '/Maintenance.php';
class RenameUsersMatchingPattern extends Maintenance {
/** @var UserFactory */
private $userFactory;
/** @var MovePageFactory */
private $movePageFactory;
/** @var TitleFactory */
private $titleFactory;
/** @var User */
private $performer;
/** @var string */
private $reason;
/** @var bool */
private $dryRun;
/** @var bool */
private $suppressRedirect;
/** @var bool */
private $skipPageMoves;
public function __construct() {
parent::__construct();
$this->addDescription( 'Rename users with a name matching a pattern. ' .
'This can be used to migrate to a temporary user (IP masking) configuration.' );
$this->addOption( 'from', 'A username pattern where $1 is ' .
'the wildcard standing in for any number of characters. All users ' .
'matching this pattern will be renamed.', true, true );
$this->addOption( 'to', 'A username pattern where $1 is ' .
'the part of the username matched by $1 in --from. Users will be ' .
' renamed to this pattern.', true, true );
$this->addOption( 'performer', 'Performer of the rename action', false, true );
$this->addOption( 'reason', 'Reason of the rename', false, true );
$this->addOption( 'suppress-redirect', 'Don\'t create redirects when moving pages' );
$this->addOption( 'skip-page-moves', 'Don\'t move associated user pages' );
$this->addOption( 'dry-run', 'Don\'t actually rename the ' .
'users, just report what it would do.' );
$this->setBatchSize( 1000 );
}
private function initServices() {
$services = MediaWikiServices::getInstance();
if ( $services->getCentralIdLookupFactory()->getNonLocalLookup() ) {
$this->fatalError( "This script cannot be run when CentralAuth is enabled." );
}
$this->userFactory = $services->getUserFactory();
$this->movePageFactory = $services->getMovePageFactory();
$this->titleFactory = $services->getTitleFactory();
}
public function execute() {
$this->initServices();
$fromPattern = new Pattern( 'from', $this->getOption( 'from' ) );
$toPattern = new Pattern( 'to', $this->getOption( 'to' ) );
if ( $this->getOption( 'performer' ) === null ) {
$performer = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] );
} else {
$performer = $this->userFactory->newFromName( $this->getOption( 'performer' ) );
}
if ( !$performer ) {
$this->error( "Unable to get performer account" );
return false;
}
$this->performer = $performer;
$this->reason = $this->getOption( 'reason', '' );
$this->dryRun = $this->getOption( 'dry-run' );
$this->suppressRedirect = $this->getOption( 'suppress-redirect' );
$this->skipPageMoves = $this->getOption( 'skip-page-moves' );
$dbr = $this->getDB( DB_REPLICA );
$batchConds = [];
$batchSize = $this->getBatchSize();
$numRenamed = 0;
do {
$res = $dbr->newSelectQueryBuilder()
->select( [ 'user_name' ] )
->from( 'user' )
->where( array_merge(
[ 'user_name' . $fromPattern->buildLike( $dbr ) ],
$batchConds
) )
->orderBy( 'user_name' )
->limit( $batchSize )
->caller( __METHOD__ )
->fetchResultSet();
foreach ( $res as $row ) {
$oldName = $row->user_name;
$batchConds = [ 'user_name > ' . $dbr->addQuotes( $oldName ) ];
$variablePart = $fromPattern->extract( $oldName );
if ( $variablePart === null ) {
$this->output( "Username \"fromName\" matched the LIKE " .
"but does not seem to match the pattern" );
continue;
}
$newName = $toPattern->generate( $variablePart );
// Canonicalize
$newTitle = $this->titleFactory->makeTitleSafe( NS_USER, $newName );
$newUser = $this->userFactory->newFromName( $newName );
if ( !$newTitle || !$newUser ) {
$this->output( "Cannot rename \"$oldName\" " .
"because \"$newName\" is not a valid title\n" );
continue;
}
$newName = $newTitle->getText();
// Check destination existence
if ( $newUser->isRegistered() ) {
$this->output( "Cannot rename \"$oldName\" " .
"because \"$newName\" already exists\n" );
continue;
}
$numRenamed += $this->renameUser( $oldName, $newName ) ? 1 : 0;
$this->waitForReplication();
}
} while ( $res->numRows() === $batchSize );
$this->output( "Renamed $numRenamed user(s)\n" );
return true;
}
/**
* @param string $oldName
* @param string $newName
* @return bool True if the user was renamed
*/
private function renameUser( $oldName, $newName ) {
$id = $this->userFactory->newFromName( $oldName )->getId();
if ( !$id ) {
$this->output( "Cannot rename non-existent user \"$oldName\"" );
}
if ( $this->dryRun ) {
$this->output( "$oldName would be renamed to $newName\n" );
} else {
$renamer = new RenameuserSQL(
$oldName,
$newName,
$id,
$this->performer,
[
'reason' => $this->reason
]
);
if ( !$renamer->rename() ) {
$this->output( "Unable to rename $oldName" );
return false;
} else {
$this->output( "$oldName was successfully renamed to $newName.\n" );
}
}
if ( $this->skipPageMoves ) {
return true;
}
$this->movePageAndSubpages( NS_USER, 'User', $oldName, $newName );
$this->movePageAndSubpages( NS_USER_TALK, 'User talk', $oldName, $newName );
return true;
}
private function movePageAndSubpages( $ns, $nsName, $oldName, $newName ) {
$oldTitle = $this->titleFactory->makeTitleSafe( $ns, $oldName );
if ( !$oldTitle ) {
$this->output( "[[$nsName:$oldName]] is an invalid title, can't move it.\n" );
return true;
}
$newTitle = $this->titleFactory->makeTitleSafe( $ns, $newName );
if ( !$newTitle ) {
$this->output( "[[$nsName:$newName]] is an invalid title, can't move to it.\n" );
return true;
}
$movePage = $this->movePageFactory->newMovePage( $oldTitle, $newTitle );
$movePage->setMaximumMovedPages( -1 );
$logMessage = wfMessage(
'renameuser-move-log', $oldName, $newName
)->inContentLanguage()->text();
if ( $this->dryRun ) {
if ( $oldTitle->exists() ) {
$this->output( "Would move [[$nsName:$oldName]] to [[$nsName:$newName]].\n" );
}
} else {
if ( $oldTitle->exists() ) {
$status = $movePage->move(
$this->performer, $logMessage, !$this->suppressRedirect );
} else {
$status = Status::newGood();
}
$status->merge( $movePage->moveSubpages(
$this->performer, $logMessage, !$this->suppressRedirect ) );
if ( !$status->isGood() ) {
$this->output( "Failed to rename user page: " .
$status->getWikiText( false, false, 'en' ) .
"\n" );
}
}
return true;
}
}
$maintClass = RenameUsersMatchingPattern::class;
require_once RUN_MAINTENANCE_IF_MAIN;