Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
FixAutoblockLogTitles
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
3 / 3
6
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 doDBUpdates
100.00% covered (success)
100.00%
49 / 49
100.00% covered (success)
100.00%
1 / 1
4
 getUpdateKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Maintenance;
4
5use Wikimedia\Rdbms\IExpression;
6use Wikimedia\Rdbms\LikeValue;
7use Wikimedia\Rdbms\SelectQueryBuilder;
8
9// @codeCoverageIgnoreStart
10require_once __DIR__ . '/Maintenance.php';
11// @codeCoverageIgnoreEnd
12
13class FixAutoblockLogTitles extends LoggedUpdateMaintenance {
14
15    public function __construct() {
16        parent::__construct();
17        $this->addDescription(
18            'Finds and fixes unblock log rows in the logging table where the log_title starts with the ' .
19            'prefix "User:#". These rows are broken because the target is an autoblock and code expects the "#" ' .
20            'character to be the first character in the title (T373929).'
21        );
22        $this->setBatchSize( 200 );
23    }
24
25    protected function doDBUpdates() {
26        $this->output( "Fixing log entries with log_title starting with 'User:#'\n" );
27
28        $dbr = $this->getReplicaDB();
29        $dbw = $this->getPrimaryDB();
30
31        $totalRowsFixed = 0;
32        $lastProcessedLogId = 0;
33        do {
34            // Get a batch of "unblock" log entries from the logging table. The check for the log_title being broken
35            // needs to be performed in batches, as it is expensive when run on the whole logging table on large wikis.
36            $logIds = $dbr->newSelectQueryBuilder()
37                ->select( 'log_id' )
38                ->from( 'logging' )
39                ->where( [
40                    'log_type' => 'block',
41                    'log_action' => 'unblock',
42                    $dbr->expr( 'log_id', '>', $lastProcessedLogId ),
43                ] )
44                ->limit( $this->getBatchSize() )
45                ->orderBy( 'log_id', SelectQueryBuilder::SORT_ASC )
46                ->caller( __METHOD__ )
47                ->fetchFieldValues();
48
49            if ( count( $logIds ) ) {
50                $lastId = end( $logIds );
51                $firstId = reset( $logIds );
52                $this->output( "...Processing unblock rows with IDs $firstId to $lastId\n" );
53
54                // Apply the LIKE query to find the rows with broken log_title values on the batch of log IDs.
55                // If any rows are found, then fix the log_title value.
56                $matchingRows = $dbr->newSelectQueryBuilder()
57                    ->select( [ 'log_id', 'log_title' ] )
58                    ->from( 'logging' )
59                    ->where( $dbr->expr(
60                        'log_title',
61                        IExpression::LIKE,
62                        new LikeValue( 'User:#', $dbr->anyString() )
63                    ) )
64                    ->andWhere( [ 'log_id' => $logIds ] )
65                    ->limit( $this->getBatchSize() )
66                    ->caller( __METHOD__ )
67                    ->fetchResultSet();
68
69                foreach ( $matchingRows as $row ) {
70                    $dbw->newUpdateQueryBuilder()
71                        ->update( 'logging' )
72                        ->set( [ 'log_title' => substr( $row->log_title, strlen( 'User:' ) ) ] )
73                        ->where( [ 'log_id' => $row->log_id ] )
74                        ->caller( __METHOD__ )
75                        ->execute();
76                    $totalRowsFixed += $dbw->affectedRows();
77                }
78
79                $this->waitForReplication();
80            }
81
82            $lastProcessedLogId = end( $logIds );
83        } while ( count( $logIds ) );
84
85        // Say we are done. Only output the total rows fixed if we found rows to fix, otherwise it may be confusing
86        // to see that no rows were fixed (and might imply that there are still rows to fix).
87        $this->output( "done." );
88        if ( $totalRowsFixed ) {
89            $this->output( " Fixed $totalRowsFixed rows." );
90        }
91        $this->output( "\n" );
92
93        return true;
94    }
95
96    protected function getUpdateKey() {
97        return __CLASS__;
98    }
99}
100
101// @codeCoverageIgnoreStart
102$maintClass = FixAutoblockLogTitles::class;
103require_once RUN_MAINTENANCE_IF_MAIN;
104// @codeCoverageIgnoreEnd