Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
SchemaChangesHandler
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
2 / 2
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 newFromGlobalState
n/a
0 / 0
n/a
0 / 0
1
 onLoadExtensionSchemaUpdates
n/a
0 / 0
n/a
0 / 0
7
 createAbuseFilterUser
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Hooks\Handlers;
4
5use DatabaseUpdater;
6use MediaWiki\Extension\AbuseFilter\Maintenance\MigrateActorsAF;
7use MediaWiki\Extension\AbuseFilter\Maintenance\UpdateVarDumps;
8use MediaWiki\Installer\Hook\LoadExtensionSchemaUpdatesHook;
9use MediaWiki\MediaWikiServices;
10use MediaWiki\User\User;
11use MediaWiki\User\UserGroupManager;
12use MessageLocalizer;
13use RequestContext;
14
15class SchemaChangesHandler implements LoadExtensionSchemaUpdatesHook {
16    /** @var MessageLocalizer */
17    private $messageLocalizer;
18    /** @var UserGroupManager */
19    private $userGroupManager;
20
21    /**
22     * @param MessageLocalizer $messageLocalizer
23     * @param UserGroupManager $userGroupManager
24     */
25    public function __construct( MessageLocalizer $messageLocalizer, UserGroupManager $userGroupManager ) {
26        $this->messageLocalizer = $messageLocalizer;
27        $this->userGroupManager = $userGroupManager;
28    }
29
30    /**
31     * @note The hook doesn't allow injecting services!
32     * @codeCoverageIgnore
33     * @return self
34     */
35    public static function newFromGlobalState(): self {
36        return new self(
37            // @todo Use a proper MessageLocalizer once available (T247127)
38            RequestContext::getMain(),
39            MediaWikiServices::getInstance()->getUserGroupManager()
40        );
41    }
42
43    /**
44     * @codeCoverageIgnore This is tested by installing or updating MediaWiki
45     * @param DatabaseUpdater $updater
46     */
47    public function onLoadExtensionSchemaUpdates( $updater ) {
48        global $wgAbuseFilterActorTableSchemaMigrationStage;
49
50        $dbType = $updater->getDB()->getType();
51        $dir = __DIR__ . "/../../../db_patches";
52
53        $updater->addExtensionTable(
54            'abuse_filter',
55            "$dir/$dbType/tables-generated.sql"
56        );
57
58        if ( $dbType === 'mysql' || $dbType === 'sqlite' ) {
59            if ( $dbType === 'mysql' ) {
60                // 1.37
61                $updater->renameExtensionIndex(
62                    'abuse_filter_log',
63                    'ip_timestamp',
64                    'afl_ip_timestamp',
65                    "$dir/mysql/patch-rename-indexes.sql",
66                    true
67                );
68
69                // 1.38
70                // This one has its own files because apparently, sometimes this particular index can already
71                // have the correct name (T291725)
72                $updater->renameExtensionIndex(
73                    'abuse_filter_log',
74                    'wiki_timestamp',
75                    'afl_wiki_timestamp',
76                    "$dir/mysql/patch-rename-wiki-timestamp-index.sql",
77                    true
78                );
79
80                // 1.38
81                // This one is also separate to avoid interferences with the afl_filter field removal below.
82                $updater->renameExtensionIndex(
83                    'abuse_filter_log',
84                    'filter_timestamp',
85                    'afl_filter_timestamp',
86                    "$dir/mysql/patch-rename-filter_timestamp-index.sql",
87                    true
88                );
89            }
90            // 1.38
91            $updater->dropExtensionField(
92                'abuse_filter_log',
93                'afl_filter',
94                "$dir/$dbType/patch-remove-afl_filter.sql"
95            );
96        } elseif ( $dbType === 'postgres' ) {
97            $updater->addExtensionUpdate( [
98                'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_timestamp'
99            ] );
100            $updater->addExtensionUpdate( [
101                'dropPgField', 'abuse_filter_log', 'afl_filter'
102            ] );
103            $updater->addExtensionUpdate( [
104                'dropDefault', 'abuse_filter_log', 'afl_filter_id'
105            ] );
106            $updater->addExtensionUpdate( [
107                'dropDefault', 'abuse_filter_log', 'afl_global'
108            ] );
109            $updater->addExtensionUpdate( [
110                'renameIndex', 'abuse_filter', 'abuse_filter_user', 'af_user'
111            ] );
112            $updater->addExtensionUpdate( [
113                'renameIndex', 'abuse_filter', 'abuse_filter_group_enabled_id', 'af_group_enabled'
114            ] );
115            $updater->addExtensionUpdate( [
116                'renameIndex', 'abuse_filter_action', 'abuse_filter_action_consequence', 'afa_consequence'
117            ] );
118            $updater->addExtensionUpdate( [
119                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_filter_timestamp_full', 'afl_filter_timestamp_full'
120            ] );
121            $updater->addExtensionUpdate( [
122                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_user_timestamp', 'afl_user_timestamp'
123            ] );
124            $updater->addExtensionUpdate( [
125                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_timestamp', 'afl_timestamp'
126            ] );
127            $updater->addExtensionUpdate( [
128                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_page_timestamp', 'afl_page_timestamp'
129            ] );
130            $updater->addExtensionUpdate( [
131                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_ip_timestamp', 'afl_ip_timestamp'
132            ] );
133            $updater->addExtensionUpdate( [
134                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_rev_id', 'afl_rev_id'
135            ] );
136            $updater->addExtensionUpdate( [
137                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_wiki_timestamp', 'afl_wiki_timestamp'
138            ] );
139            $updater->addExtensionUpdate( [
140                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_filter', 'afh_filter'
141            ] );
142            $updater->addExtensionUpdate( [
143                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_user', 'afh_user'
144            ] );
145            $updater->addExtensionUpdate( [
146                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_user_text', 'afh_user_text'
147            ] );
148            $updater->addExtensionUpdate( [
149                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_timestamp', 'afh_timestamp'
150            ] );
151            $updater->addExtensionUpdate( [
152                'changeNullableField', ' abuse_filter_history', 'afh_public_comments', 'NULL', true
153            ] );
154            $updater->addExtensionUpdate( [
155                'changeNullableField', ' abuse_filter_history', 'afh_actions', 'NULL', true
156            ] );
157        }
158
159        // 1.41
160        $updater->addExtensionUpdate( [
161            'addField', 'abuse_filter', 'af_actor',
162            "$dir/$dbType/patch-add-af_actor.sql", true
163        ] );
164
165        // 1.41
166        $updater->addExtensionUpdate( [
167            'addField', 'abuse_filter_history', 'afh_actor',
168            "$dir/$dbType/patch-add-afh_actor.sql", true
169        ] );
170
171        $updater->addExtensionUpdate( [ [ $this, 'createAbuseFilterUser' ] ] );
172        // 1.35
173        $updater->addPostDatabaseUpdateMaintenance( UpdateVarDumps::class );
174
175        // Don't launch the script on update.php if the migration stage is not high enough.
176        // This would throw an exception.
177        // Also check if the global is set.
178        // If globals aren't loaded, it's install.php, and not update.php. This is intentional,
179        // see for instance, T193855 or T198331.
180        if ( isset( $wgAbuseFilterActorTableSchemaMigrationStage ) &&
181            ( $wgAbuseFilterActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW )
182        ) {
183            // 1.41
184            $updater->addPostDatabaseUpdateMaintenance( MigrateActorsAF::class );
185        }
186    }
187
188    /**
189     * Updater callback to create the AbuseFilter user after the user tables have been updated.
190     * @param DatabaseUpdater $updater
191     * @return bool
192     */
193    public function createAbuseFilterUser( DatabaseUpdater $updater ): bool {
194        $username = $this->messageLocalizer->msg( 'abusefilter-blocker' )->inContentLanguage()->text();
195        $user = User::newFromName( $username );
196
197        if ( $user && !$updater->updateRowExists( 'create abusefilter-blocker-user' ) ) {
198            $user = User::newSystemUser( $username, [ 'steal' => true ] );
199            $updater->insertUpdateRow( 'create abusefilter-blocker-user' );
200            // Promote user so it doesn't look too crazy.
201            $this->userGroupManager->addUserToGroup( $user, 'sysop' );
202            return true;
203        }
204        return false;
205    }
206}