MediaWiki REL1_39
userOptions.php
Go to the documentation of this file.
1<?php
30
31require_once __DIR__ . '/Maintenance.php';
32
37
38 public function __construct() {
39 parent::__construct();
40
41 $this->addDescription( 'Pass through all users and change one of their options.
42The new option is NOT validated.' );
43
44 $this->addOption( 'list', 'List available user options and their default value' );
45 $this->addOption( 'usage', 'Report all options statistics or just one if you specify it' );
46 $this->addOption( 'old', 'The value to look for', false, true );
47 $this->addOption( 'new', 'New value to update users with', false, true );
48 $this->addOption( 'fromuserid', 'Start from this user ID when changing options',
49 false, true );
50 $this->addOption( 'touserid', 'Do not go beyond this user ID when changing options',
51 false, true );
52 $this->addOption( 'nowarn', 'Hides the 5 seconds warning' );
53 $this->addOption( 'dry', 'Do not save user settings back to database' );
54 $this->addArg( 'option name', 'Name of the option to change or provide statistics about', false );
55 $this->setBatchSize( 100 );
56 }
57
61 public function execute() {
62 if ( $this->hasOption( 'list' ) ) {
63 $this->listAvailableOptions();
64 } elseif ( $this->hasOption( 'usage' ) ) {
65 $this->showUsageStats();
66 } elseif ( $this->hasOption( 'old' )
67 && $this->hasOption( 'new' )
68 && $this->hasArg( 0 )
69 ) {
70 $this->updateOptions();
71 } else {
72 $this->maybeHelp( true );
73 }
74 }
75
79 private function listAvailableOptions() {
80 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
81 $def = $userOptionsLookup->getDefaultOptions();
82 ksort( $def );
83 $maxOpt = 0;
84 foreach ( $def as $opt => $value ) {
85 $maxOpt = max( $maxOpt, strlen( $opt ) );
86 }
87 foreach ( $def as $opt => $value ) {
88 $this->output( sprintf( "%-{$maxOpt}s: %s\n", $opt, $value ) );
89 }
90 }
91
95 private function showUsageStats() {
96 $option = $this->getArg( 0 );
97
98 $ret = [];
99 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
100 $defaultOptions = $userOptionsLookup->getDefaultOptions();
101
102 // We list user by user_id from one of the replica DBs
104 $result = $dbr->select( 'user',
105 [ 'user_id' ],
106 [],
107 __METHOD__
108 );
109
110 foreach ( $result as $id ) {
111 $user = User::newFromId( $id->user_id );
112
113 // Get the options and update stats
114 if ( $option ) {
115 if ( !array_key_exists( $option, $defaultOptions ) ) {
116 $this->fatalError( "Invalid user option. Use --list to see valid choices\n" );
117 }
118
119 $userValue = $userOptionsLookup->getOption( $user, $option );
120 if ( $userValue <> $defaultOptions[$option] ) {
121 $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
122 }
123 } else {
124 foreach ( $defaultOptions as $name => $defaultValue ) {
125 $userValue = $userOptionsLookup->getOption( $user, $name );
126 if ( $userValue != $defaultValue ) {
127 $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
128 }
129 }
130 }
131 }
132
133 foreach ( $ret as $optionName => $usageStats ) {
134 $this->output( "Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n" );
135 foreach ( $usageStats as $value => $count ) {
136 $this->output( " $count user(s): '$value'\n" );
137 }
138 print "\n";
139 }
140 }
141
145 private function updateOptions() {
146 $dryRun = $this->hasOption( 'dry' );
147 $settingWord = $dryRun ? 'Would set' : 'Setting';
148 $option = $this->getArg( 0 );
149 $from = $this->getOption( 'old' );
150 $to = $this->getOption( 'new' );
151
152 if ( !$dryRun ) {
153 $this->warn( $option, $from, $to );
154 }
155
156 $userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
158 // The fromuserid parameter is inclusive, but iterating is easier with an exclusive
159 // range so convert it.
160 $fromUserId = (int)$this->getOption( 'fromuserid', 1 ) - 1;
161 $toUserId = (int)$this->getOption( 'touserid', 0 ) ?: null;
162 $queryBuilderTemplate = new SelectQueryBuilder( $dbr );
163 $queryBuilderTemplate
164 ->table( 'user' )
165 ->join( 'user_properties', null, [
166 'user_id = up_user',
167 'up_property' => $option,
168 ] )
169 ->fields( [ 'user_id', 'user_name' ] )
170 // up_value is unindexed so this can be slow, but should be acceptable in a script
171 ->where( [ 'up_value' => $from ] )
172 // need to order by ID so we can use ID ranges for query continuation
173 // also needed for the fromuserid / touserid parameters to work
174 ->orderBy( 'user_id', SelectQueryBuilder::SORT_ASC )
175 ->limit( $this->getBatchSize() )
176 ->caller( __METHOD__ );
177 if ( $toUserId ) {
178 $queryBuilderTemplate->andWhere( "user_id <= $toUserId " );
179 }
180
181 do {
182 $queryBuilder = clone $queryBuilderTemplate;
183 $queryBuilder->andWhere( "user_id > $fromUserId" );
184 $result = $queryBuilder->fetchResultSet();
185 foreach ( $result as $row ) {
186 $this->output( "$settingWord {$option} for {$row->user_name} from '{$from}' to '{$to}'\n" );
187 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
188 if ( !$dryRun ) {
189 $userOptionsManager->setOption( $user, $option, $to );
190 $userOptionsManager->saveOptions( $user );
191 }
192 $fromUserId = (int)$row->user_id;
193 }
194 $this->waitForReplication();
195 } while ( $result->numRows() );
196 }
197
205 private function warn( $option, $from, $to ) {
206 if ( $this->hasOption( 'nowarn' ) ) {
207 return;
208 }
209
210 $this->output( <<<WARN
211The script is about to change the options for ALL USERS in the database.
212Users with option <$option> = '$from' will be made to use '$to'.
213
214Abort with control-c in the next five seconds....
215WARN
216 );
217 $this->countDown( 5 );
218 }
219}
220
221$maintClass = UserOptionsMaintenance::class;
222require_once RUN_MAINTENANCE_IF_MAIN;
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
addArg( $arg, $description, $required=true)
Add some args that are needed.
output( $out, $channel=null)
Throw some output to the user.
hasArg( $argId=0)
Does a given argument exist?
waitForReplication()
Wait for replica DBs to catch up.
hasOption( $name)
Checks to see if a particular option was set.
countDown( $seconds)
Count down from $seconds to zero on the terminal, with a one-second pause between showing each number...
getBatchSize()
Returns batch size.
getArg( $argId=0, $default=null)
Get an argument.
addDescription( $text)
Set the description text.
maybeHelp( $force=false)
Maybe show the help.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
setBatchSize( $s=0)
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
Service locator for MediaWiki core services.
Value object representing a user's identity.
execute()
Do the actual work.
__construct()
Default constructor.
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:639
Note that none of the methods in this class are stable to override.
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:69
const DB_REPLICA
Definition defines.php:26
$maintClass