aboutsummaryrefslogtreecommitdiffstats
path: root/maintenance/fixAutoblockLogTitles.php
blob: d2fcca74cf6350b3ebb2cccfadcf1093b698bba5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php

namespace MediaWiki\Maintenance;

use Wikimedia\Rdbms\IExpression;
use Wikimedia\Rdbms\LikeValue;
use Wikimedia\Rdbms\SelectQueryBuilder;

// @codeCoverageIgnoreStart
require_once __DIR__ . '/Maintenance.php';
// @codeCoverageIgnoreEnd

class FixAutoblockLogTitles extends LoggedUpdateMaintenance {

	public function __construct() {
		parent::__construct();
		$this->addDescription(
			'Finds and fixes unblock log rows in the logging table where the log_title starts with the ' .
			'prefix "User:#". These rows are broken because the target is an autoblock and code expects the "#" ' .
			'character to be the first character in the title (T373929).'
		);
		$this->setBatchSize( 200 );
	}

	protected function doDBUpdates() {
		$this->output( "Fixing log entries with log_title starting with 'User:#'\n" );

		$dbr = $this->getReplicaDB();
		$dbw = $this->getPrimaryDB();

		$totalRowsFixed = 0;
		$lastProcessedLogId = 0;
		do {
			// Get a batch of "unblock" log entries from the logging table. The check for the log_title being broken
			// needs to be performed in batches, as it is expensive when run on the whole logging table on large wikis.
			$logIds = $dbr->newSelectQueryBuilder()
				->select( 'log_id' )
				->from( 'logging' )
				->where( [
					'log_type' => 'block',
					'log_action' => 'unblock',
					$dbr->expr( 'log_id', '>', $lastProcessedLogId ),
				] )
				->limit( $this->getBatchSize() )
				->orderBy( 'log_id', SelectQueryBuilder::SORT_ASC )
				->caller( __METHOD__ )
				->fetchFieldValues();

			if ( count( $logIds ) ) {
				$lastId = end( $logIds );
				$firstId = reset( $logIds );
				$this->output( "...Processing unblock rows with IDs $firstId to $lastId\n" );

				// Apply the LIKE query to find the rows with broken log_title values on the batch of log IDs.
				// If any rows are found, then fix the log_title value.
				$matchingRows = $dbr->newSelectQueryBuilder()
					->select( [ 'log_id', 'log_title' ] )
					->from( 'logging' )
					->where( $dbr->expr(
						'log_title',
						IExpression::LIKE,
						new LikeValue( 'User:#', $dbr->anyString() )
					) )
					->andWhere( [ 'log_id' => $logIds ] )
					->limit( $this->getBatchSize() )
					->caller( __METHOD__ )
					->fetchResultSet();

				foreach ( $matchingRows as $row ) {
					$dbw->newUpdateQueryBuilder()
						->update( 'logging' )
						->set( [ 'log_title' => substr( $row->log_title, strlen( 'User:' ) ) ] )
						->where( [ 'log_id' => $row->log_id ] )
						->caller( __METHOD__ )
						->execute();
					$totalRowsFixed += $dbw->affectedRows();
				}

				$this->waitForReplication();
			}

			$lastProcessedLogId = end( $logIds );
		} while ( count( $logIds ) );

		// Say we are done. Only output the total rows fixed if we found rows to fix, otherwise it may be confusing
		// to see that no rows were fixed (and might imply that there are still rows to fix).
		$this->output( "done." );
		if ( $totalRowsFixed ) {
			$this->output( " Fixed $totalRowsFixed rows." );
		}
		$this->output( "\n" );

		return true;
	}

	protected function getUpdateKey() {
		return __CLASS__;
	}
}

// @codeCoverageIgnoreStart
$maintClass = FixAutoblockLogTitles::class;
require_once RUN_MAINTENANCE_IF_MAIN;
// @codeCoverageIgnoreEnd