39 parent::__construct();
41 $this->
addDescription(
'Pass through all users and change or delete one of their options.
42The new option is NOT validated.' );
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(
'delete',
'Delete the option instead of updating' );
49 $this->
addOption(
'fromuserid',
'Start from this user ID when changing/deleting options',
51 $this->
addOption(
'touserid',
'Do not go beyond this user ID when changing/deleting options',
53 $this->
addOption(
'nowarn',
'Hides the 5 seconds warning' );
54 $this->
addOption(
'dry',
'Do not save user settings back to database' );
55 $this->
addArg(
'option name',
'Name of the option to change or provide statistics about',
false );
64 $this->listAvailableOptions();
65 } elseif ( $this->
hasOption(
'usage' ) ) {
66 $this->showUsageStats();
71 $this->updateOptions();
72 } elseif ( $this->
hasOption(
'delete' ) ) {
73 $this->deleteOptions();
82 private function listAvailableOptions() {
83 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
84 $def = $userOptionsLookup->getDefaultOptions();
87 foreach ( $def as $opt => $value ) {
88 $maxOpt = max( $maxOpt, strlen( $opt ) );
90 foreach ( $def as $opt => $value ) {
91 $this->
output( sprintf(
"%-{$maxOpt}s: %s\n", $opt, $value ) );
98 private function showUsageStats() {
99 $option = $this->
getArg( 0 );
102 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
103 $defaultOptions = $userOptionsLookup->getDefaultOptions();
107 $result =
$dbr->select(
'user',
113 foreach ( $result as $id ) {
118 if ( !array_key_exists( $option, $defaultOptions ) ) {
119 $this->
fatalError(
"Invalid user option. Use --list to see valid choices\n" );
122 $userValue = $userOptionsLookup->getOption( $user, $option );
123 if ( $userValue <> $defaultOptions[$option] ) {
124 $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
127 foreach ( $defaultOptions as $name => $defaultValue ) {
128 $userValue = $userOptionsLookup->getOption( $user, $name );
129 if ( $userValue != $defaultValue ) {
130 $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
136 foreach ( $ret as $optionName => $usageStats ) {
137 $this->
output(
"Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n" );
138 foreach ( $usageStats as $value => $count ) {
139 $this->
output(
" $count user(s): '$value'\n" );
148 private function updateOptions() {
150 $settingWord = $dryRun ?
'Would set' :
'Setting';
151 $option = $this->
getArg( 0 );
157 $fromUserId = (int)$this->
getOption(
'fromuserid', 1 ) - 1;
158 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
161 $forUsers = $from ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
164The script is about to change the options
for $forUsers in the database.
165Users with option <$option> =
'$from' will be made to use
'$to'.
167Abort with control-c in the next five seconds....
172 $userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager();
175 $queryBuilderTemplate
177 ->join(
'user_properties',
null, [
179 'up_property' => $option,
181 ->fields( [
'user_id',
'user_name' ] )
183 ->where( [
'up_value' => $from ] )
186 ->orderBy(
'user_id', SelectQueryBuilder::SORT_ASC )
188 ->caller( __METHOD__ );
190 $queryBuilderTemplate->andWhere(
"user_id <= $toUserId " );
194 $queryBuilder = clone $queryBuilderTemplate;
195 $queryBuilder->andWhere(
"user_id > $fromUserId" );
196 $result = $queryBuilder->fetchResultSet();
197 foreach ( $result as $row ) {
198 $this->
output(
"$settingWord {$option} for {$row->user_name} from '{$from}' to '{$to}'\n" );
199 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
201 $userOptionsManager->setOption( $user, $option, $to );
202 $userOptionsManager->saveOptions( $user );
204 $fromUserId = (int)$row->user_id;
207 }
while ( $result->numRows() );
213 private function deleteOptions() {
215 $option = $this->
getArg( 0 );
216 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
217 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
221 $forUsers = $fromUserId ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
223The script is about to
delete '$option' option
for $forUsers from user_properties table.
224This action is IRREVERSIBLE.
226Abort with control-c in the next five seconds....
235 $rowsInThisBatch = -1;
236 $minUserId = $fromUserId;
237 while ( $rowsInThisBatch != 0 ) {
239 'up_property' => $option,
240 "up_user > $minUserId"
243 $conds[] =
"up_user < $toUserId";
246 $conds[
'up_value'] = $old;
249 $userIds =
$dbr->selectFieldValues(
255 if ( $userIds === [] ) {
262 'up_property' => $option,
263 'up_user' => $userIds
266 $deleteConds[
'up_value'] = $old;
273 $rowsInThisBatch = $dbw->affectedRows();
275 $rowsInThisBatch = count( $userIds );
279 $rowsNum += $rowsInThisBatch;
280 $minUserId = max( $userIds );
284 $this->
output(
"Done! Deleted $rowsNum rows.\n" );
286 $this->
output(
"Would delete $rowsNum rows.\n" );
295 private function warn(
string $message ) {
300 $this->
output( $message );
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
addArg( $arg, $description, $required=true, $multi=false)
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...
getArg( $argId=0, $default=null)
Get an argument.
addDescription( $text)
Set the description text.
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.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.