42 parent::__construct();
44 $this->
addDescription(
'Pass through all users and change or delete one of their options.
45The new option is NOT validated.' );
47 $this->
addOption(
'list',
'List available user options and their default value' );
48 $this->
addOption(
'usage',
'Report all options statistics or just one if you specify it' );
51 'The value to look for. If it is a default value for the option, pass --old-is-default as well.',
54 $this->
addOption(
'old-is-default',
'If passed, --old is interpreted as a default value.' );
55 $this->
addOption(
'new',
'New value to update users with',
false,
true );
56 $this->
addOption(
'delete',
'Delete the option instead of updating' );
57 $this->
addOption(
'delete-defaults',
'Delete user_properties row matching the default' );
58 $this->
addOption(
'fromuserid',
'Start from this user ID when changing/deleting options',
60 $this->
addOption(
'touserid',
'Do not go beyond this user ID when changing/deleting options',
62 $this->
addOption(
'nowarn',
'Hides the 5 seconds warning' );
63 $this->
addOption(
'dry',
'Do not save user settings back to database' );
64 $this->
addArg(
'option name',
'Name of the option to change or provide statistics about',
false );
73 $this->listAvailableOptions();
74 } elseif ( $this->
hasOption(
'usage' ) ) {
75 $this->showUsageStats();
80 $this->updateOptions();
81 } elseif ( $this->
hasOption(
'delete' ) ) {
82 $this->deleteOptions();
83 } elseif ( $this->
hasOption(
'delete-defaults' ) ) {
84 $this->deleteDefaults();
93 private function listAvailableOptions() {
98 foreach ( $def as $opt => $value ) {
99 $maxOpt = max( $maxOpt, strlen( $opt ) );
101 foreach ( $def as $opt => $value ) {
102 $this->
output( sprintf(
"%-{$maxOpt}s: %s\n", $opt, $value ) );
109 private function showUsageStats() {
110 $option = $this->
getArg( 0 );
116 if ( $option && !array_key_exists( $option, $defaultOptions ) ) {
117 $this->
fatalError(
"Invalid user option. Use --list to see valid choices\n" );
123 $result = $dbr->newSelectQueryBuilder()
124 ->select( [
'user_id' ] )
126 ->caller( __METHOD__ )->fetchResultSet();
128 foreach ( $result as $id ) {
129 $user = User::newFromId( $id->user_id );
133 $userValue = $userOptionsLookup->
getOption( $user, $option );
134 if ( $userValue != $defaultOptions[$option] ) {
135 $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
138 foreach ( $defaultOptions as $name => $defaultValue ) {
139 $userValue = $userOptionsLookup->
getOption( $user, $name );
140 if ( $userValue != $defaultValue ) {
141 $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
147 foreach ( $ret as $optionName => $usageStats ) {
148 $this->
output(
"Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n" );
149 foreach ( $usageStats as $value => $count ) {
150 $this->
output(
" $count user(s): '$value'\n" );
159 private function updateOptions() {
161 $settingWord = $dryRun ?
'Would set' :
'Setting';
162 $option = $this->
getArg( 0 );
163 $fromIsDefault = $this->
hasOption(
'old-is-default' );
169 $fromUserId = (int)$this->
getOption(
'fromuserid', 1 ) - 1;
170 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
173 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
176The script is about to change the options
for $forUsers in the database.
177Users with option <$option> =
'$from' will be made to use
'$to'.
179Abort with control-c in the next five seconds....
187 $queryBuilderTemplate = $dbr->newSelectQueryBuilder()
189 ->leftJoin(
'user_properties',
null, [
191 'up_property' => $option,
193 ->fields( [
'user_id',
'user_name' ] )
195 ->where( [
'up_value' => $fromIsDefault ?
null : $from ] )
200 ->caller( __METHOD__ );
202 $queryBuilderTemplate->andWhere( $dbr->expr(
'user_id',
'<=', $toUserId ) );
205 if ( $tempUserConfig->isKnown() ) {
206 $queryBuilderTemplate->andWhere(
207 $tempUserConfig->getMatchCondition( $dbr,
'user_name', IExpression::NOT_LIKE )
212 $queryBuilder = clone $queryBuilderTemplate;
213 $queryBuilder->andWhere( $dbr->expr(
'user_id',
'>', $fromUserId ) );
214 $result = $queryBuilder->fetchResultSet();
215 foreach ( $result as $row ) {
216 $fromUserId = (int)$row->user_id;
218 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
219 if ( $fromIsDefault ) {
224 if ( $from != $userOptionsManager->getDefaultOption( $option, $user ) ) {
229 $this->
output(
"$settingWord {$option} for {$row->user_name} from '{$from}' to '{$to}'\n" );
231 $userOptionsManager->setOption( $user, $option, $to );
232 $userOptionsManager->saveOptions( $user );
236 }
while ( $result->numRows() );
242 private function deleteOptions() {
244 $option = $this->
getArg( 0 );
245 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
246 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
250 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
252The script is about to
delete '$option' option
for $forUsers from user_properties table.
253This action is IRREVERSIBLE.
255Abort with control-c in the next five seconds....
264 $rowsInThisBatch = -1;
265 $minUserId = $fromUserId;
266 while ( $rowsInThisBatch != 0 ) {
267 $queryBuilder = $dbr->newSelectQueryBuilder()
268 ->select(
'up_user' )
269 ->from(
'user_properties' )
270 ->where( [
'up_property' => $option, $dbr->expr(
'up_user',
'>', $minUserId ) ] );
272 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'<', $toUserId ) );
275 $queryBuilder->andWhere( [
'up_value' => $old ] );
279 ->orderBy(
'up_user', SelectQueryBuilder::SORT_ASC )
282 $userIds = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
283 if ( $userIds === [] ) {
289 $delete = $dbw->newDeleteQueryBuilder()
290 ->deleteFrom(
'user_properties' )
291 ->where( [
'up_property' => $option,
'up_user' => $userIds ] );
293 $delete->andWhere( [
'up_value' => $old ] );
295 $delete->caller( __METHOD__ )->execute();
296 $rowsInThisBatch = $dbw->affectedRows();
298 $rowsInThisBatch = count( $userIds );
302 $rowsNum += $rowsInThisBatch;
303 $minUserId = max( $userIds );
307 $this->
output(
"Done! Deleted $rowsNum rows.\n" );
309 $this->
output(
"Would delete $rowsNum rows.\n" );
313 private function deleteDefaults() {
315 $option = $this->
getArg( 0 );
316 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
317 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
319 if ( $option ===
null ) {
320 $this->
fatalError(
"Option name is required" );
325This script is about to
delete all rows in user_properties that match the current
326defaults
for the user (including conditional defaults).
327This action is IRREVERSIBLE.
329Abort with control-c in the next five seconds....
337 $queryBuilderTemplate = $dbr->newSelectQueryBuilder()
338 ->select( [
'user_id',
'user_name',
'up_value' ] )
339 ->from(
'user_properties' )
340 ->join(
'user',
null, [
'up_user = user_id' ] )
341 ->where( [
'up_property' => $option ] )
343 ->caller( __METHOD__ );
345 if ( $toUserId !==
null ) {
346 $queryBuilderTemplate->andWhere( $dbr->expr(
'up_user',
'<=', $toUserId ) );
351 $queryBuilder = clone $queryBuilderTemplate;
352 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'>', $fromUserId ) );
353 $result = $queryBuilder->fetchResultSet();
354 foreach ( $result as $row ) {
355 $fromUserId = (int)$row->user_id;
359 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
360 $userOptionsManager->setOption( $user, $option, $row->up_value );
361 $userOptionsManager->saveOptions( $user );
364 }
while ( $result->numRows() );
366 $this->
output(
"Done!\n" );
374 private function warn(
string $message ) {
379 $this->
output( $message );
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
addArg( $arg, $description, $required=true, $multi=false)
Add some args that are needed.
output( $out, $channel=null)
Throw some output to the user.
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.