40 parent::__construct();
42 $this->
addDescription(
'Pass through all users and change or delete one of their options.
43The new option is NOT validated.' );
45 $this->
addOption(
'list',
'List available user options and their default value' );
46 $this->
addOption(
'usage',
'Report all options statistics or just one if you specify it' );
49 'The value to look for. If it is a default value for the option, pass --old-is-default as well.',
52 $this->
addOption(
'old-is-default',
'If passed, --old is interpreted as a default value.' );
53 $this->
addOption(
'new',
'New value to update users with',
false,
true );
54 $this->
addOption(
'delete',
'Delete the option instead of updating' );
55 $this->
addOption(
'delete-defaults',
'Delete user_properties row matching the default' );
56 $this->
addOption(
'fromuserid',
'Start from this user ID when changing/deleting options',
58 $this->
addOption(
'touserid',
'Do not go beyond this user ID when changing/deleting options',
60 $this->
addOption(
'nowarn',
'Hides the 5 seconds warning' );
61 $this->
addOption(
'dry',
'Do not save user settings back to database' );
62 $this->
addArg(
'option name',
'Name of the option to change or provide statistics about',
false );
71 $this->listAvailableOptions();
72 } elseif ( $this->
hasOption(
'usage' ) ) {
73 $this->showUsageStats();
78 $this->updateOptions();
79 } elseif ( $this->
hasOption(
'delete' ) ) {
80 $this->deleteOptions();
81 } elseif ( $this->
hasOption(
'delete-defaults' ) ) {
82 $this->deleteDefaults();
91 private function listAvailableOptions() {
93 $def = $userOptionsLookup->getDefaultOptions(
null );
96 foreach ( $def as $opt => $value ) {
97 $maxOpt = max( $maxOpt, strlen( $opt ) );
99 foreach ( $def as $opt => $value ) {
100 $this->
output( sprintf(
"%-{$maxOpt}s: %s\n", $opt, $value ) );
107 private function showUsageStats() {
108 $option = $this->
getArg( 0 );
112 $defaultOptions = $userOptionsLookup->getDefaultOptions();
117 $result = $dbr->newSelectQueryBuilder()
118 ->select( [
'user_id' ] )
120 ->caller( __METHOD__ )->fetchResultSet();
122 foreach ( $result as $id ) {
123 $user = User::newFromId( $id->user_id );
127 if ( !array_key_exists( $option, $defaultOptions ) ) {
128 $this->
fatalError(
"Invalid user option. Use --list to see valid choices\n" );
131 $userValue = $userOptionsLookup->getOption( $user, $option );
132 if ( $userValue <> $defaultOptions[$option] ) {
133 $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
136 foreach ( $defaultOptions as $name => $defaultValue ) {
137 $userValue = $userOptionsLookup->getOption( $user, $name );
138 if ( $userValue != $defaultValue ) {
139 $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
145 foreach ( $ret as $optionName => $usageStats ) {
146 $this->
output(
"Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n" );
147 foreach ( $usageStats as $value => $count ) {
148 $this->
output(
" $count user(s): '$value'\n" );
157 private function updateOptions() {
159 $settingWord = $dryRun ?
'Would set' :
'Setting';
160 $option = $this->
getArg( 0 );
161 $fromIsDefault = $this->
hasOption(
'old-is-default' );
167 $fromUserId = (int)$this->
getOption(
'fromuserid', 1 ) - 1;
168 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
171 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
174The script is about to change the options
for $forUsers in the database.
175Users with option <$option> =
'$from' will be made to use
'$to'.
177Abort with control-c in the next five seconds....
186 $queryBuilderTemplate
188 ->leftJoin(
'user_properties',
null, [
190 'up_property' => $option,
192 ->fields( [
'user_id',
'user_name' ] )
194 ->where( [
'up_value' => $fromIsDefault ?
null : $from ] )
199 ->caller( __METHOD__ );
201 $queryBuilderTemplate->andWhere(
"user_id <= $toUserId " );
204 if ( $tempUserConfig->isEnabled() ) {
205 $queryBuilderTemplate->andWhere(
206 $tempUserConfig->getMatchCondition( $dbr,
'user_name', IExpression::NOT_LIKE )
211 $queryBuilder = clone $queryBuilderTemplate;
212 $queryBuilder->andWhere(
"user_id > $fromUserId" );
213 $result = $queryBuilder->fetchResultSet();
214 foreach ( $result as $row ) {
215 $fromUserId = (int)$row->user_id;
217 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
218 if ( $fromIsDefault ) {
223 if ( $from != $userOptionsManager->getDefaultOption( $option, $user ) ) {
228 $this->
output(
"$settingWord {$option} for {$row->user_name} from '{$from}' to '{$to}'\n" );
230 $userOptionsManager->setOption( $user, $option, $to );
231 $userOptionsManager->saveOptions( $user );
235 }
while ( $result->numRows() );
241 private function deleteOptions() {
243 $option = $this->
getArg( 0 );
244 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
245 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
249 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
251The script is about to
delete '$option' option
for $forUsers from user_properties table.
252This action is IRREVERSIBLE.
254Abort with control-c in the next five seconds....
263 $rowsInThisBatch = -1;
264 $minUserId = $fromUserId;
265 while ( $rowsInThisBatch != 0 ) {
266 $queryBuilder = $dbr->newSelectQueryBuilder()
267 ->select(
'up_user' )
268 ->from(
'user_properties' )
269 ->where( [
'up_property' => $option,
"up_user > $minUserId" ] );
271 $queryBuilder->andWhere(
"up_user < $toUserId" );
274 $queryBuilder->andWhere( [
'up_value' => $old ] );
277 $userIds = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
278 if ( $userIds === [] ) {
284 $delete = $dbw->newDeleteQueryBuilder()
285 ->deleteFrom(
'user_properties' )
286 ->where( [
'up_property' => $option,
'up_user' => $userIds ] );
288 $delete->andWhere( [
'up_value' => $old ] );
290 $delete->caller( __METHOD__ )->execute();
291 $rowsInThisBatch = $dbw->affectedRows();
293 $rowsInThisBatch = count( $userIds );
297 $rowsNum += $rowsInThisBatch;
298 $minUserId = max( $userIds );
302 $this->
output(
"Done! Deleted $rowsNum rows.\n" );
304 $this->
output(
"Would delete $rowsNum rows.\n" );
308 private function deleteDefaults() {
310 $option = $this->
getArg( 0 );
311 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
312 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
314 if ( $option ===
null ) {
315 $this->
fatalError(
"Option name is required" );
320This script is about to
delete all rows in user_properties that match the current
321defaults
for the user (including conditional defaults).
322This action is IRREVERSIBLE.
324Abort with control-c in the next five seconds....
333 $queryBuilderTemplate->select( [
'user_id',
'user_name',
'up_value' ] )
334 ->from(
'user_properties' )
335 ->join(
'user',
null, [
'up_user = user_id' ] )
336 ->where( [
'up_property' => $option ] )
338 ->caller( __METHOD__ );
340 if ( $toUserId !==
null ) {
341 $queryBuilderTemplate->andWhere( $dbr->expr(
'up_user',
'<=', $toUserId ) );
346 $queryBuilder = clone $queryBuilderTemplate;
347 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'>', $fromUserId ) );
348 $result = $queryBuilder->fetchResultSet();
349 foreach ( $result as $row ) {
350 $fromUserId = (int)$row->user_id;
354 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
355 $userOptionsManager->setOption( $user, $option, $row->up_value );
356 $userOptionsManager->saveOptions( $user );
359 }
while ( $result->numRows() );
361 $this->
output(
"Done!\n" );
369 private function warn(
string $message ) {
374 $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?
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.
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.