Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 80
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
FixStuckGlobalRename
0.00% covered (danger)
0.00%
0 / 74
0.00% covered (danger)
0.00%
0 / 2
132
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 1
110
1<?php
2
3use MediaWiki\Extension\CentralAuth\CentralAuthServices;
4use MediaWiki\Extension\CentralAuth\GlobalRename\LocalRenameJob\LocalRenameUserJob;
5use MediaWiki\Extension\CentralAuth\User\CentralAuthUser;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\Title\Title;
8use MediaWiki\User\User;
9use MediaWiki\WikiMap\WikiMap;
10use Wikimedia\Rdbms\SelectQueryBuilder;
11
12$IP = getenv( 'MW_INSTALL_PATH' );
13if ( $IP === false ) {
14    $IP = __DIR__ . '/../../..';
15}
16require_once "$IP/maintenance/Maintenance.php";
17
18/**
19 * Script to run global renames that are stuck in the status table with "queued" or "in progress"
20 * but failed for whatever reason
21 */
22class FixStuckGlobalRename extends Maintenance {
23    public function __construct() {
24        parent::__construct();
25        $this->requireExtension( 'CentralAuth' );
26        $this->addArg( 'oldname', 'Old name' );
27        $this->addArg( 'newname', 'New name' );
28        $this->addOption( 'logwiki', 'Wiki where the log entry exists', true, true );
29        $this->addOption( 'ignorestatus', 'Ignore rename status. Don\'t do this when the rename '
30            . 'jobs might still be running.' );
31        $this->addDescription( 'Unstuck global rename on a single wiki' );
32    }
33
34    public function execute() {
35        global $wgLocalDatabases;
36        $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils();
37        $databaseManager = CentralAuthServices::getDatabaseManager();
38
39        $oldName = $userNameUtils->getCanonical( $this->getArg( 0 ) );
40        $newName = $userNameUtils->getCanonical( $this->getArg( 1 ) );
41        if ( $oldName === false || $newName === false ) {
42            $this->fatalError( 'Invalid name' );
43        }
44
45        $logTitle = Title::newFromText( 'Special:CentralAuth' )->getSubpage( $newName );
46        $ca = new CentralAuthUser( $newName );
47        if ( !$ca->renameInProgressOn( WikiMap::getCurrentWikiId() ) ) {
48            $this->fatalError( "{$ca->getName()} does not have a rename in progress on this wiki." );
49        }
50
51        $dbr = $databaseManager->getLocalDB( DB_REPLICA, $this->getOption( 'logwiki' ) );
52        $queryData = DatabaseLogEntry::getSelectQueryData();
53        $row = $dbr->newSelectQueryBuilder()
54            ->tables( $queryData['tables'] )
55            ->select( $queryData['fields'] )
56            ->where( $queryData['conds'] )
57            ->andWhere( [
58                'log_type' => 'gblrename',
59                'log_action' => 'rename',
60                'log_namespace' => NS_SPECIAL,
61                'log_title' => $logTitle->getDBkey(),
62            ] )
63            ->options( $queryData['options'] )
64            ->orderBy( 'log_timestamp', SelectQueryBuilder::SORT_DESC )
65            ->joinConds( $queryData['join_conds'] )
66            ->caller( __METHOD__ )
67            ->fetchRow();
68
69        // try to guess the options if the log record does not contain them
70        $movepages = true;
71        $suppressredirects = false;
72        if ( $row ) {
73            $logEntry = DatabaseLogEntry::newFromRow( $row );
74            $renamer = $logEntry->getPerformerIdentity()->getName();
75            $comment = $logEntry->getComment();
76            $logParams = $logEntry->getParameters();
77            if ( isset( $logParams['movepages'] ) ) {
78                $movepages = $logParams['movepages'];
79            }
80            if ( isset( $logParams['suppressredirects'] ) ) {
81                $suppressredirects = $logParams['suppressredirects'];
82            }
83            $this->output( "Using $renamer as the renamer.\n" );
84        } else {
85            $this->output( "Could not find log entry, falling back to system account\n" );
86            // Go through User::newSystemUser() to ensure the user exists (T344632).
87            // The username is reserved in onUserGetReservedNames() hook.
88            $renamer = User::newSystemUser( 'Global rename script', [ 'steal' => true ] )->getName();
89            $comment = '';
90        }
91
92        $params = [
93            'from' => $oldName,
94            'to' => $newName,
95            'renamer' => $renamer,
96            'movepages' => $movepages,
97            'suppressredirects' => $suppressredirects,
98            'reason' => $comment,
99            'ignorestatus' => $this->getOption( 'ignorestatus', false ),
100            // No way to recover localuser attachment details, faking it.
101            // $wgLocalDatabases will contain wikis where the user does not have an account.
102            // That's fine, LocalRenameUserJob will only use the one that matches WikiMap::getCurrentWikiId().
103            // FIXME the real attachment info should be saved & restored instead, see T215107
104            'reattach' => array_fill_keys( $wgLocalDatabases, [
105                'attachedMethod' => 'admin',
106                'attachedTimestamp' => wfTimestamp( TS_MW ),
107            ] ),
108        ];
109        foreach ( $params as $key => $value ) {
110            if ( $key === 'reattach' ) {
111                continue;
112            }
113            $this->output( "$key$value\n" );
114        }
115        $title = Title::newFromText( 'Global rename job' );
116        $job = new LocalRenameUserJob( $title, $params );
117        $this->output( "\nStarting to run job...\n" );
118        $status = $job->run();
119        $job->teardown( $status );
120        $this->output( $status ? "Done!\n" : "Failed!\n" );
121    }
122}
123
124$maintClass = FixStuckGlobalRename::class;
125require_once RUN_MAINTENANCE_IF_MAIN;