Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 82
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
NewTopicOptOutActiveUsers
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 4
272
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 1
72
 skipReason
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
42
 updatePrefs
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\DiscussionTools\Maintenance;
4
5use Maintenance;
6use MediaWiki\Extension\DiscussionTools\Hooks\HookUtils;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\User\UserFactory;
9use Wikimedia\Rdbms\IDatabase;
10
11class NewTopicOptOutActiveUsers extends Maintenance {
12
13    private IDatabase $dbw;
14    private UserFactory $userFactory;
15
16    public function __construct() {
17        parent::__construct();
18        $this->requireExtension( 'DiscussionTools' );
19        $this->addDescription( 'Opt out active users from the new topic tool' );
20        $this->addOption( 'dry-run', 'Output information, do not save changes' );
21        $this->addOption( 'save', 'Save the changes to the database' );
22        $this->setBatchSize( 100 );
23    }
24
25    public function execute() {
26        if ( $this->hasOption( 'dry-run' ) ) {
27            $save = false;
28            $this->output( "Dry run:\n" );
29        } elseif ( $this->hasOption( 'save' ) ) {
30            $save = true;
31            $this->output( "CHANGING PREFERENCES!\n" );
32            $this->countDown( 5 );
33        } else {
34            $this->error( "Please provide '--dry-run' or '--save' option" );
35            return;
36        }
37
38        $this->dbw = $this->getDB( DB_PRIMARY );
39        $this->userFactory = MediaWikiServices::getInstance()->getUserFactory();
40
41        $userRows = $this->dbw->newSelectQueryBuilder()
42            ->caller( __METHOD__ )
43            ->table( 'querycachetwo' )
44            ->where( [
45                'qcc_type' => 'activeusers',
46                'qcc_namespace' => NS_USER,
47            ] )
48            ->join( 'user', null, 'qcc_title=user_name' )
49            ->where( [ 'user_editcount >= 100' ] )
50            ->fields( [ 'user_id', 'user_name' ] )
51            ->fetchResultSet();
52
53        $count = count( $userRows );
54        $countUpdated = 0;
55        $this->output( "Found $count active users with enough edits\n" );
56
57        foreach ( $userRows as $i => $row ) {
58            $skipReason = $this->skipReason( $row->user_id );
59            if ( $skipReason ) {
60                $this->output( "Won't update '$row->user_name' because: $skipReason\n" );
61            } else {
62                $this->output( "Will update '$row->user_name'\n" );
63                $countUpdated++;
64                if ( $save ) {
65                    $this->updatePrefs( $row->user_id );
66                    if ( $countUpdated % $this->getBatchSize() === 0 ) {
67                        $this->waitForReplication();
68                    }
69                }
70            }
71        }
72
73        if ( $save ) {
74            $this->output( "Updated $countUpdated out of $count users\n" );
75        } else {
76            $this->output( "Would update $countUpdated out of $count users\n" );
77        }
78    }
79
80    private function skipReason( int $userId ): ?string {
81        // We can't use UserOptionsLookup here, because we're not interested in the default options,
82        // but only in the options actually stored in the database.
83
84        // We're not looking at global preferences, because if the user has set them, then they will
85        // override our local preferences anyway.
86
87        // Check that the user has not already set their preference for new topic tool to any value
88        $foundRow = $this->dbw->newSelectQueryBuilder()
89            ->caller( __METHOD__ )
90            ->table( 'user_properties' )
91            ->where( [ 'up_user' => $userId, 'up_property' => 'discussiontools-' . HookUtils::NEWTOPICTOOL ] )
92            ->field( '1' )
93            ->fetchField();
94        if ( $foundRow ) {
95            return HookUtils::NEWTOPICTOOL;
96        }
97
98        // Check that the user has not already opted into the beta feature
99        $foundRow = $this->dbw->newSelectQueryBuilder()
100            ->caller( __METHOD__ )
101            ->table( 'user_properties' )
102            ->where( [
103                'up_user' => $userId,
104                'up_property' => 'discussiontools-betaenable',
105                'up_value' => 1,
106            ] )
107            ->field( '1' )
108            ->fetchField();
109        if ( $foundRow ) {
110            return 'betaenable';
111        }
112
113        // Skip accounts that shouldn't have non-default preferences
114        $user = $this->userFactory->newFromId( $userId );
115        if ( $user->isSystemUser() ) {
116            return 'system';
117        }
118        if ( $user->isBot() ) {
119            return 'bot';
120        }
121        if ( $user->isTemp() ) {
122            return 'temp';
123        }
124
125        return null;
126    }
127
128    private function updatePrefs( int $userId ): void {
129        // We can't use UserOptionsManager here, because we want to store the preference
130        // in the database even if it's identical to the current default
131        // (this script is only used when we're about to change the default).
132        $this->dbw->newInsertQueryBuilder()
133            ->table( 'user_properties' )
134            ->row( [
135                'up_user' => $userId,
136                'up_property' => 'discussiontools-' . HookUtils::NEWTOPICTOOL,
137                'up_value' => 0,
138            ] )
139            ->caller( __METHOD__ )
140            ->execute();
141    }
142
143}
144
145$maintClass = NewTopicOptOutActiveUsers::class;