43 parent::__construct();
45 $this->
addDescription(
'Pass through all users and change or delete one of their options.
46The new option is NOT validated.' );
48 $this->
addOption(
'list',
'List available user options and their default value' );
49 $this->
addOption(
'usage',
'Report all options statistics or just one if you specify it' );
52 'The value to look for. If it is a default value for the option, pass --old-is-default as well.',
53 false,
true,
false,
true
55 $this->
addOption(
'old-is-default',
'If passed, --old is interpreted as a default value.' );
56 $this->
addOption(
'new',
'New value to update users with',
false,
true );
57 $this->
addOption(
'delete',
'Delete the option instead of updating' );
58 $this->
addOption(
'delete-defaults',
'Delete user_properties row matching the default' );
59 $this->
addOption(
'fromuserid',
'Start from this user ID when changing/deleting options',
61 $this->
addOption(
'touserid',
'Do not go beyond this user ID when changing/deleting options',
63 $this->
addOption(
'nowarn',
'Hides the 5 seconds warning' );
64 $this->
addOption(
'dry',
'Do not save user settings back to database' );
65 $this->
addArg(
'option name',
'Name of the option to change or provide statistics about',
false );
74 $this->listAvailableOptions();
75 } elseif ( $this->
hasOption(
'usage' ) ) {
76 $this->showUsageStats();
81 $this->updateOptions();
82 } elseif ( $this->
hasOption(
'delete' ) ) {
83 $this->deleteOptions();
84 } elseif ( $this->
hasOption(
'delete-defaults' ) ) {
85 $this->deleteDefaults();
94 private function listAvailableOptions() {
99 foreach ( $def as $opt => $value ) {
100 $maxOpt = max( $maxOpt, strlen( $opt ) );
102 foreach ( $def as $opt => $value ) {
103 $this->
output( sprintf(
"%-{$maxOpt}s: %s\n", $opt, $value ) );
110 private function showUsageStats() {
111 $option = $this->
getArg( 0 );
117 if ( $option && !array_key_exists( $option, $defaultOptions ) ) {
118 $this->
fatalError(
"Invalid user option. Use --list to see valid choices\n" );
124 $result = $dbr->newSelectQueryBuilder()
125 ->select( [
'user_id' ] )
127 ->caller( __METHOD__ )->fetchResultSet();
129 foreach ( $result as $id ) {
130 $user = User::newFromId( $id->user_id );
134 $userValue = $userOptionsLookup->
getOption( $user, $option );
135 if ( $userValue != $defaultOptions[$option] ) {
136 $ret[$option][$userValue] = ( $ret[$option][$userValue] ?? 0 ) + 1;
139 foreach ( $defaultOptions as $name => $defaultValue ) {
140 $userValue = $userOptionsLookup->
getOption( $user, $name );
141 if ( $userValue != $defaultValue ) {
142 $ret[$name][$userValue] = ( $ret[$name][$userValue] ?? 0 ) + 1;
148 foreach ( $ret as $optionName => $usageStats ) {
149 $this->
output(
"Usage for <$optionName> (default: '{$defaultOptions[$optionName]}'):\n" );
150 foreach ( $usageStats as $value => $count ) {
151 $this->
output(
" $count user(s): '$value'\n" );
160 private function updateOptions() {
162 $settingWord = $dryRun ?
'Would set' :
'Setting';
163 $option = $this->
getArg( 0 );
164 $fromIsDefault = $this->
hasOption(
'old-is-default' );
170 $fromUserId = (int)$this->
getOption(
'fromuserid', 1 ) - 1;
171 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
172 $fromAsText = implode(
'|', $from );
175 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
178The script is about to change the options
for $forUsers in the database.
179Users with option <$option> =
'$fromAsText' will be made to use
'$to'.
181Abort with control-c in the next five seconds....
189 $queryBuilderTemplate = $dbr->newSelectQueryBuilder()
191 ->leftJoin(
'user_properties',
null, [
193 'up_property' => $option,
195 ->fields( [
'user_id',
'user_name',
'up_value' ] )
197 ->where( [
'up_value' => $fromIsDefault ?
null : $from ] )
202 ->caller( __METHOD__ );
204 $queryBuilderTemplate->andWhere( $dbr->expr(
'user_id',
'<=', $toUserId ) );
207 if ( $tempUserConfig->isKnown() ) {
208 $queryBuilderTemplate->andWhere(
209 $tempUserConfig->getMatchCondition( $dbr,
'user_name', IExpression::NOT_LIKE )
214 $queryBuilder = clone $queryBuilderTemplate;
215 $queryBuilder->andWhere( $dbr->expr(
'user_id',
'>', $fromUserId ) );
216 $result = $queryBuilder->fetchResultSet();
217 foreach ( $result as $row ) {
218 $fromUserId = (int)$row->user_id;
219 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
221 if ( $fromIsDefault ) {
226 $oldOptionMatchingDefault =
null;
227 $oldOptionIsDefault =
true;
228 foreach ( $from as $oldOption ) {
229 $oldOptionIsDefault = $oldOption == $userOptionsManager->getDefaultOption( $option, $user );
230 if ( $oldOptionIsDefault ) {
231 $oldOptionMatchingDefault = $oldOption;
235 if ( !$oldOptionIsDefault ) {
237 "Skipping $option for $row->user_name as the default value for that user is not " .
238 "specified in --from\n"
243 $fromForThisUser = $oldOptionMatchingDefault ?? $fromAsText;
245 $fromForThisUser = $row->up_value;
248 $this->
output(
"$settingWord $option for $row->user_name from '$fromForThisUser' to '$to'\n" );
250 $userOptionsManager->setOption( $user, $option, $to );
251 $userOptionsManager->saveOptions( $user );
255 }
while ( $result->numRows() );
261 private function deleteOptions() {
263 $option = $this->
getArg( 0 );
264 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
265 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
269 $forUsers = ( $fromUserId || $toUserId ) ?
"some users (ID $fromUserId-$toUserId)" :
'ALL USERS';
271The script is about to
delete '$option' option
for $forUsers from user_properties table.
272This action is IRREVERSIBLE.
274Abort with control-c in the next five seconds....
283 $rowsInThisBatch = -1;
284 $minUserId = $fromUserId;
285 while ( $rowsInThisBatch != 0 ) {
286 $queryBuilder = $dbr->newSelectQueryBuilder()
287 ->select(
'up_user' )
288 ->from(
'user_properties' )
289 ->where( [
'up_property' => $option, $dbr->expr(
'up_user',
'>', $minUserId ) ] );
291 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'<', $toUserId ) );
294 $queryBuilder->andWhere( [
'up_value' => $old ] );
298 ->orderBy(
'up_user', SelectQueryBuilder::SORT_ASC )
301 $userIds = $queryBuilder->caller( __METHOD__ )->fetchFieldValues();
302 if ( $userIds === [] ) {
308 $delete = $dbw->newDeleteQueryBuilder()
309 ->deleteFrom(
'user_properties' )
310 ->where( [
'up_property' => $option,
'up_user' => $userIds ] );
312 $delete->andWhere( [
'up_value' => $old ] );
314 $delete->caller( __METHOD__ )->execute();
315 $rowsInThisBatch = $dbw->affectedRows();
317 $rowsInThisBatch = count( $userIds );
321 $rowsNum += $rowsInThisBatch;
322 $minUserId = max( $userIds );
326 $this->
output(
"Done! Deleted $rowsNum rows.\n" );
328 $this->
output(
"Would delete $rowsNum rows.\n" );
332 private function deleteDefaults() {
334 $option = $this->
getArg( 0 );
335 $fromUserId = (int)$this->
getOption(
'fromuserid', 0 );
336 $toUserId = (int)$this->
getOption(
'touserid', 0 ) ?:
null;
338 if ( $option ===
null ) {
339 $this->
fatalError(
"Option name is required" );
343 $this->
fatalError(
"--delete-defaults does not support a dry run." );
347This script is about to
delete all rows in user_properties that match the current
348defaults
for the user (including conditional defaults).
349This action is IRREVERSIBLE.
351Abort with control-c in the next five seconds....
357 $queryBuilderTemplate = $dbr->newSelectQueryBuilder()
358 ->select( [
'user_id',
'user_name',
'up_value' ] )
359 ->from(
'user_properties' )
360 ->join(
'user',
null, [
'up_user = user_id' ] )
361 ->where( [
'up_property' => $option ] )
363 ->caller( __METHOD__ );
365 if ( $toUserId !==
null ) {
366 $queryBuilderTemplate->andWhere( $dbr->expr(
'up_user',
'<=', $toUserId ) );
371 $queryBuilder = clone $queryBuilderTemplate;
372 $queryBuilder->andWhere( $dbr->expr(
'up_user',
'>', $fromUserId ) );
373 $result = $queryBuilder->fetchResultSet();
374 foreach ( $result as $row ) {
375 $fromUserId = (int)$row->user_id;
379 $user = UserIdentityValue::newRegistered( $row->user_id, $row->user_name );
380 $userOptionsManager->setOption( $user, $option, $row->up_value );
381 $userOptionsManager->saveOptions( $user );
384 }
while ( $result->numRows() );
386 $this->
output(
"Done!\n" );
392 private function warn(
string $message ) {
397 $this->
output( $message );