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 / 71 |
|
0.00% |
0 / 3 |
240 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 27 |
|
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 | // @codeCoverageIgnoreStart |
28 | require_once __DIR__ . '/Maintenance.php'; |
29 | // @codeCoverageIgnoreEnd |
30 | |
31 | use MediaWiki\MainConfigNames; |
32 | use MediaWiki\User\Options\UserOptionsLookup; |
33 | use Wikimedia\Rdbms\IExpression; |
34 | use Wikimedia\Rdbms\LikeValue; |
35 | |
36 | /** |
37 | * Maintenance script that removes unused preferences from the database. |
38 | * |
39 | * @ingroup Maintenance |
40 | */ |
41 | class CleanupPreferences extends Maintenance { |
42 | public function __construct() { |
43 | parent::__construct(); |
44 | $this->addDescription( 'Clean up hidden preferences or removed preferences' ); |
45 | $this->setBatchSize( 50 ); |
46 | $this->addOption( 'dry-run', 'Print debug info instead of actually deleting' ); |
47 | $this->addOption( 'hidden', 'Drop hidden preferences ($wgHiddenPrefs)' ); |
48 | $this->addOption( 'unknown', |
49 | 'Drop unknown preferences (not in $wgDefaultUserOptions or prefixed with "userjs-")' ); |
50 | } |
51 | |
52 | /** |
53 | * We will do this in three passes |
54 | * 1) The easiest is to drop the hidden preferences from the database. We |
55 | * don't actually want them |
56 | * 2) Drop preference keys that we don't know about. They could've been |
57 | * removed from core, provided by a now-disabled extension, or the result |
58 | * of a bug. We don't want them. |
59 | */ |
60 | public function execute() { |
61 | $dbr = $this->getReplicaDB(); |
62 | $hidden = $this->hasOption( 'hidden' ); |
63 | $unknown = $this->hasOption( 'unknown' ); |
64 | |
65 | if ( !$hidden && !$unknown ) { |
66 | $this->output( "Did not select one of --hidden, --unknown, exiting\n" ); |
67 | return; |
68 | } |
69 | |
70 | // Remove hidden prefs. Iterate over them to avoid the IN on a large table |
71 | if ( $hidden ) { |
72 | $hiddenPrefs = $this->getConfig()->get( MainConfigNames::HiddenPrefs ); |
73 | if ( !$hiddenPrefs ) { |
74 | $this->output( "No hidden preferences, skipping\n" ); |
75 | } |
76 | foreach ( $hiddenPrefs as $hiddenPref ) { |
77 | $this->deleteByWhere( |
78 | $dbr, |
79 | 'Dropping hidden preferences', |
80 | [ 'up_property' => $hiddenPref ] |
81 | ); |
82 | } |
83 | } |
84 | |
85 | // Remove unknown preferences. Special-case 'userjs-' as we can't control those names. |
86 | if ( $unknown ) { |
87 | $defaultUserOptions = $this->getServiceContainer()->getUserOptionsLookup()->getDefaultOptions( null ); |
88 | $where = [ |
89 | $dbr->expr( 'up_property', IExpression::NOT_LIKE, |
90 | new LikeValue( 'userjs-', $dbr->anyString() ) ), |
91 | $dbr->expr( 'up_property', IExpression::NOT_LIKE, |
92 | new LikeValue( UserOptionsLookup::LOCAL_EXCEPTION_SUFFIX, $dbr->anyString() ) ), |
93 | $dbr->expr( 'up_property', '!=', array_keys( $defaultUserOptions ) ), |
94 | ]; |
95 | // Allow extensions to add to the where clause to prevent deletion of their own prefs. |
96 | $this->getHookRunner()->onDeleteUnknownPreferences( $where, $dbr ); |
97 | $this->deleteByWhere( $dbr, 'Dropping unknown preferences', $where ); |
98 | } |
99 | } |
100 | |
101 | private function deleteByWhere( $dbr, $startMessage, $where ) { |
102 | $this->output( $startMessage . "...\n" ); |
103 | $dryRun = $this->hasOption( 'dry-run' ); |
104 | |
105 | $iterator = new BatchRowIterator( |
106 | $dbr, |
107 | 'user_properties', |
108 | [ 'up_user', 'up_property' ], |
109 | $this->getBatchSize() |
110 | ); |
111 | if ( $dryRun ) { |
112 | $iterator->setFetchColumns( [ 'up_user', 'up_property', 'up_value' ] ); |
113 | } else { |
114 | $iterator->setFetchColumns( [ 'up_user', 'up_property' ] ); |
115 | } |
116 | $iterator->addConditions( $where ); |
117 | $iterator->setCaller( __METHOD__ ); |
118 | |
119 | $dbw = $this->getPrimaryDB(); |
120 | $total = 0; |
121 | foreach ( $iterator as $batch ) { |
122 | $numRows = count( $batch ); |
123 | $total += $numRows; |
124 | // Progress or something |
125 | $this->output( "..doing $numRows entries\n" ); |
126 | |
127 | // Delete our batch, then wait |
128 | $deleteWhere = []; |
129 | foreach ( $batch as $row ) { |
130 | if ( $dryRun ) { |
131 | $this->output( |
132 | " DRY RUN, would drop: " . |
133 | "[up_user] => '{$row->up_user}' " . |
134 | "[up_property] => '{$row->up_property}' " . |
135 | "[up_value] => '{$row->up_value}'\n" |
136 | ); |
137 | continue; |
138 | } |
139 | $deleteWhere[$row->up_user][$row->up_property] = true; |
140 | } |
141 | if ( $deleteWhere && !$dryRun ) { |
142 | $dbw->newDeleteQueryBuilder() |
143 | ->deleteFrom( 'user_properties' ) |
144 | ->where( $dbw->makeWhereFrom2d( $deleteWhere, 'up_user', 'up_property' ) ) |
145 | ->caller( __METHOD__ )->execute(); |
146 | |
147 | $this->waitForReplication(); |
148 | } |
149 | } |
150 | $this->output( "DONE! (handled $total entries)\n" ); |
151 | } |
152 | } |
153 | |
154 | // @codeCoverageIgnoreStart |
155 | $maintClass = CleanupPreferences::class; // Tells it to run the class |
156 | require_once RUN_MAINTENANCE_IF_MAIN; |
157 | // @codeCoverageIgnoreEnd |