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    /** @inheritDoc */
26    protected function doDBUpdates() {
27        $this->output( "Fixing log entries with log_title starting with 'User:#'\n" );
28
29        $dbr = $this->getReplicaDB();
30        $dbw = $this->getPrimaryDB();
31
32        $totalRowsFixed = 0;
33        $lastProcessedLogId = 0;
34        do {
35            // Get a batch of "unblock" log entries from the logging table. The check for the log_title being broken
36            // needs to be performed in batches, as it is expensive when run on the whole logging table on large wikis.
37            $logIds = $dbr->newSelectQueryBuilder()
38                ->select( 'log_id' )
39                ->from( 'logging' )
40                ->where( [
41                    'log_type' => 'block',
42                    'log_action' => 'unblock',
43                    $dbr->expr( 'log_id', '>', $lastProcessedLogId ),
44                ] )
45                ->limit( $this->getBatchSize() )
46                ->orderBy( 'log_id', SelectQueryBuilder::SORT_ASC )
47                ->caller( __METHOD__ )
48                ->fetchFieldValues();
49
50            if ( count( $logIds ) ) {
51                $lastId = end( $logIds );
52                $firstId = reset( $logIds );
53                $this->output( "...Processing unblock rows with IDs $firstId to $lastId\n" );
54
55                // Apply the LIKE query to find the rows with broken log_title values on the batch of log IDs.
56                // If any rows are found, then fix the log_title value.
57                $matchingRows = $dbr->newSelectQueryBuilder()
58                    ->select( [ 'log_id', 'log_title' ] )
59                    ->from( 'logging' )
60                    ->where( $dbr->expr(
61                        'log_title',
62                        IExpression::LIKE,
63                        new LikeValue( 'User:#', $dbr->anyString() )
64                    ) )
65                    ->andWhere( [ 'log_id' => $logIds ] )
66                    ->limit( $this->getBatchSize() )
67                    ->caller( __METHOD__ )
68                    ->fetchResultSet();
69
70                foreach ( $matchingRows as $row ) {
71                    $dbw->newUpdateQueryBuilder()
72                        ->update( 'logging' )
73                        ->set( [ 'log_title' => substr( $row->log_title, strlen( 'User:' ) ) ] )
74                        ->where( [ 'log_id' => $row->log_id ] )
75                        ->caller( __METHOD__ )
76                        ->execute();
77                    $totalRowsFixed += $dbw->affectedRows();
78                }
79
80                $this->waitForReplication();
81            }
82
83            $lastProcessedLogId = end( $logIds );
84        } while ( count( $logIds ) );
85
86        // Say we are done. Only output the total rows fixed if we found rows to fix, otherwise it may be confusing
87        // to see that no rows were fixed (and might imply that there are still rows to fix).
88        $this->output( "done." );
89        if ( $totalRowsFixed ) {
90            $this->output( " Fixed $totalRowsFixed rows." );
91        }
92        $this->output( "\n" );
93
94        return true;
95    }
96
97    /** @inheritDoc */
98    protected function getUpdateKey() {
99        return __CLASS__;
100    }
101}
102
103// @codeCoverageIgnoreStart
104$maintClass = FixAutoblockLogTitles::class;
105require_once RUN_MAINTENANCE_IF_MAIN;
106// @codeCoverageIgnoreEnd