Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 71 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
CleanupPreferences | |
0.00% |
0 / 68 |
|
0.00% |
0 / 3 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
deleteByWhere | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | /** |
3 | * Clean up user preferences from the database. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @author TyA <tya.wiki@gmail.com> |
22 | * @author Chad <chad@wikimedia.org> |
23 | * @see https://phabricator.wikimedia.org/T32976 |
24 | * @ingroup Maintenance |
25 | */ |
26 | |
27 | require_once __DIR__ . '/Maintenance.php'; |
28 | |
29 | use MediaWiki\MainConfigNames; |
30 | |
31 | /** |
32 | * Maintenance script that removes unused preferences from the database. |
33 | * |
34 | * @ingroup Maintenance |
35 | */ |
36 | class CleanupPreferences extends Maintenance { |
37 | public function __construct() { |
38 | parent::__construct(); |
39 | $this->addDescription( 'Clean up hidden preferences or removed preferences' ); |
40 | $this->setBatchSize( 50 ); |
41 | $this->addOption( 'dry-run', 'Print debug info instead of actually deleting' ); |
42 | $this->addOption( 'hidden', 'Drop hidden preferences ($wgHiddenPrefs)' ); |
43 | $this->addOption( 'unknown', |
44 | 'Drop unknown preferences (not in $wgDefaultUserOptions or prefixed with "userjs-")' ); |
45 | } |
46 | |
47 | /** |
48 | * We will do this in three passes |
49 | * 1) The easiest is to drop the hidden preferences from the database. We |
50 | * don't actually want them |
51 | * 2) Drop preference keys that we don't know about. They could've been |
52 | * removed from core, provided by a now-disabled extension, or the result |
53 | * of a bug. We don't want them. |
54 | */ |
55 | public function execute() { |
56 | $dbr = $this->getDB( DB_REPLICA ); |
57 | $hidden = $this->hasOption( 'hidden' ); |
58 | $unknown = $this->hasOption( 'unknown' ); |
59 | |
60 | if ( !$hidden && !$unknown ) { |
61 | $this->output( "Did not select one of --hidden, --unknown, exiting\n" ); |
62 | return; |
63 | } |
64 | |
65 | // Remove hidden prefs. Iterate over them to avoid the IN on a large table |
66 | if ( $hidden ) { |
67 | $hiddenPrefs = $this->getConfig()->get( MainConfigNames::HiddenPrefs ); |
68 | if ( !$hiddenPrefs ) { |
69 | $this->output( "No hidden preferences, skipping\n" ); |
70 | } |
71 | foreach ( $hiddenPrefs as $hiddenPref ) { |
72 | $this->deleteByWhere( |
73 | $dbr, |
74 | 'Dropping hidden preferences', |
75 | [ 'up_property' => $hiddenPref ] |
76 | ); |
77 | } |
78 | } |
79 | |
80 | // Remove unknown preferences. Special-case 'userjs-' as we can't control those names. |
81 | if ( $unknown ) { |
82 | $defaultUserOptions = $this->getServiceContainer()->getUserOptionsLookup()->getDefaultOptions( null ); |
83 | $where = [ |
84 | 'up_property NOT' . $dbr->buildLike( 'userjs-', $dbr->anyString() ), |
85 | 'up_property NOT IN (' . $dbr->makeList( array_keys( $defaultUserOptions ) ) . ')', |
86 | ]; |
87 | // Allow extensions to add to the where clause to prevent deletion of their own prefs. |
88 | $this->getHookRunner()->onDeleteUnknownPreferences( $where, $dbr ); |
89 | $this->deleteByWhere( $dbr, 'Dropping unknown preferences', $where ); |
90 | } |
91 | } |
92 | |
93 | private function deleteByWhere( $dbr, $startMessage, $where ) { |
94 | $this->output( $startMessage . "...\n" ); |
95 | $dryRun = $this->hasOption( 'dry-run' ); |
96 | |
97 | $iterator = new BatchRowIterator( |
98 | $dbr, |
99 | 'user_properties', |
100 | [ 'up_user', 'up_property' ], |
101 | $this->getBatchSize() |
102 | ); |
103 | if ( $dryRun ) { |
104 | $iterator->setFetchColumns( [ 'up_user', 'up_property', 'up_value' ] ); |
105 | } else { |
106 | $iterator->setFetchColumns( [ 'up_user', 'up_property' ] ); |
107 | } |
108 | $iterator->addConditions( $where ); |
109 | $iterator->setCaller( __METHOD__ ); |
110 | |
111 | $dbw = $this->getPrimaryDB(); |
112 | $total = 0; |
113 | foreach ( $iterator as $batch ) { |
114 | $numRows = count( $batch ); |
115 | $total += $numRows; |
116 | // Progress or something |
117 | $this->output( "..doing $numRows entries\n" ); |
118 | |
119 | // Delete our batch, then wait |
120 | $deleteWhere = []; |
121 | foreach ( $batch as $row ) { |
122 | if ( $dryRun ) { |
123 | $this->output( |
124 | " DRY RUN, would drop: " . |
125 | "[up_user] => '{$row->up_user}' " . |
126 | "[up_property] => '{$row->up_property}' " . |
127 | "[up_value] => '{$row->up_value}'\n" |
128 | ); |
129 | continue; |
130 | } |
131 | $deleteWhere[$row->up_user][$row->up_property] = true; |
132 | } |
133 | if ( $deleteWhere && !$dryRun ) { |
134 | $dbw->newDeleteQueryBuilder() |
135 | ->deleteFrom( 'user_properties' ) |
136 | ->where( $dbw->makeWhereFrom2d( $deleteWhere, 'up_user', 'up_property' ) ) |
137 | ->caller( __METHOD__ )->execute(); |
138 | |
139 | $this->waitForReplication(); |
140 | } |
141 | } |
142 | $this->output( "DONE! (handled $total entries)\n" ); |
143 | } |
144 | } |
145 | |
146 | $maintClass = CleanupPreferences::class; // Tells it to run the class |
147 | require_once RUN_MAINTENANCE_IF_MAIN; |