Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.57% covered (warning)
88.57%
124 / 140
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
PopulateCheckUserTable
92.54% covered (success)
92.54%
124 / 134
66.67% covered (warning)
66.67%
2 / 3
19.15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getUpdateKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 doDBUpdates
92.19% covered (success)
92.19%
118 / 128
0.00% covered (danger)
0.00%
0 / 1
17.14
1<?php
2
3namespace MediaWiki\CheckUser\Maintenance;
4
5use DatabaseLogEntry;
6use LoggedUpdateMaintenance;
7use MediaWiki\MediaWikiServices;
8use RecentChange;
9use Wikimedia\IPUtils;
10
11$IP = getenv( 'MW_INSTALL_PATH' );
12if ( $IP === false ) {
13    $IP = __DIR__ . '/../../..';
14}
15require_once "$IP/maintenance/Maintenance.php";
16
17/**
18 * Populate the cu_changes table needed for CheckUser queries with
19 * data from recent changes.
20 * This is automatically run during first installation within update.php
21 * but --force parameter should be set if you want to manually run thereafter.
22 */
23class PopulateCheckUserTable extends LoggedUpdateMaintenance {
24    public function __construct() {
25        parent::__construct();
26        $this->addDescription( 'Populate `cu_changes` table with entries from recentchanges' );
27        $this->addOption( 'cutoff', 'Cut-off time for rc_timestamp' );
28        $this->setBatchSize( 100 );
29
30        $this->requireExtension( 'CheckUser' );
31    }
32
33    /**
34     * @inheritDoc
35     */
36    protected function getUpdateKey() {
37        return __CLASS__;
38    }
39
40    /**
41     * @inheritDoc
42     */
43    protected function doDBUpdates() {
44        $db = $this->getPrimaryDB();
45
46        // Check if the table is empty
47        $rcRows = $db->newSelectQueryBuilder()
48            ->table( 'recentchanges' )
49            ->caller( __METHOD__ )
50            ->fetchRowCount();
51        if ( !$rcRows ) {
52            $this->output( "recentchanges is empty; nothing to add.\n" );
53            return true;
54        }
55
56        $cutoff = $this->getOption( 'cutoff' );
57        if ( $cutoff ) {
58            // Something leftover... clear old entries to minimize dupes
59            $cutoff = $db->timestamp( $cutoff );
60            $db->newDeleteQueryBuilder()
61                ->deleteFrom( 'cu_changes' )
62                ->where( $db->expr( 'cuc_timestamp', '<', $cutoff ) )
63                ->caller( __METHOD__ )
64                ->execute();
65            $cutoffCond = $db->expr( 'rc_timestamp', '<', $cutoff );
66        } else {
67            $cutoffCond = null;
68        }
69
70        $start = (int)$db->newSelectQueryBuilder()
71            ->field( 'MIN(rc_id)' )
72            ->table( 'recentchanges' )
73            ->caller( __METHOD__ )
74            ->fetchField();
75        $end = (int)$db->newSelectQueryBuilder()
76            ->field( 'MAX(rc_id)' )
77            ->table( 'recentchanges' )
78            ->caller( __METHOD__ )
79            ->fetchField();
80        // Do remaining chunk
81        $end += $this->mBatchSize - 1;
82        $blockStart = $start;
83        $blockEnd = $start + $this->mBatchSize - 1;
84
85        $this->output(
86            "Starting population of cu_changes with recentchanges rc_id from $start to $end.\n"
87        );
88
89        $services = MediaWikiServices::getInstance();
90        $commentStore = $services->getCommentStore();
91        $rcQuery = RecentChange::getQueryInfo();
92
93        while ( $blockStart <= $end ) {
94            $this->output( "...migrating rc_id from $blockStart to $blockEnd\n" );
95            $queryBuilder = $db->newSelectQueryBuilder()
96                ->fields( $rcQuery['fields'] )
97                ->tables( $rcQuery['tables'] )
98                ->joinConds( $rcQuery['joins'] )
99                ->conds( [
100                    $db->expr( 'rc_id', '>=', $blockStart ),
101                    $db->expr( 'rc_id', '<=', $blockEnd ),
102                ] )
103                ->caller( __METHOD__ );
104            if ( $cutoffCond ) {
105                $queryBuilder->andWhere( $cutoffCond );
106            }
107            $res = $queryBuilder->fetchResultSet();
108            $cuChangesBatch = [];
109            $cuPrivateEventBatch = [];
110            $cuLogEventBatch = [];
111            foreach ( $res as $row ) {
112                $eventTablesMigrationStage = $services->getMainConfig()
113                    ->get( 'CheckUserEventTablesMigrationStage' );
114                $comment = $commentStore->getComment( 'rc_comment', $row );
115                if (
116                    $row->rc_type == RC_LOG &&
117                    ( $eventTablesMigrationStage & SCHEMA_COMPAT_WRITE_NEW )
118                ) {
119                    $logEntry = null;
120                    if ( $row->rc_logid != 0 ) {
121                        $logEntry = DatabaseLogEntry::newFromId( $row->rc_logid, $db );
122                    }
123                    if ( $logEntry === null ) {
124                        $cuPrivateEventBatch[] = [
125                            'cupe_timestamp' => $row->rc_timestamp,
126                            'cupe_actor' => $row->rc_actor,
127                            'cupe_namespace' => $row->rc_namespace,
128                            'cupe_title' => $row->rc_title,
129                            'cupe_comment_id' => $comment->id,
130                            'cupe_page' => $row->rc_cur_id,
131                            'cupe_log_action' => $row->rc_log_action,
132                            'cupe_log_type' => $row->rc_log_type,
133                            'cupe_params' => $row->rc_params,
134                            'cupe_ip' => $row->rc_ip,
135                            'cupe_ip_hex' => IPUtils::toHex( $row->rc_ip ),
136                        ];
137                    } else {
138                        $cuLogEventBatch[] = [
139                            'cule_timestamp' => $row->rc_timestamp,
140                            'cule_actor' => $row->rc_actor,
141                            'cule_log_id' => $row->rc_logid,
142                            'cule_ip' => $row->rc_ip,
143                            'cule_ip_hex' => IPUtils::toHex( $row->rc_ip ),
144                        ];
145                    }
146                }
147                if (
148                    $row->rc_type != RC_LOG ||
149                    ( $eventTablesMigrationStage & SCHEMA_COMPAT_WRITE_OLD )
150                ) {
151                    $cuChangesRow = [
152                        'cuc_timestamp' => $row->rc_timestamp,
153                        'cuc_namespace' => $row->rc_namespace,
154                        'cuc_title' => $row->rc_title,
155                        'cuc_actor' => $row->rc_actor,
156                        'cuc_comment_id' => $comment->id,
157                        'cuc_minor' => $row->rc_minor,
158                        'cuc_page_id' => $row->rc_cur_id,
159                        'cuc_this_oldid' => $row->rc_this_oldid,
160                        'cuc_last_oldid' => $row->rc_last_oldid,
161                        'cuc_type' => $row->rc_type,
162                        'cuc_ip' => $row->rc_ip,
163                        'cuc_ip_hex' => IPUtils::toHex( $row->rc_ip ),
164                        'cuc_only_for_read_old' => 0,
165                    ];
166                    if (
167                        $row->rc_type == RC_LOG &&
168                        ( $eventTablesMigrationStage & SCHEMA_COMPAT_WRITE_NEW )
169                    ) {
170                        $cuChangesRow['cuc_only_for_read_old'] = 1;
171                    }
172                    $cuChangesBatch[] = $cuChangesRow;
173                }
174            }
175            if ( count( $cuChangesBatch ) ) {
176                $db->newInsertQueryBuilder()
177                    ->insertInto( 'cu_changes' )
178                    ->rows( $cuChangesBatch )
179                    ->caller( __METHOD__ )
180                    ->execute();
181            }
182            if ( count( $cuPrivateEventBatch ) ) {
183                $db->newInsertQueryBuilder()
184                    ->insertInto( 'cu_private_event' )
185                    ->rows( $cuPrivateEventBatch )
186                    ->caller( __METHOD__ )
187                    ->execute();
188            }
189            if ( count( $cuLogEventBatch ) ) {
190                $db->newInsertQueryBuilder()
191                    ->insertInto( 'cu_log_event' )
192                    ->rows( $cuLogEventBatch )
193                    ->caller( __METHOD__ )
194                    ->execute();
195            }
196            $blockStart += $this->mBatchSize - 1;
197            $blockEnd += $this->mBatchSize - 1;
198            $this->waitForReplication();
199        }
200
201        $this->output( "...cu_changes table has been populated.\n" );
202        return true;
203    }
204}
205
206$maintClass = PopulateCheckUserTable::class;
207require_once RUN_MAINTENANCE_IF_MAIN;