summaryrefslogtreecommitdiff
blob: 17a1b77d52f7598a29fe0334aa0c600026eb80d6 (plain)
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<?php

if ( getenv( 'MW_INSTALL_PATH' ) ) {
	$IP = getenv( 'MW_INSTALL_PATH' );
} else {
	$IP = __DIR__ . '/../../..';
}
require_once "$IP/maintenance/Maintenance.php";

use MediaWiki\MediaWikiServices;

/**
 * Adds rows missing per T54919
 * @codeCoverageIgnore
 * No need to cover: old, single-use script.
 */
class AddMissingLoggingEntries extends LoggedUpdateMaintenance {
	public function __construct() {
		parent::__construct();

		$this->addDescription( 'Add missing logging entries for abusefilter-modify T54919' );
		$this->addOption( 'dry-run', 'Perform a dry run' );
		$this->addOption( 'verbose', 'Print a list of affected afh_id' );
		$this->requireExtension( 'Abuse Filter' );
	}

	/**
	 * @inheritDoc
	 */
	public function getUpdateKey() {
		return __CLASS__;
	}

	/**
	 * @inheritDoc
	 */
	public function doDBUpdates() {
		$dryRun = $this->hasOption( 'dry-run' );
		$logParams = [];
		$afhRows = [];
		$db = wfGetDB( DB_REPLICA, 'vslow' );

		$logParamsConcat = $db->buildConcat( [ 'afh_id', $db->addQuotes( "\n" ) ] );
		$legacyParamsLike = $db->buildLike( $logParamsConcat, $db->anyString() );
		// Non-legacy entries are a serialized array with 'newId' and 'historyId' keys
		$newLogParamsLike = $db->buildLike( $db->anyString(), 'historyId', $db->anyString() );
		// Find all entries in abuse_filter_history without logging entry of same timestamp
		$afhResult = $db->select(
			[ 'abuse_filter_history', 'logging' ],
			[ 'afh_id', 'afh_filter', 'afh_timestamp', 'afh_user', 'afh_deleted', 'afh_user_text' ],
			[
				'log_id IS NULL',
				"NOT log_params $newLogParamsLike"
			],
			__METHOD__,
			[],
			[ 'logging' => [
				'LEFT JOIN',
				"afh_timestamp = log_timestamp AND log_params $legacyParamsLike AND log_type = 'abusefilter'"
			] ]
		);

		// Because the timestamp matches aren't exact (sometimes a couple of
		// seconds off), we need to check all our results and ignore those that
		// do actually have log entries
		foreach ( $afhResult as $row ) {
			$logParams[] = $row->afh_id . "\n" . $row->afh_filter;
			$afhRows[$row->afh_id] = $row;
		}

		if ( !count( $afhRows ) ) {
			$this->output( "Nothing to do.\n" );
			return !$dryRun;
		}

		$logResult = wfGetDB( DB_REPLICA )->select(
			'logging',
			[ 'log_params' ],
			[ 'log_type' => 'abusefilter', 'log_params' => $logParams ],
			__METHOD__
		);

		foreach ( $logResult as $row ) {
			// id . "\n" . filter
			$afhId = explode( "\n", $row->log_params, 2 )[0];
			// Forget this row had any issues - it just has a different timestamp in the log
			unset( $afhRows[$afhId] );
		}

		if ( !count( $afhRows ) ) {
			$this->output( "Nothing to do.\n" );
			return !$dryRun;
		}

		if ( $dryRun ) {
			$msg = count( $afhRows ) . " rows to insert.";
			if ( $this->hasOption( 'verbose' ) ) {
				$msg .= " Affected IDs (afh_id):\n" . implode( ', ', array_keys( $afhRows ) );
			}
			$this->output( "$msg\n" );
			return false;
		}

		$dbw = wfGetDB( DB_MASTER );
		$factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();

		$count = 0;
		foreach ( $afhRows as $row ) {
			if ( $count % 100 === 0 ) {
				$factory->waitForReplication();
			}
			$user = User::newFromAnyId( $row->afh_user, $row->afh_user_text, null );

			if ( $user === null ) {
				// This isn't supposed to happen.
				continue;
			}

			// This copies the code in AbuseFilter::doSaveFilter
			$logEntry = new ManualLogEntry( 'abusefilter', 'modify' );
			$logEntry->setPerformer( $user );
			$logEntry->setTarget(
				SpecialPage::getTitleFor( SpecialAbuseFilter::PAGE_NAME, $row->afh_filter )
			);
			// Use the new format!
			$logEntry->setParameters( [
				'historyId' => $row->afh_id,
				'newId' => $row->afh_filter
			] );
			$logEntry->setTimestamp( $row->afh_timestamp );
			$logEntry->insert( $dbw );

			$count++;
		}

		$this->output( "Inserted $count rows.\n" );
		return true;
	}
}

$maintClass = AddMissingLoggingEntries::class;
require_once RUN_MAINTENANCE_IF_MAIN;