29 parent::__construct();
31 $this->
addDescription(
'Pass through all users and change or delete one of their options.
32The new option is NOT validated.' );
34 $this->
addOption(
'list',
'List available user options and their default value' );
35 $this->
addOption(
'usage',
'Report all options statistics or just one if you specify it' );
38 'The value to look for. If it is a default value for the option, pass --old-is-default as well.',
39 false,
true,
false,
true
41 $this->
addOption(
'old-is-default',
'If passed, --old is interpreted as a default value.' );
42 $this->
addOption(
'new',
'New value to update users with',
false,
true );
43 $this->
addOption(
'delete',
'Delete the option instead of updating' );
44 $this->
addOption(
'delete-defaults',
'Delete user_properties row matching the default' );
45 $this->
addOption(
'fromuserid',
'Start from this user ID when changing/deleting options',
47 $this->
addOption(
'touserid',
'Do not go beyond this user ID when changing/deleting options',
49 $this->
addOption(
'nowarn',
'Hides the 5 seconds warning' );
50 $this->
addOption(
'dry',
'Do not save user settings back to database' );
51 $this->
addArg(
'option name',
'Name of the option to change or provide statistics about',
false );
60 $this->listAvailableOptions();
61 } elseif ( $this->
hasOption(
'usage' ) ) {
62 $this->showUsageStats();
67 $this->updateOptions();
68 } elseif ( $this->
hasOption(
'delete' ) ) {
69 $this->deleteOptions();
70 } elseif ( $this->
hasOption(
'delete-defaults' ) ) {
71 $this->deleteDefaults();
80 private function listAvailableOptions() {
85 foreach ( $def as $opt => $value ) {
86 $maxOpt = max( $maxOpt, strlen( $opt ) );
88 foreach ( $def as $opt => $value ) {
89 $this->
output( sprintf(
"%-{$maxOpt}s: %s\n", $opt, $value ) );
96 private function showUsageStats() {
97 $option = $this->
getArg( 0 );
103 if ( $option && !array_key_exists( $option, $defaultOptions ) ) {
104 $this->
fatalError(
"Invalid user option. Use --list to see valid choices\n" );
110 $result = $dbr->newSelectQueryBuilder()
111 ->select( [
'user_id' ] )
113 ->caller( __METHOD__ )->fetchResultSet();
115 foreach ( $result as $id ) {
116 $user = User::newFromId( $id->user_id );
120 $userValue = $userOptionsLookup->
getOption( $user, $option );
121 if ( $userValue != $defaultOptions[$option] ) {
122 $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
125 foreach ( $defaultOptions as $name => $defaultValue ) {
126 $userValue = $userOptionsLookup->
getOption( $user, $name );
127 if ( $userValue != $defaultValue ) {
128 $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
134 foreach ( $ret as $optionName => $usageStats ) {
135 $this->
output(
"Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n" );
136 foreach ( $usageStats as $value => $count ) {
137 $this->
output(
" $count user(s): '$value'\n" );
146 private function updateOptions() {
148 $settingWord = $dryRun ?
'Would set' :
'Setting';
149 $option = $this->
getArg( 0 );
150 $fromIsDefault = $this->
hasOption(
'old-is-default' );
156 $fromUserId = (int)$this->
getOption(
'fromuserid', 1 ) - 1;
157 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
158 $fromAsText = implode(
'|', $from );
161 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
164The script is about to change the options
for $forUsers in the database.
165Users with option <$option> =
'$fromAsText' will be made to use
'$to'.
167Abort with control-c in the next five seconds....
175 $queryBuilderTemplate = $dbr->newSelectQueryBuilder()
177 ->leftJoin(
'user_properties',
null, [
179 'up_property' => $option,
181 ->fields( [
'user_id',
'user_name',
'up_value' ] )
183 ->where( [
'up_value' => $fromIsDefault ?
null : $from ] )
188 ->caller( __METHOD__ );
190 $queryBuilderTemplate->andWhere( $dbr->expr(
'user_id',
'<=', $toUserId ) );
193 if ( $tempUserConfig->isKnown() ) {
194 $queryBuilderTemplate->andWhere(
195 $tempUserConfig->getMatchCondition( $dbr,
'user_name', IExpression::NOT_LIKE )
200 $queryBuilder = clone $queryBuilderTemplate;
201 $queryBuilder->andWhere( $dbr->expr(
'user_id',
'>', $fromUserId ) );
202 $result = $queryBuilder->fetchResultSet();
203 foreach ( $result as $row ) {
204 $fromUserId = (int)$row->user_id;
205 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
207 if ( $fromIsDefault ) {
212 $oldOptionMatchingDefault =
null;
213 $oldOptionIsDefault =
true;
214 foreach ( $from as $oldOption ) {
215 $oldOptionIsDefault = $oldOption == $userOptionsManager->getDefaultOption( $option, $user );
216 if ( $oldOptionIsDefault ) {
217 $oldOptionMatchingDefault = $oldOption;
221 if ( !$oldOptionIsDefault ) {
223 "Skipping $option for $row->user_name as the default value for that user is not " .
224 "specified in --from\n"
229 $fromForThisUser = $oldOptionMatchingDefault ?? $fromAsText;
231 $fromForThisUser = $row->up_value;
234 $this->
output(
"$settingWord $option for $row->user_name from '$fromForThisUser' to '$to'\n" );
236 $userOptionsManager->setOption( $user, $option, $to );
237 $userOptionsManager->saveOptions( $user );
241 }
while ( $result->numRows() );
247 private function deleteOptions() {
249 $option = $this->
getArg( 0 );
250 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
251 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
255 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
257The script is about to
delete '$option' option
for $forUsers from user_properties table.
258This action is IRREVERSIBLE.
260Abort with control-c in the next five seconds....
269 $rowsInThisBatch = -1;
270 $minUserId = $fromUserId;
271 while ( $rowsInThisBatch != 0 ) {
272 $queryBuilder = $dbr->newSelectQueryBuilder()
273 ->select(
'up_user' )
274 ->from(
'user_properties' )
275 ->where( [
'up_property' => $option, $dbr->expr(
'up_user',
'>', $minUserId ) ] );
277 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'<', $toUserId ) );
280 $queryBuilder->andWhere( [
'up_value' => $old ] );
284 ->orderBy(
'up_user', SelectQueryBuilder::SORT_ASC )
287 $userIds = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
288 if ( $userIds === [] ) {
294 $delete = $dbw->newDeleteQueryBuilder()
295 ->deleteFrom(
'user_properties' )
296 ->where( [
'up_property' => $option,
'up_user' => $userIds ] );
298 $delete->andWhere( [
'up_value' => $old ] );
300 $delete->caller( __METHOD__ )->execute();
301 $rowsInThisBatch = $dbw->affectedRows();
303 $rowsInThisBatch = count( $userIds );
307 $rowsNum += $rowsInThisBatch;
308 $minUserId = max( $userIds );
312 $this->
output(
"Done! Deleted $rowsNum rows.\n" );
314 $this->
output(
"Would delete $rowsNum rows.\n" );
318 private function deleteDefaults() {
320 $option = $this->
getArg( 0 );
321 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
322 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
324 if ( $option ===
null ) {
325 $this->
fatalError(
"Option name is required" );
329 $this->
fatalError(
"--delete-defaults does not support a dry run." );
333This script is about to
delete all rows in user_properties that match the current
334defaults
for the user (including conditional defaults).
335This action is IRREVERSIBLE.
337Abort with control-c in the next five seconds....
343 $queryBuilderTemplate = $dbr->newSelectQueryBuilder()
344 ->select( [
'user_id',
'user_name',
'up_value' ] )
345 ->from(
'user_properties' )
346 ->join(
'user',
null, [
'up_user = user_id' ] )
347 ->where( [
'up_property' => $option ] )
349 ->caller( __METHOD__ );
351 if ( $toUserId !==
null ) {
352 $queryBuilderTemplate->andWhere( $dbr->expr(
'up_user',
'<=', $toUserId ) );
357 $queryBuilder = clone $queryBuilderTemplate;
358 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'>', $fromUserId ) );
359 $result = $queryBuilder->fetchResultSet();
360 foreach ( $result as $row ) {
361 $fromUserId = (int)$row->user_id;
365 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
366 $userOptionsManager->setOption( $user, $option, $row->up_value );
367 $userOptionsManager->saveOptions( $user );
370 }
while ( $result->numRows() );
372 $this->
output(
"Done!\n" );
378 private function warn(
string $message ) {
383 $this->
output( $message );