Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
SchemaChangesHandler
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
2 / 2
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
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 MediaWiki\Context\RequestContext;
6use MediaWiki\Extension\AbuseFilter\Maintenance\MigrateActorsAF;
7use MediaWiki\Extension\AbuseFilter\Maintenance\PopulateAbuseFilterLogIPHex;
8use MediaWiki\Installer\DatabaseUpdater;
9use MediaWiki\Installer\Hook\LoadExtensionSchemaUpdatesHook;
10use MediaWiki\MediaWikiServices;
11use MediaWiki\User\User;
12use MediaWiki\User\UserFactory;
13use MediaWiki\User\UserGroupManager;
14use MessageLocalizer;
15
16class SchemaChangesHandler implements LoadExtensionSchemaUpdatesHook {
17    /** @var MessageLocalizer */
18    private $messageLocalizer;
19    /** @var UserGroupManager */
20    private $userGroupManager;
21    /** @var UserFactory */
22    private $userFactory;
23
24    /**
25     * @param MessageLocalizer $messageLocalizer
26     * @param UserGroupManager $userGroupManager
27     * @param UserFactory $userFactory
28     */
29    public function __construct(
30        MessageLocalizer $messageLocalizer,
31        UserGroupManager $userGroupManager,
32        UserFactory $userFactory
33    ) {
34        $this->messageLocalizer = $messageLocalizer;
35        $this->userGroupManager = $userGroupManager;
36        $this->userFactory = $userFactory;
37    }
38
39    /**
40     * @note The hook doesn't allow injecting services!
41     * @codeCoverageIgnore
42     * @return self
43     */
44    public static function newFromGlobalState(): self {
45        return new self(
46            // @todo Use a proper MessageLocalizer once available (T247127)
47            RequestContext::getMain(),
48            MediaWikiServices::getInstance()->getUserGroupManager(),
49            MediaWikiServices::getInstance()->getUserFactory()
50        );
51    }
52
53    /**
54     * @codeCoverageIgnore This is tested by installing or updating MediaWiki
55     * @param DatabaseUpdater $updater
56     */
57    public function onLoadExtensionSchemaUpdates( $updater ) {
58        $dbType = $updater->getDB()->getType();
59        $maintenanceDb = $updater->getDB();
60        $dir = __DIR__ . "/../../../db_patches";
61
62        $updater->addExtensionTable(
63            'abuse_filter',
64            "$dir/$dbType/tables-generated.sql"
65        );
66
67        if ( $dbType === 'mysql' || $dbType === 'sqlite' ) {
68            if ( $dbType === 'mysql' ) {
69                // 1.37
70                $updater->renameExtensionIndex(
71                    'abuse_filter_log',
72                    'ip_timestamp',
73                    'afl_ip_timestamp',
74                    "$dir/mysql/patch-rename-indexes.sql",
75                    true
76                );
77
78                // 1.38
79                // This one has its own files because apparently, sometimes this particular index can already
80                // have the correct name (T291725)
81                $updater->renameExtensionIndex(
82                    'abuse_filter_log',
83                    'wiki_timestamp',
84                    'afl_wiki_timestamp',
85                    "$dir/mysql/patch-rename-wiki-timestamp-index.sql",
86                    true
87                );
88
89                // 1.38
90                // This one is also separate to avoid interferences with the afl_filter field removal below.
91                $updater->renameExtensionIndex(
92                    'abuse_filter_log',
93                    'filter_timestamp',
94                    'afl_filter_timestamp',
95                    "$dir/mysql/patch-rename-filter_timestamp-index.sql",
96                    true
97                );
98            }
99            // 1.38
100            $updater->dropExtensionField(
101                'abuse_filter_log',
102                'afl_filter',
103                "$dir/$dbType/patch-remove-afl_filter.sql"
104            );
105        } elseif ( $dbType === 'postgres' ) {
106            $updater->addExtensionUpdate( [
107                'dropPgIndex', 'abuse_filter_log', 'abuse_filter_log_timestamp'
108            ] );
109            $updater->addExtensionUpdate( [
110                'dropPgField', 'abuse_filter_log', 'afl_filter'
111            ] );
112            $updater->addExtensionUpdate( [
113                'dropDefault', 'abuse_filter_log', 'afl_filter_id'
114            ] );
115            $updater->addExtensionUpdate( [
116                'dropDefault', 'abuse_filter_log', 'afl_global'
117            ] );
118            $updater->addExtensionUpdate( [
119                'renameIndex', 'abuse_filter', 'abuse_filter_user', 'af_user'
120            ] );
121            $updater->addExtensionUpdate( [
122                'renameIndex', 'abuse_filter', 'abuse_filter_group_enabled_id', 'af_group_enabled'
123            ] );
124            $updater->addExtensionUpdate( [
125                'renameIndex', 'abuse_filter_action', 'abuse_filter_action_consequence', 'afa_consequence'
126            ] );
127            $updater->addExtensionUpdate( [
128                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_filter_timestamp_full', 'afl_filter_timestamp_full'
129            ] );
130            $updater->addExtensionUpdate( [
131                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_user_timestamp', 'afl_user_timestamp'
132            ] );
133            $updater->addExtensionUpdate( [
134                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_timestamp', 'afl_timestamp'
135            ] );
136            $updater->addExtensionUpdate( [
137                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_page_timestamp', 'afl_page_timestamp'
138            ] );
139            $updater->addExtensionUpdate( [
140                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_ip_timestamp', 'afl_ip_timestamp'
141            ] );
142            $updater->addExtensionUpdate( [
143                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_rev_id', 'afl_rev_id'
144            ] );
145            $updater->addExtensionUpdate( [
146                'renameIndex', 'abuse_filter_log', 'abuse_filter_log_wiki_timestamp', 'afl_wiki_timestamp'
147            ] );
148            $updater->addExtensionUpdate( [
149                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_filter', 'afh_filter'
150            ] );
151            $updater->addExtensionUpdate( [
152                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_user', 'afh_user'
153            ] );
154            $updater->addExtensionUpdate( [
155                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_user_text', 'afh_user_text'
156            ] );
157            $updater->addExtensionUpdate( [
158                'renameIndex', 'abuse_filter_history', 'abuse_filter_history_timestamp', 'afh_timestamp'
159            ] );
160            $updater->addExtensionUpdate( [
161                'changeNullableField', ' abuse_filter_history', 'afh_public_comments', 'NULL', true
162            ] );
163            $updater->addExtensionUpdate( [
164                'changeNullableField', ' abuse_filter_history', 'afh_actions', 'NULL', true
165            ] );
166        }
167
168        // 1.41
169        $updater->addExtensionUpdate( [
170            'addField', 'abuse_filter', 'af_actor',
171            "$dir/$dbType/patch-add-af_actor.sql", true
172        ] );
173
174        // 1.41
175        $updater->addExtensionUpdate( [
176            'addField', 'abuse_filter_history', 'afh_actor',
177            "$dir/$dbType/patch-add-afh_actor.sql", true
178        ] );
179
180        // 1.43
181        $updater->addExtensionUpdate( [
182            'runMaintenance',
183            MigrateActorsAF::class,
184        ] );
185
186        // 1.43
187        $updater->addExtensionUpdate( [
188            'dropField', 'abuse_filter', 'af_user',
189            "$dir/$dbType/patch-drop-af_user.sql", true
190        ] );
191
192        // 1.43
193        $updater->addExtensionUpdate( [
194            'dropField', 'abuse_filter_history', 'afh_user',
195            "$dir/$dbType/patch-drop-afh_user.sql", true
196        ] );
197
198        // 1.44
199        $updater->addExtensionUpdate( [
200            'dropField', 'abuse_filter_log', 'afl_patrolled_by',
201            "$dir/$dbType/patch-drop-afl_patrolled_by.sql", true
202        ] );
203
204        // 1.45
205        $updater->addExtensionIndex(
206            'abuse_filter_log', 'afl_var_dump_timestamp',
207            "$dir/$dbType/patch-add-index-afl_var_dump_timestamp.sql"
208        );
209        $updater->addExtensionField(
210            'abuse_filter_log', 'afl_ip_hex',
211            "$dir/$dbType/patch-add-afl_ip_hex.sql"
212        );
213        $updater->addExtensionUpdate( [
214            'runMaintenance',
215            PopulateAbuseFilterLogIPHex::class,
216        ] );
217        $updater->modifyExtensionField(
218            'abuse_filter_log', 'afl_ip',
219            "$dir/$dbType/patch-add-default-afl_ip.sql"
220        );
221        // Only run this for SQLite if afl_ip exists, as modifyExtensionField does not take into
222        // account that SQLite patches that use temporary tables. If the afl_ip field does not exist
223        // this SQL would fail, however, afl_ip not existing also means this change has
224        // been previously applied.
225        if (
226            $dbType !== 'sqlite' ||
227            $maintenanceDb->fieldExists( 'abuse_filter_log', 'afl_ip', __METHOD__ )
228        ) {
229            $updater->modifyExtensionField(
230                'abuse_filter_log', 'afl_ip_hex',
231                "$dir/$dbType/patch-remove-default-afl_ip_hex.sql"
232            );
233        }
234        $updater->dropExtensionField(
235            'abuse_filter_log', 'afl_ip',
236            "$dir/$dbType/patch-drop-afl_ip.sql"
237        );
238
239        $updater->addExtensionUpdate( [ [ $this, 'createAbuseFilterUser' ] ] );
240    }
241
242    /**
243     * Updater callback to create the AbuseFilter user after the user tables have been updated.
244     * @param DatabaseUpdater $updater
245     * @return bool
246     */
247    public function createAbuseFilterUser( DatabaseUpdater $updater ): bool {
248        $username = $this->messageLocalizer->msg( 'abusefilter-blocker' )->inContentLanguage()->text();
249        $user = $this->userFactory->newFromName( $username );
250
251        if ( $user && !$updater->updateRowExists( 'create abusefilter-blocker-user' ) ) {
252            $user = User::newSystemUser( $username, [ 'steal' => true ] );
253            $updater->insertUpdateRow( 'create abusefilter-blocker-user' );
254            // Promote user so it doesn't look too crazy.
255            $this->userGroupManager->addUserToGroup( $user, 'sysop' );
256            return true;
257        }
258        return false;
259    }
260}