Update git submodules
[mediawiki.git] / maintenance / cleanupPreferences.php
blobb0e5dc06a7fbc4c2eb1bcc07e14ac650c642001b
1 <?php
2 /**
3 * Clean up user preferences from the database.
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.
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.
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
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
27 require_once __DIR__ . '/Maintenance.php';
29 use MediaWiki\MainConfigNames;
31 /**
32 * Maintenance script that removes unused preferences from the database.
34 * @ingroup Maintenance
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-")' );
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.
55 public function execute() {
56 $dbr = $this->getDB( DB_REPLICA );
57 $hidden = $this->hasOption( 'hidden' );
58 $unknown = $this->hasOption( 'unknown' );
60 if ( !$hidden && !$unknown ) {
61 $this->output( "Did not select one of --hidden, --unknown, exiting\n" );
62 return;
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" );
71 foreach ( $hiddenPrefs as $hiddenPref ) {
72 $this->deleteByWhere(
73 $dbr,
74 'Dropping hidden preferences',
75 [ 'up_property' => $hiddenPref ]
80 // Remove unknown preferences. Special-case 'userjs-' as we can't control those names.
81 if ( $unknown ) {
82 $defaultUserOptions = $this->getServiceContainer()->getUserOptionsLookup()->getDefaultOptions();
83 $where = [
84 'up_property NOT' . $dbr->buildLike( 'userjs-', $dbr->anyString() ),
85 'up_property NOT IN (' . $dbr->makeList( array_keys( $defaultUserOptions ) ) . ')',
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 );
93 private function deleteByWhere( $dbr, $startMessage, $where ) {
94 $this->output( $startMessage . "...\n" );
95 $dryRun = $this->hasOption( 'dry-run' );
97 $iterator = new BatchRowIterator(
98 $dbr,
99 'user_properties',
100 [ 'up_user', 'up_property' ],
101 $this->getBatchSize()
103 if ( $dryRun ) {
104 $iterator->setFetchColumns( [ 'up_user', 'up_property', 'up_value' ] );
105 } else {
106 $iterator->setFetchColumns( [ 'up_user', 'up_property' ] );
108 $iterator->addConditions( $where );
109 $iterator->setCaller( __METHOD__ );
111 $dbw = $this->getDB( DB_PRIMARY );
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" );
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"
129 continue;
131 $deleteWhere[$row->up_user][$row->up_property] = true;
133 if ( $deleteWhere && !$dryRun ) {
134 $dbw->delete(
135 'user_properties',
136 $dbw->makeWhereFrom2d( $deleteWhere, 'up_user', 'up_property' ),
137 __METHOD__
140 $this->waitForReplication();
143 $this->output( "DONE! (handled $total entries)\n" );
147 $maintClass = CleanupPreferences::class; // Tells it to run the class
148 require_once RUN_MAINTENANCE_IF_MAIN;