Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 99 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
SendConfirmAndMigrateEmail | |
0.00% |
0 / 93 |
|
0.00% |
0 / 4 |
506 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
110 | |||
resendConfirmationEmail | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
90 | |||
report | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | $IP = getenv( 'MW_INSTALL_PATH' ); |
4 | if ( $IP === false ) { |
5 | $IP = __DIR__ . '/../../..'; |
6 | } |
7 | require_once "$IP/maintenance/Maintenance.php"; |
8 | |
9 | use MediaWiki\Extension\CentralAuth\CentralAuthServices; |
10 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
11 | use MediaWiki\Extension\CentralAuth\User\EmailableUser; |
12 | use MediaWiki\WikiMap\WikiMap; |
13 | |
14 | /** |
15 | * This maintenance script is used to resend confirmation emails to users with |
16 | * unattached accounts in the hopes that we will then be able to automatically |
17 | * attach some unattached accounts. In addition, by using an EmailableUser object |
18 | * we are able to override the contents of the email and send them to Special:MergeAccount |
19 | * instead of Special:ConfirmEmail and thus put them right into the workflow to attach |
20 | * accounts by password. |
21 | * |
22 | * THIS MUST BE RUN ON THE LOCAL WIKI SO THAT THE EMAIL IS SENT IN THE CORRECT LANGUAGE |
23 | */ |
24 | class SendConfirmAndMigrateEmail extends Maintenance { |
25 | |
26 | /** |
27 | * Whether to send to accounts with confirmed emails |
28 | * @var bool |
29 | */ |
30 | private $sendToConfirmed; |
31 | |
32 | /** |
33 | * How long to wait in between emails |
34 | * |
35 | * @var int |
36 | */ |
37 | private $sleep; |
38 | |
39 | /** |
40 | * @var bool |
41 | */ |
42 | private $dryrun; |
43 | |
44 | /** |
45 | * @var string|bool |
46 | */ |
47 | private $resume; |
48 | |
49 | /** @var float */ |
50 | protected $start; |
51 | |
52 | /** @var int */ |
53 | protected $sent; |
54 | |
55 | /** @var int */ |
56 | protected $total; |
57 | |
58 | public function __construct() { |
59 | parent::__construct(); |
60 | $this->requireExtension( 'CentralAuth' ); |
61 | $this->addDescription( "Resends the 'confirm your email address email' with a link to " . |
62 | "Special:MergeAccount" ); |
63 | $this->start = microtime( true ); |
64 | $this->sent = 0; |
65 | $this->total = 0; |
66 | |
67 | $this->addOption( 'userlist', 'List of usernames', false, true ); |
68 | $this->addOption( 'username', 'The user name to migrate', false, true, 'u' ); |
69 | $this->addOption( 'confirmed', 'Send email to confirmed accounts', false, false ); |
70 | $this->addOption( 'sleep', |
71 | 'How long to wait in between emails (default 1 second)', false, true ); |
72 | $this->addOption( 'dryrun', 'Don\'t actually send any emails', false, false ); |
73 | $this->addOption( 'resume', 'Which username to resume after', false, true ); |
74 | $this->setBatchSize( 1000 ); |
75 | } |
76 | |
77 | public function execute() { |
78 | $this->sendToConfirmed = $this->getOption( 'confirmed', false ); |
79 | $this->sleep = (int)$this->getOption( 'sleep', 1 ); |
80 | $this->dryrun = $this->hasOption( 'dryrun' ); |
81 | $this->resume = $this->getOption( 'resume', true ); |
82 | |
83 | // check to see if we are processing a single username |
84 | if ( $this->getOption( 'username', false ) !== false ) { |
85 | $username = $this->getOption( 'username' ); |
86 | |
87 | $this->resendConfirmationEmail( $username ); |
88 | |
89 | } elseif ( $this->getOption( 'userlist', false ) !== false ) { |
90 | $list = $this->getOption( 'userlist' ); |
91 | if ( !is_file( $list ) ) { |
92 | $this->output( "ERROR - File not found: $list\n" ); |
93 | exit( 1 ); |
94 | } |
95 | $file = fopen( $list, 'r' ); |
96 | if ( $file === false ) { |
97 | $this->output( "ERROR - Could not open file: $list\n" ); |
98 | exit( 1 ); |
99 | } |
100 | $databaseManager = CentralAuthServices::getDatabaseManager(); |
101 | // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition |
102 | while ( $username = fgets( $file ) ) { |
103 | // trim the \n |
104 | $username = trim( $username ); |
105 | if ( $this->resume !== true ) { |
106 | if ( $username === $this->resume ) { |
107 | $this->resume = true; |
108 | } |
109 | continue; |
110 | } |
111 | $this->resendConfirmationEmail( $username ); |
112 | |
113 | if ( $this->total % $this->mBatchSize == 0 ) { |
114 | $this->output( "Waiting for replicas to catch up ... " ); |
115 | if ( !$this->dryrun ) { |
116 | $databaseManager->waitForReplication(); |
117 | } |
118 | $this->output( "done\n" ); |
119 | } |
120 | } |
121 | fclose( $file ); |
122 | |
123 | } else { |
124 | $this->output( "ERROR - No username or list of usernames given\n" ); |
125 | exit( 1 ); |
126 | } |
127 | |
128 | $this->report(); |
129 | $this->output( "done.\n" ); |
130 | } |
131 | |
132 | /** |
133 | * @param string $username |
134 | */ |
135 | private function resendConfirmationEmail( $username ) { |
136 | $wikiID = WikiMap::getCurrentWikiId(); |
137 | |
138 | $this->total++; |
139 | $this->output( "Sending confirmation email for: '$username@$wikiID'\n" ); |
140 | |
141 | // we want to start with the local user |
142 | $user = EmailableUser::newFromName( $username ); |
143 | if ( $user === false ) { |
144 | $this->output( "ERROR: $username is an invalid username\n" ); |
145 | return; |
146 | } |
147 | $user->load(); |
148 | |
149 | if ( !$this->sendToConfirmed && $user->isEmailConfirmed() ) { |
150 | $this->output( |
151 | "ERROR: The user '$username@$wikiID' already has a confirmed email address\n" |
152 | ); |
153 | return; |
154 | } |
155 | |
156 | $central = CentralAuthUser::getInstance( $user ); |
157 | |
158 | if ( !$central->exists() ) { |
159 | $this->output( "ERROR: No global account for '$username'\n" ); |
160 | return; |
161 | } |
162 | if ( !$central->isAttached() ) { |
163 | $this->output( "ERROR: '$username@$wikiID' is not attached to the global user\n" ); |
164 | return; |
165 | } |
166 | |
167 | $unattached = $central->queryUnattached(); |
168 | if ( count( $unattached ) == 0 ) { |
169 | $this->output( "ERROR: No unattached accounts for '$username'\n" ); |
170 | return; |
171 | } |
172 | |
173 | if ( $this->dryrun ) { |
174 | $this->output( "Would have sent email\n" ); |
175 | return; |
176 | } |
177 | |
178 | if ( $user->sendConfirmAndMigrateMail() ) { |
179 | $this->output( "Sent email to $username\n" ); |
180 | $this->sent++; |
181 | sleep( $this->sleep ); |
182 | } else { |
183 | $this->output( |
184 | "ERROR: Sending confirm and migrate email failed for '$username@$wikiID'\n" |
185 | ); |
186 | } |
187 | } |
188 | |
189 | private function report() { |
190 | $delta = microtime( true ) - $this->start; |
191 | $this->output( sprintf( |
192 | "%s: %s processed %d usernames (%.1f/sec), %d (%.1f%%) emails sent\n", |
193 | WikiMap::getCurrentWikiId(), |
194 | wfTimestamp( TS_DB ), |
195 | $this->total, |
196 | $this->total / $delta, |
197 | $this->sent, |
198 | $this->total > 0 ? ( $this->sent / $this->total * 100.0 ) : 0 |
199 | ) ); |
200 | } |
201 | } |
202 | |
203 | $maintClass = SendConfirmAndMigrateEmail::class; |
204 | require_once RUN_MAINTENANCE_IF_MAIN; |