Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
MigrateCentralWikiLogs
0.00% covered (danger)
0.00%
0 / 82
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 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 1
110
1<?php
2/**
3 * This script should be run as part of migrating to a new central OAuth wiki in your
4 * cluster. See the notes in migrateCentralWikiLogs.php for the complete process.
5 * This script is intended to be run on the new central wiki after the tables have already
6 * been migrated. This will fill in the logs from newest to oldest, and tries to do sane
7 * things if you need to stop it and restart it later.
8 *
9 * @ingroup Maintenance
10 */
11// @codeCoverageIgnoreStart
12if ( getenv( 'MW_INSTALL_PATH' ) ) {
13    $IP = getenv( 'MW_INSTALL_PATH' );
14} else {
15    $IP = __DIR__ . '/../../..';
16}
17
18require_once "$IP/maintenance/Maintenance.php";
19// @codeCoverageIgnoreEnd
20
21use MediaWiki\Logging\LogEntryBase;
22use MediaWiki\Logging\ManualLogEntry;
23use MediaWiki\Maintenance\Maintenance;
24use MediaWiki\MediaWikiServices;
25use MediaWiki\Title\Title;
26use MediaWiki\WikiMap\WikiMap;
27use Wikimedia\Rdbms\SelectQueryBuilder;
28
29class MigrateCentralWikiLogs extends Maintenance {
30    public function __construct() {
31        parent::__construct();
32        $this->addDescription( "Import central wiki logs to this wiki" );
33        $this->addOption( 'old', 'Previous central wiki', true, true );
34        $this->setBatchSize( 200 );
35        $this->requireExtension( "OAuth" );
36    }
37
38    public function execute() {
39        $oldWiki = $this->getOption( 'old' );
40        $targetWiki = WikiMap::getCurrentWikiId();
41
42        $this->output( "Moving OAuth logs from '$oldWiki' to '$targetWiki'\n" );
43
44        // We only read from $oldDb, but we do want to make sure we get the most recent logs.
45        $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
46        $oldDb = $lbFactory->getMainLB( $oldWiki )->getConnection( DB_PRIMARY, [], $oldWiki );
47        $targetDb = $lbFactory->getMainLB( $targetWiki )
48            ->getConnection( DB_PRIMARY, [], $targetWiki );
49
50        $targetMinTS = $targetDb->newSelectQueryBuilder()
51            ->select( 'MIN(log_timestamp)' )
52            ->from( 'logging' )
53            ->where( [ 'log_type' => 'mwoauthconsumer' ] )
54            ->caller( __METHOD__ )
55            ->fetchField();
56
57        $lastMinTimestamp = null;
58        if ( $targetMinTS !== false ) {
59            $lastMinTimestamp = $targetMinTS;
60        }
61
62        $commentStore = MediaWikiServices::getInstance()->getCommentStore();
63        $commentQuery = $commentStore->getJoin( 'log_comment' );
64
65        do {
66            $conds = [ 'log_type' => 'mwoauthconsumer' ];
67
68            // This assumes that we don't have more than mBatchSize oauth log entries
69            // with the same timestamp. Otherwise this will go into an infinite loop.
70            if ( $lastMinTimestamp !== null ) {
71                $conds[] = $oldDb->expr( 'log_timestamp', '<', $oldDb->timestamp( $lastMinTimestamp ) );
72            }
73
74            $oldLoggs = $oldDb->newSelectQueryBuilder()
75                ->select( [
76                    'log_id', 'log_action', 'log_timestamp', 'log_params', 'log_deleted',
77                    'actor_id', 'actor_name', 'actor_user'
78                ] )
79                ->from( 'logging' )
80                ->join( 'actor', null, 'actor_id=log_actor' )
81                ->where( $conds )
82                ->queryInfo( $commentQuery )
83                ->orderBy( 'log_timestamp', SelectQueryBuilder::SORT_DESC )
84                ->limit( $this->mBatchSize + 1 )
85                ->caller( __METHOD__ )
86                ->fetchResultSet();
87
88            $rowCount = $oldLoggs->numRows();
89
90            if ( $rowCount == $this->mBatchSize + 1 ) {
91                $first = $oldLoggs->fetchObject();
92                $oldLoggs->seek( $rowCount - 2 );
93                $last = $oldLoggs->fetchObject();
94                if ( $first->log_timestamp === $last->log_timestamp ) {
95                    $this->fatalError( "Batch size too low to avoid infinite loop.\n" );
96                }
97                $extra = $oldLoggs->fetchObject();
98                if ( $last->log_timestamp === $extra->log_timestamp ) {
99                    $this->fatalError( "We hit an edge case. Please increase the batch " .
100                        " size and restart the transfer.\n" );
101                }
102                $oldLoggs->rewind();
103            }
104
105            $targetDb->begin( __METHOD__ );
106            foreach ( $oldLoggs as $key => $row ) {
107                // Skip if this is the extra row we selected
108                if ( $key > $this->mBatchSize ) {
109                    continue;
110                }
111
112                $lastMinTimestamp = $row->log_timestamp;
113
114                $this->output( "Migrating log {$row->log_id}...\n" );
115                if ( !$row->actor_user ) {
116                    $this->output(
117                        "Cannot transfer log_id: {$row->log_id}, the log user doesn't exist"
118                    );
119                    continue;
120                }
121                $logUser = MediaWikiServices::getInstance()->getActorNormalization()
122                    ->newActorFromRow( $row );
123                $params = LogEntryBase::extractParams( $row->log_params );
124                if ( !isset( $params['4:consumer'] ) ) {
125                    $this->output( "Cannot transfer log_id: {$row->log_id}, param isn't correct" );
126                    continue;
127                }
128                $logEntry = new ManualLogEntry( 'mwoauthconsumer', $row->log_action );
129                $logEntry->setPerformer( $logUser );
130                $logEntry->setTarget( Title::makeTitleSafe( NS_USER, $row->actor_name ) );
131                $logEntry->setComment( $commentStore->getComment( 'log_comment', $row )->text );
132                $logEntry->setParameters( $params );
133                $logEntry->setRelations( [
134                    'OAuthConsumer' => [ $params['4:consumer'] ]
135                ] );
136                // ManualLogEntry::insert() calls $dbw->timestamp on the value
137                $logEntry->setTimestamp( $row->log_timestamp );
138                // @TODO: Maybe this will do something some day. Sigh.
139                $logEntry->setDeleted( $row->log_deleted );
140                $logEntry->insert( $targetDb );
141            }
142            $targetDb->commit( __METHOD__ );
143
144            $this->waitForReplication();
145
146        } while ( $rowCount );
147    }
148
149}
150
151// @codeCoverageIgnoreStart
152$maintClass = MigrateCentralWikiLogs::class;
153require_once RUN_MAINTENANCE_IF_MAIN;
154// @codeCoverageIgnoreEnd