Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 896 |
|
0.00% |
0 / 16 |
CRAP | |
0.00% |
0 / 1 |
VoterEligibilityPage | |
0.00% |
0 / 896 |
|
0.00% |
0 / 16 |
15500 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
156 | |||
saveProperties | |
0.00% |
0 / 62 |
|
0.00% |
0 / 1 |
132 | |||
fetchList | |
0.00% |
0 / 45 |
|
0.00% |
0 / 1 |
56 | |||
saveList | |
0.00% |
0 / 93 |
|
0.00% |
0 / 1 |
240 | |||
executeConfig | |
0.00% |
0 / 360 |
|
0.00% |
0 / 1 |
380 | |||
parseDate | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
checkRequired | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
checkMin | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
checkCentralBlockThreshold | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
checkEditsBeforeCount | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
checkEditsBetweenCount | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
checkListEditsEndDate | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
processConfig | |
0.00% |
0 / 98 |
|
0.00% |
0 / 1 |
506 | |||
executeEdit | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
72 | |||
executeClear | |
0.00% |
0 / 101 |
|
0.00% |
0 / 1 |
110 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\Pages; |
4 | |
5 | use DateTime; |
6 | use DateTimeZone; |
7 | use Exception; |
8 | use FormatJson; |
9 | use HTMLForm; |
10 | use MediaWiki\Deferred\DeferredUpdates; |
11 | use MediaWiki\Extension\SecurePoll\Context; |
12 | use MediaWiki\Extension\SecurePoll\Exceptions\InvalidDataException; |
13 | use MediaWiki\Extension\SecurePoll\Jobs\PopulateVoterListJob; |
14 | use MediaWiki\Extension\SecurePoll\SecurePollContentHandler; |
15 | use MediaWiki\Extension\SecurePoll\SpecialSecurePoll; |
16 | use MediaWiki\Linker\Linker; |
17 | use MediaWiki\Linker\LinkRenderer; |
18 | use MediaWiki\Page\WikiPageFactory; |
19 | use MediaWiki\SpecialPage\SpecialPage; |
20 | use MediaWiki\Status\Status; |
21 | use MediaWiki\Title\TitleFactory; |
22 | use MediaWiki\User\UserGroupManager; |
23 | use MediaWiki\WikiMap\WikiMap; |
24 | use Message; |
25 | use MWExceptionHandler; |
26 | use Wikimedia\Rdbms\DBConnectionError; |
27 | use Wikimedia\Rdbms\ILoadBalancer; |
28 | use Wikimedia\Rdbms\LBFactory; |
29 | use Wikimedia\RequestTimeout\TimeoutException; |
30 | |
31 | /** |
32 | * Special:SecurePoll subpage for managing the voter list for a poll |
33 | */ |
34 | class VoterEligibilityPage extends ActionPage { |
35 | /** @var string[] */ |
36 | private static $lists = [ |
37 | 'voter' => 'need-list', |
38 | 'include' => 'include-list', |
39 | 'exclude' => 'exclude-list', |
40 | ]; |
41 | |
42 | /** @var LBFactory */ |
43 | private $lbFactory; |
44 | |
45 | /** @var LinkRenderer */ |
46 | private $linkRenderer; |
47 | |
48 | /** @var TitleFactory */ |
49 | private $titleFactory; |
50 | |
51 | /** @var UserGroupManager */ |
52 | private $userGroupManager; |
53 | |
54 | /** @var WikiPageFactory */ |
55 | private $wikiPageFactory; |
56 | |
57 | /** |
58 | * @param SpecialSecurePoll $specialPage |
59 | * @param LBFactory $lbFactory |
60 | * @param LinkRenderer $linkRenderer |
61 | * @param TitleFactory $titleFactory |
62 | * @param UserGroupManager $userGroupManager |
63 | * @param WikiPageFactory $wikiPageFactory |
64 | */ |
65 | public function __construct( |
66 | SpecialSecurePoll $specialPage, |
67 | LBFactory $lbFactory, |
68 | LinkRenderer $linkRenderer, |
69 | TitleFactory $titleFactory, |
70 | UserGroupManager $userGroupManager, |
71 | WikiPageFactory $wikiPageFactory |
72 | ) { |
73 | parent::__construct( $specialPage ); |
74 | $this->lbFactory = $lbFactory; |
75 | $this->linkRenderer = $linkRenderer; |
76 | $this->titleFactory = $titleFactory; |
77 | $this->userGroupManager = $userGroupManager; |
78 | $this->wikiPageFactory = $wikiPageFactory; |
79 | } |
80 | |
81 | /** |
82 | * Execute the subpage. |
83 | * @param array $params Array of subpage parameters. |
84 | */ |
85 | public function execute( $params ) { |
86 | $out = $this->specialPage->getOutput(); |
87 | |
88 | if ( !count( $params ) ) { |
89 | $out->addWikiMsg( 'securepoll-too-few-params' ); |
90 | |
91 | return; |
92 | } |
93 | |
94 | $electionId = intval( $params[0] ); |
95 | $this->election = $this->context->getElection( $electionId ); |
96 | if ( !$this->election ) { |
97 | $out->addWikiMsg( 'securepoll-invalid-election', $electionId ); |
98 | |
99 | return; |
100 | } |
101 | if ( !$this->election->isAdmin( $this->specialPage->getUser() ) ) { |
102 | $out->addWikiMsg( 'securepoll-need-admin' ); |
103 | |
104 | return; |
105 | } |
106 | |
107 | $jumpUrl = $this->election->getProperty( 'jump-url' ); |
108 | if ( $jumpUrl ) { |
109 | $jumpId = $this->election->getProperty( 'jump-id' ); |
110 | if ( !$jumpId ) { |
111 | throw new InvalidDataException( 'Configuration error: no jump-id' ); |
112 | } |
113 | $jumpUrl .= "/votereligibility/$jumpId"; |
114 | if ( count( $params ) > 1 ) { |
115 | $jumpUrl .= '/' . implode( '/', array_slice( $params, 1 ) ); |
116 | } |
117 | |
118 | $wiki = $this->election->getProperty( 'main-wiki' ); |
119 | if ( $wiki ) { |
120 | $wiki = WikiMap::getWikiName( $wiki ); |
121 | } else { |
122 | $wiki = $this->msg( 'securepoll-votereligibility-redirect-otherwiki' )->text(); |
123 | } |
124 | |
125 | $out->addWikiMsg( |
126 | 'securepoll-votereligibility-redirect', |
127 | Message::rawParam( Linker::makeExternalLink( $jumpUrl, $wiki ) ) |
128 | ); |
129 | |
130 | return; |
131 | } |
132 | |
133 | if ( count( $params ) >= 3 ) { |
134 | $operation = $params[1]; |
135 | } else { |
136 | $operation = 'config'; |
137 | } |
138 | |
139 | switch ( $operation ) { |
140 | case 'edit': |
141 | $this->executeEdit( $params[2] ); |
142 | break; |
143 | case 'clear': |
144 | $this->executeClear( $params[2] ); |
145 | break; |
146 | default: |
147 | $this->executeConfig(); |
148 | break; |
149 | } |
150 | } |
151 | |
152 | private function saveProperties( $properties, $delete, $comment ) { |
153 | $localWiki = WikiMap::getCurrentWikiId(); |
154 | $wikis = $this->election->getProperty( 'wikis' ); |
155 | if ( $wikis ) { |
156 | $wikis = explode( "\n", $wikis ); |
157 | $i = array_search( $localWiki, $wikis ); |
158 | if ( $i !== false ) { |
159 | unset( $wikis[$i] ); |
160 | } |
161 | array_unshift( $wikis, $localWiki ); |
162 | } else { |
163 | $wikis = [ $localWiki ]; |
164 | } |
165 | |
166 | $dbw = null; |
167 | foreach ( $wikis as $dbname ) { |
168 | |
169 | if ( $dbname === $localWiki ) { |
170 | $lb = $this->lbFactory->getMainLB(); |
171 | $dbw = $lb->getConnection( ILoadBalancer::DB_PRIMARY, |
172 | [], false, ILoadBalancer::CONN_TRX_AUTOCOMMIT ); |
173 | } else { |
174 | unset( $dbw ); |
175 | $lb = $this->lbFactory->getMainLB( $dbname ); |
176 | $dbw = $lb->getConnection( ILoadBalancer::DB_PRIMARY, |
177 | [], $dbname, ILoadBalancer::CONN_TRX_AUTOCOMMIT ); |
178 | |
179 | try { |
180 | // Connect to the DB and check if the LB is in read-only mode |
181 | if ( $dbw->isReadOnly() ) { |
182 | continue; |
183 | } |
184 | } catch ( DBConnectionError $e ) { |
185 | MWExceptionHandler::logException( $e ); |
186 | continue; |
187 | } |
188 | } |
189 | |
190 | $dbw->startAtomic( __METHOD__ ); |
191 | |
192 | $id = $dbw->newSelectQueryBuilder() |
193 | ->select( 'el_entity' ) |
194 | ->from( 'securepoll_elections' ) |
195 | ->where( [ 'el_title' => $this->election->title ] ) |
196 | ->caller( __METHOD__ ) |
197 | ->fetchField(); |
198 | if ( $id ) { |
199 | $ins = []; |
200 | foreach ( $properties as $key => $value ) { |
201 | $ins[] = [ |
202 | 'pr_entity' => $id, |
203 | 'pr_key' => $key, |
204 | 'pr_value' => $value, |
205 | ]; |
206 | } |
207 | |
208 | $dbw->newDeleteQueryBuilder() |
209 | ->deleteFrom( 'securepoll_properties' ) |
210 | ->where( [ |
211 | 'pr_entity' => $id, |
212 | 'pr_key' => array_merge( $delete, array_keys( $properties ) ), |
213 | ] ) |
214 | ->caller( __METHOD__ ) |
215 | ->execute(); |
216 | |
217 | if ( $ins ) { |
218 | $dbw->newInsertQueryBuilder() |
219 | ->insertInto( 'securepoll_properties' ) |
220 | ->rows( $ins ) |
221 | ->caller( __METHOD__ ) |
222 | ->execute(); |
223 | } |
224 | } |
225 | |
226 | $dbw->endAtomic( __METHOD__ ); |
227 | } |
228 | |
229 | // Record this election to the SecurePoll namespace, if so configured. |
230 | if ( $this->specialPage->getConfig()->get( 'SecurePollUseNamespace' ) ) { |
231 | // Create a new context to bypass caching |
232 | $context = new Context; |
233 | $election = $context->getElection( $this->election->getId() ); |
234 | |
235 | [ $title, $content ] = SecurePollContentHandler::makeContentFromElection( |
236 | $election |
237 | ); |
238 | $wp = $this->wikiPageFactory->newFromTitle( $title ); |
239 | $wp->doUserEditContent( $content, $this->specialPage->getUser(), $comment ); |
240 | } |
241 | } |
242 | |
243 | private function fetchList( $property, $db = DB_REPLICA ) { |
244 | $wikis = $this->election->getProperty( 'wikis' ); |
245 | $localWiki = WikiMap::getCurrentWikiId(); |
246 | if ( $wikis ) { |
247 | $wikis = explode( "\n", $wikis ); |
248 | if ( !in_array( $localWiki, $wikis ) ) { |
249 | $wikis[] = $localWiki; |
250 | } |
251 | } else { |
252 | $wikis = [ $localWiki ]; |
253 | } |
254 | |
255 | $names = []; |
256 | foreach ( $wikis as $dbname ) { |
257 | $lb = $this->lbFactory->getMainLB( $dbname ); |
258 | $dbr = $lb->getConnection( $db, [], $dbname ); |
259 | |
260 | $id = $dbr->newSelectQueryBuilder() |
261 | ->select( 'el_entity' ) |
262 | ->from( 'securepoll_elections' ) |
263 | ->where( [ |
264 | 'el_title' => $this->election->title |
265 | ] ) |
266 | ->caller( __METHOD__ ) |
267 | ->fetchField(); |
268 | if ( !$id ) { |
269 | // WTF? |
270 | continue; |
271 | } |
272 | $list = $dbr->newSelectQueryBuilder() |
273 | ->select( 'pr_value' ) |
274 | ->from( 'securepoll_properties' ) |
275 | ->where( [ |
276 | 'pr_entity' => $id, |
277 | 'pr_key' => $property, |
278 | ] ) |
279 | ->caller( __METHOD__ ) |
280 | ->fetchField(); |
281 | if ( !$list ) { |
282 | continue; |
283 | } |
284 | |
285 | $res = $dbr->newSelectQueryBuilder() |
286 | ->select( 'user_name' ) |
287 | ->from( 'securepoll_lists' ) |
288 | ->join( 'user', null, 'user_id=li_member' ) |
289 | ->where( [ |
290 | 'li_name' => $list, |
291 | ] ) |
292 | ->caller( __METHOD__ ) |
293 | ->fetchResultSet(); |
294 | foreach ( $res as $row ) { |
295 | $names[] = str_replace( '_', ' ', $row->user_name ) . "@$dbname"; |
296 | } |
297 | } |
298 | sort( $names ); |
299 | |
300 | return $names; |
301 | } |
302 | |
303 | private function saveList( $property, $names, $comment ) { |
304 | $localWiki = WikiMap::getCurrentWikiId(); |
305 | |
306 | $wikiNames = [ '*' => [] ]; |
307 | foreach ( explode( "\n", $names ) as $name ) { |
308 | $name = trim( $name ); |
309 | $i = strrpos( $name, '@' ); |
310 | if ( $i === false ) { |
311 | $wiki = '*'; |
312 | } else { |
313 | $wiki = trim( substr( $name, $i + 1 ) ); |
314 | $name = trim( substr( $name, 0, $i ) ); |
315 | } |
316 | if ( $wiki !== '' && $name !== '' ) { |
317 | $wikiNames[$wiki][] = str_replace( '_', ' ', $name ); |
318 | } |
319 | } |
320 | |
321 | $list = "{$this->election->getId()}/list/$property"; |
322 | |
323 | $wikis = $this->election->getProperty( 'wikis' ); |
324 | if ( $wikis ) { |
325 | $wikis = explode( "\n", $wikis ); |
326 | $i = array_search( $localWiki, $wikis ); |
327 | if ( $i !== false ) { |
328 | unset( $wikis[$i] ); |
329 | } |
330 | array_unshift( $wikis, $localWiki ); |
331 | } else { |
332 | $wikis = [ $localWiki ]; |
333 | } |
334 | |
335 | $dbw = null; |
336 | foreach ( $wikis as $dbname ) { |
337 | if ( $dbname === $localWiki ) { |
338 | $lb = $this->lbFactory->getMainLB(); |
339 | $dbw = $lb->getConnection( ILoadBalancer::DB_PRIMARY, |
340 | [], false, ILoadBalancer::CONN_TRX_AUTOCOMMIT ); |
341 | } else { |
342 | unset( $dbw ); |
343 | $lb = $this->lbFactory->getMainLB( $dbname ); |
344 | $dbw = $lb->getConnection( ILoadBalancer::DB_PRIMARY, |
345 | [], $dbname, ILoadBalancer::CONN_TRX_AUTOCOMMIT ); |
346 | try { |
347 | // Connect to the DB and check if the LB is in read-only mode |
348 | if ( $dbw->isReadOnly() ) { |
349 | continue; |
350 | } |
351 | } catch ( DBConnectionError $e ) { |
352 | MWExceptionHandler::logException( $e ); |
353 | continue; |
354 | } |
355 | } |
356 | |
357 | $dbw->startAtomic( __METHOD__ ); |
358 | |
359 | $id = $dbw->newSelectQueryBuilder() |
360 | ->select( 'el_entity' ) |
361 | ->from( 'securepoll_elections' ) |
362 | ->where( [ 'el_title' => $this->election->title ] ) |
363 | ->caller( __METHOD__ ) |
364 | ->fetchField(); |
365 | if ( $id ) { |
366 | $dbw->newReplaceQueryBuilder() |
367 | ->replaceInto( 'securepoll_properties' ) |
368 | ->uniqueIndexFields( [ 'pr_entity', 'pr_key' ] ) |
369 | ->row( [ |
370 | 'pr_entity' => $id, |
371 | 'pr_key' => $property, |
372 | 'pr_value' => $list, |
373 | ] ) |
374 | ->caller( __METHOD__ ) |
375 | ->execute(); |
376 | |
377 | if ( isset( $wikiNames[$dbname] ) ) { |
378 | $queryNames = array_merge( $wikiNames['*'], $wikiNames[$dbname] ); |
379 | } else { |
380 | $queryNames = $wikiNames['*']; |
381 | } |
382 | |
383 | $dbw->newDeleteQueryBuilder() |
384 | ->deleteFrom( 'securepoll_lists' ) |
385 | ->where( [ 'li_name' => $list ] ) |
386 | ->caller( __METHOD__ ) |
387 | ->execute(); |
388 | if ( $queryNames ) { |
389 | $dbw->insertSelect( |
390 | 'securepoll_lists', |
391 | 'user', |
392 | [ |
393 | 'li_name' => $dbw->addQuotes( $list ), |
394 | 'li_member' => 'user_id' |
395 | ], |
396 | [ 'user_name' => $queryNames ], |
397 | __METHOD__ |
398 | ); |
399 | } |
400 | } |
401 | |
402 | $dbw->endAtomic( __METHOD__ ); |
403 | } |
404 | |
405 | // Record this election to the SecurePoll namespace, if so configured. |
406 | if ( $this->specialPage->getConfig()->get( 'SecurePollUseNamespace' ) ) { |
407 | // Create a new context to bypass caching |
408 | $context = new Context; |
409 | $election = $context->getElection( $this->election->getId() ); |
410 | |
411 | [ $title, $content ] = SecurePollContentHandler::makeContentFromElection( |
412 | $election |
413 | ); |
414 | $wp = $this->wikiPageFactory->newFromTitle( $title ); |
415 | $wp->doUserEditContent( $content, $this->specialPage->getUser(), $comment ); |
416 | |
417 | $json = FormatJson::encode( |
418 | $this->fetchList( $property, DB_PRIMARY ), |
419 | false, |
420 | FormatJson::ALL_OK |
421 | ); |
422 | $title = $this->titleFactory->makeTitle( NS_SECUREPOLL, $list ); |
423 | $wp = $this->wikiPageFactory->newFromTitle( $title ); |
424 | $wp->doUserEditContent( |
425 | SecurePollContentHandler::makeContent( $json, $title, 'SecurePoll' ), |
426 | $this->specialPage->getUser(), |
427 | $comment |
428 | ); |
429 | } |
430 | } |
431 | |
432 | private function executeConfig() { |
433 | $out = $this->specialPage->getOutput(); |
434 | $out->addModuleStyles( [ |
435 | 'mediawiki.widgets.TagMultiselectWidget.styles', |
436 | 'ext.securepoll', |
437 | ] ); |
438 | $out->setPageTitleMsg( $this->msg( 'securepoll-votereligibility-title' ) ); |
439 | |
440 | $formItems = []; |
441 | |
442 | $formItems['default_submit'] = [ |
443 | 'section' => 'basic', |
444 | 'type' => 'submit', |
445 | 'buttonlabel' => 'submit', |
446 | 'cssclass' => 'securepoll-default-submit' |
447 | ]; |
448 | |
449 | $formItems['min-edits'] = [ |
450 | 'section' => 'basic', |
451 | 'label-message' => 'securepoll-votereligibility-label-min_edits', |
452 | 'type' => 'int', |
453 | 'min' => 0, |
454 | 'default' => $this->election->getProperty( 'min-edits', '' ), |
455 | ]; |
456 | |
457 | $date = $this->election->getProperty( 'max-registration', '' ); |
458 | if ( $date !== '' ) { |
459 | $date = gmdate( 'Y-m-d', (int)wfTimestamp( TS_UNIX, $date ) ); |
460 | } else { |
461 | $date = gmdate( 'Y-m-d', strtotime( 'yesterday' ) ); |
462 | } |
463 | $formItems['max-registration'] = [ |
464 | 'section' => 'basic', |
465 | 'label-message' => 'securepoll-votereligibility-label-max_registration', |
466 | 'type' => 'date', |
467 | 'default' => $date, |
468 | ]; |
469 | |
470 | $formItems['not-sitewide-blocked'] = [ |
471 | 'section' => 'basic', |
472 | 'type' => 'check', |
473 | 'label-message' => 'securepoll-votereligibility-label-not_blocked_sitewide', |
474 | 'default' => $this->election->getProperty( 'not-sitewide-blocked' ), |
475 | ]; |
476 | |
477 | $formItems['not-partial-blocked'] = [ |
478 | 'section' => 'basic', |
479 | 'type' => 'check', |
480 | 'label-message' => 'securepoll-votereligibility-label-not_blocked_partial', |
481 | 'default' => $this->election->getProperty( 'not-partial-blocked' ), |
482 | ]; |
483 | |
484 | $formItems['not-centrally-blocked'] = [ |
485 | 'section' => 'basic', |
486 | 'label-message' => 'securepoll-votereligibility-label-not_centrally_blocked', |
487 | 'type' => 'check', |
488 | 'hidelabel' => true, |
489 | 'default' => $this->election->getProperty( 'not-centrally-blocked', false ), |
490 | ]; |
491 | |
492 | $formItems['central-block-threshold'] = [ |
493 | 'section' => 'basic', |
494 | 'label-message' => 'securepoll-votereligibility-label-central_block_threshold', |
495 | 'type' => 'int', |
496 | 'validation-callback' => [ |
497 | $this, |
498 | 'checkCentralBlockThreshold', |
499 | ], |
500 | 'hide-if' => [ |
501 | '===', |
502 | 'not-centrally-blocked', |
503 | '' |
504 | ], |
505 | 'default' => $this->election->getProperty( 'central-block-threshold', '' ), |
506 | ]; |
507 | |
508 | $formItems['not-bot'] = [ |
509 | 'section' => 'basic', |
510 | 'label-message' => 'securepoll-votereligibility-label-not_bot', |
511 | 'type' => 'check', |
512 | 'hidelabel' => true, |
513 | 'default' => $this->election->getProperty( 'not-bot', false ), |
514 | ]; |
515 | |
516 | $formItems['allow-usergroups'] = [ |
517 | 'section' => 'basic', |
518 | 'label-message' => 'securepoll-votereligibility-label-include_groups', |
519 | 'allowArbitrary' => false, |
520 | 'type' => 'tagmultiselect', |
521 | 'allowedValues' => $this->userGroupManager->listAllGroups(), |
522 | 'default' => implode( "\n", explode( '|', $this->election->getProperty( 'allow-usergroups', "" ) ) ) |
523 | ]; |
524 | |
525 | foreach ( self::$lists as $list => $property ) { |
526 | $use = null; |
527 | $links = []; |
528 | if ( $list === 'voter' ) { |
529 | $complete = $this->election->getProperty( 'list_complete-count', 0 ); |
530 | $total = $this->election->getProperty( 'list_total-count', 0 ); |
531 | if ( $complete !== $total ) { |
532 | $use = $this->msg( 'securepoll-votereligibility-label-processing' )->numParams( |
533 | round( $complete * 100.0 / $total, 1 ) |
534 | )->numParams( $complete, $total ); |
535 | $links = [ 'clear' ]; |
536 | } |
537 | } |
538 | if ( $use === null && $this->election->getProperty( $property ) ) { |
539 | $use = $this->msg( 'securepoll-votereligibility-label-inuse' ); |
540 | $links = [ |
541 | 'edit', |
542 | 'clear' |
543 | ]; |
544 | } |
545 | if ( $use === null ) { |
546 | $use = $this->msg( 'securepoll-votereligibility-label-notinuse' ); |
547 | $links = [ 'edit' ]; |
548 | } |
549 | |
550 | $formItems[] = [ |
551 | 'section' => "lists/$list", |
552 | 'type' => 'info', |
553 | 'raw' => true, |
554 | 'default' => $use->parse(), |
555 | ]; |
556 | |
557 | $prefix = 'votereligibility/' . $this->election->getId(); |
558 | foreach ( $links as $action ) { |
559 | $title = SpecialPage::getTitleFor( 'SecurePoll', "$prefix/$action/$list" ); |
560 | $link = $this->linkRenderer->makeLink( $title, |
561 | $this->msg( "securepoll-votereligibility-label-$action" )->text() ); |
562 | $formItems[] = [ |
563 | 'section' => "lists/$list", |
564 | 'type' => 'info', |
565 | 'raw' => true, |
566 | 'default' => $link, |
567 | ]; |
568 | } |
569 | |
570 | if ( $list === 'voter' ) { |
571 | $formItems['list_populate'] = [ |
572 | 'section' => "lists/$list", |
573 | 'label-message' => 'securepoll-votereligibility-label-populate', |
574 | 'type' => 'check', |
575 | 'hidelabel' => true, |
576 | 'default' => $this->election->getProperty( 'list_populate', false ), |
577 | ]; |
578 | |
579 | $formItems['list_edits-before'] = [ |
580 | 'section' => "lists/$list", |
581 | 'label-message' => 'securepoll-votereligibility-label-edits_before', |
582 | 'type' => 'check', |
583 | 'default' => $this->election->getProperty( 'list_edits-before', false ), |
584 | 'hide-if' => [ |
585 | '===', |
586 | 'list_populate', |
587 | '' |
588 | ], |
589 | ]; |
590 | |
591 | $formItems['list_edits-before-count'] = [ |
592 | 'section' => "lists/$list", |
593 | 'label-message' => 'securepoll-votereligibility-label-edits_before_count', |
594 | 'type' => 'int', |
595 | 'validation-callback' => [ |
596 | $this, |
597 | 'checkEditsBeforeCount', |
598 | ], |
599 | 'hide-if' => [ |
600 | 'OR', |
601 | [ |
602 | '===', |
603 | 'list_populate', |
604 | '' |
605 | ], |
606 | [ |
607 | '===', |
608 | 'list_edits-before', |
609 | '' |
610 | ], |
611 | ], |
612 | 'default' => $this->election->getProperty( 'list_edits-before-count', '' ), |
613 | ]; |
614 | |
615 | $date = $this->election->getProperty( 'list_edits-before-date', '' ); |
616 | if ( $date !== '' ) { |
617 | $date = gmdate( 'Y-m-d', (int)wfTimestamp( TS_UNIX, $date ) ); |
618 | } else { |
619 | $date = gmdate( 'Y-m-d', strtotime( 'yesterday' ) ); |
620 | } |
621 | $formItems['list_edits-before-date'] = [ |
622 | 'section' => "lists/$list", |
623 | 'label-message' => 'securepoll-votereligibility-label-edits_before_date', |
624 | 'type' => 'date', |
625 | 'max' => gmdate( 'Y-m-d', strtotime( 'yesterday' ) ), |
626 | 'required' => true, |
627 | 'hide-if' => [ |
628 | 'OR', |
629 | [ |
630 | '===', |
631 | 'list_populate', |
632 | '' |
633 | ], |
634 | [ |
635 | '===', |
636 | 'list_edits-before', |
637 | '' |
638 | ], |
639 | ], |
640 | 'default' => $date, |
641 | ]; |
642 | |
643 | $formItems['list_edits-between'] = [ |
644 | 'section' => "lists/$list", |
645 | 'label-message' => 'securepoll-votereligibility-label-edits_between', |
646 | 'type' => 'check', |
647 | 'hide-if' => [ |
648 | '===', |
649 | 'list_populate', |
650 | '' |
651 | ], |
652 | 'default' => $this->election->getProperty( 'list_edits-between', false ), |
653 | ]; |
654 | |
655 | $formItems['list_edits-between-count'] = [ |
656 | 'section' => "lists/$list", |
657 | 'label-message' => 'securepoll-votereligibility-label-edits_between_count', |
658 | 'type' => 'int', |
659 | 'validation-callback' => [ |
660 | $this, |
661 | 'checkEditsBetweenCount', |
662 | ], |
663 | 'hide-if' => [ |
664 | 'OR', |
665 | [ |
666 | '===', |
667 | 'list_populate', |
668 | '' |
669 | ], |
670 | [ |
671 | '===', |
672 | 'list_edits-between', |
673 | '' |
674 | ], |
675 | ], |
676 | 'default' => $this->election->getProperty( 'list_edits-between-count', '' ), |
677 | ]; |
678 | |
679 | $editCountStartDate = $this->election->getProperty( 'list_edits-startdate', '' ); |
680 | if ( $editCountStartDate !== '' ) { |
681 | $editCountStartDate = gmdate( |
682 | 'Y-m-d', |
683 | (int)wfTimestamp( TS_UNIX, $editCountStartDate ) |
684 | ); |
685 | } |
686 | |
687 | $formItems['list_edits-startdate'] = [ |
688 | 'section' => "lists/$list", |
689 | 'label-message' => 'securepoll-votereligibility-label-edits_startdate', |
690 | 'type' => 'date', |
691 | 'max' => gmdate( 'Y-m-d', strtotime( 'yesterday' ) ), |
692 | 'required' => true, |
693 | 'hide-if' => [ |
694 | 'OR', |
695 | [ |
696 | '===', |
697 | 'list_populate', |
698 | '' |
699 | ], |
700 | [ |
701 | '===', |
702 | 'list_edits-between', |
703 | '' |
704 | ], |
705 | ], |
706 | 'default' => $editCountStartDate, |
707 | ]; |
708 | |
709 | $editCountEndDate = $this->election->getProperty( 'list_edits-enddate', '' ); |
710 | if ( $editCountEndDate === '' ) { |
711 | $editCountEndDate = gmdate( 'Y-m-d', strtotime( 'yesterday' ) ); |
712 | } else { |
713 | $editCountEndDate = gmdate( |
714 | 'Y-m-d', |
715 | (int)wfTimestamp( TS_UNIX, $editCountEndDate ) |
716 | ); |
717 | } |
718 | |
719 | $formItems['list_edits-enddate'] = [ |
720 | 'section' => "lists/$list", |
721 | 'label-message' => 'securepoll-votereligibility-label-edits_enddate', |
722 | 'type' => 'date', |
723 | 'max' => gmdate( 'Y-m-d', strtotime( 'yesterday' ) ), |
724 | 'required' => true, |
725 | 'validation-callback' => [ |
726 | $this, |
727 | 'checkListEditsEndDate' |
728 | ], |
729 | 'hide-if' => [ |
730 | 'OR', |
731 | [ |
732 | '===', |
733 | 'list_populate', |
734 | '' |
735 | ], |
736 | [ |
737 | '===', |
738 | 'list_edits-between', |
739 | '' |
740 | ], |
741 | ], |
742 | 'default' => $editCountEndDate, |
743 | ]; |
744 | |
745 | $groups = $this->election->getProperty( 'list_exclude-groups', [] ); |
746 | if ( $groups ) { |
747 | $groups = array_map( |
748 | static function ( $group ) { |
749 | return [ 'group' => $group ]; |
750 | }, |
751 | explode( '|', $groups ) |
752 | ); |
753 | } |
754 | $formItems['list_exclude-groups'] = [ |
755 | 'section' => "lists/$list", |
756 | 'label-message' => 'securepoll-votereligibility-label-exclude_groups', |
757 | 'type' => 'cloner', |
758 | 'format' => 'raw', |
759 | 'default' => $groups, |
760 | 'fields' => [ |
761 | 'group' => [ |
762 | 'type' => 'text', |
763 | 'required' => true, |
764 | ], |
765 | ], |
766 | 'hide-if' => [ |
767 | '===', |
768 | 'list_populate', |
769 | '' |
770 | ], |
771 | ]; |
772 | |
773 | $groups = $this->election->getProperty( 'list_include-groups', [] ); |
774 | if ( $groups ) { |
775 | $groups = array_map( |
776 | static function ( $group ) { |
777 | return [ 'group' => $group ]; |
778 | }, |
779 | explode( '|', $groups ) |
780 | ); |
781 | } |
782 | $formItems['list_include-groups'] = [ |
783 | 'section' => "lists/$list", |
784 | 'label-message' => 'securepoll-votereligibility-label-include_groups', |
785 | 'type' => 'cloner', |
786 | 'format' => 'raw', |
787 | 'default' => $groups, |
788 | 'fields' => [ |
789 | 'group' => [ |
790 | 'type' => 'text', |
791 | 'required' => true, |
792 | ], |
793 | ], |
794 | 'hide-if' => [ |
795 | '===', |
796 | 'list_populate', |
797 | '' |
798 | ], |
799 | ]; |
800 | } |
801 | } |
802 | |
803 | if ( $this->specialPage->getConfig()->get( 'SecurePollUseNamespace' ) ) { |
804 | $formItems['comment'] = [ |
805 | 'type' => 'text', |
806 | 'label-message' => 'securepoll-votereligibility-label-comment', |
807 | 'maxlength' => 250, |
808 | ]; |
809 | } |
810 | |
811 | $form = HTMLForm::factory( |
812 | 'ooui', |
813 | $formItems, |
814 | $this->specialPage->getContext(), |
815 | 'securepoll-votereligibility' |
816 | ); |
817 | $form->addHeaderHtml( |
818 | $this->msg( 'securepoll-votereligibility-basic-info' )->parseAsBlock(), |
819 | 'basic' |
820 | ); |
821 | $form->addHeaderHtml( |
822 | $this->msg( 'securepoll-votereligibility-lists-info' )->parseAsBlock(), |
823 | 'lists' |
824 | ); |
825 | |
826 | $form->setSubmitTextMsg( 'securepoll-votereligibility-action' ); |
827 | $form->setSubmitCallback( |
828 | [ |
829 | $this, |
830 | 'processConfig' |
831 | ] |
832 | ); |
833 | $result = $form->show(); |
834 | |
835 | if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { |
836 | $out->setPageTitleMsg( $this->msg( 'securepoll-votereligibility-saved' ) ); |
837 | $out->addWikiMsg( 'securepoll-votereligibility-saved-text' ); |
838 | $out->returnToMain( false, SpecialPage::getTitleFor( 'SecurePoll' ) ); |
839 | } |
840 | } |
841 | |
842 | /** |
843 | * Based on HTMLDateRangeField::praseDate() |
844 | * |
845 | * @param string $value Date to be parsed |
846 | * @return int |
847 | */ |
848 | protected function parseDate( $value ) { |
849 | $value = trim( $value ); |
850 | $value .= ' T00:00:00+0000'; |
851 | |
852 | try { |
853 | $date = new DateTime( $value, new DateTimeZone( 'GMT' ) ); |
854 | |
855 | return $date->getTimestamp(); |
856 | } catch ( TimeoutException $ex ) { |
857 | // Unfortunately DateTime throws a generic Exception, but we can't |
858 | // ignore an exception generated by the RequestTimeout library. |
859 | throw $ex; |
860 | } catch ( Exception $ex ) { |
861 | return 0; |
862 | } |
863 | } |
864 | |
865 | /** |
866 | * Check that a required field has been filled. |
867 | * |
868 | * This is a hack to allow OOUI to work with no-JS environments, |
869 | * because the browser will prevent submission if fields that |
870 | * would be hidden by JS are required but not filled. |
871 | * |
872 | * @internal For use by the HTMLFormField |
873 | * @param string $value |
874 | * @return true|Message true on success, Message on error |
875 | */ |
876 | public function checkRequired( $value ) { |
877 | if ( $value === '' ) { |
878 | return Status::newFatal( 'htmlform-required' )->getMessage(); |
879 | } |
880 | return true; |
881 | } |
882 | |
883 | /** |
884 | * Check that a field has a minimum value |
885 | * |
886 | * This is a hack that reimplements input[min] because the |
887 | * browser implementation implicitly makes the field required |
888 | * as well. Since the hide-if infrastructure doesn't manage |
889 | * conditional requirements, this re-implementation allows |
890 | * for hide-if-affected fields to display errors when they are |
891 | * relevant (as opposed to all the time, even if the field |
892 | * is not in use) |
893 | * |
894 | * @internal For use by the HTMLFormField |
895 | * @param int $value |
896 | * @param int $min |
897 | * @return bool|string true on success, string on error |
898 | */ |
899 | public function checkMin( $value, $min ) { |
900 | if ( $value < $min ) { |
901 | return $this->msg( 'htmlform-int-toolow', $min )->parse(); |
902 | } |
903 | |
904 | return true; |
905 | } |
906 | |
907 | /** |
908 | * Pass input automatically if the parent input is not checked |
909 | * Otherwise check that input exists and is not less than 1 |
910 | * |
911 | * @internal For use by the HTMLFormField |
912 | * @param string $value |
913 | * @param mixed[] $formData |
914 | * @return bool|string true on success, string on error |
915 | */ |
916 | public function checkCentralBlockThreshold( $value, $formData ) { |
917 | if ( !$formData['not-centrally-blocked'] ) { |
918 | return true; |
919 | } |
920 | |
921 | $exists = $this->checkRequired( $value ); |
922 | if ( $exists !== true ) { |
923 | return $exists; |
924 | } |
925 | |
926 | return $this->checkMin( (int)$value, 1 ); |
927 | } |
928 | |
929 | /** |
930 | * Pass input automatically if the parent input is not checked |
931 | * Otherwise check that input exists and is not less than 1 |
932 | * |
933 | * @internal For use by the HTMLFormField |
934 | * @param string $value |
935 | * @param mixed[] $formData |
936 | * @return bool|string true on success, string on error |
937 | */ |
938 | public function checkEditsBeforeCount( $value, $formData ) { |
939 | if ( !$formData['list_edits-before'] ) { |
940 | return true; |
941 | } |
942 | |
943 | $exists = $this->checkRequired( $value ); |
944 | if ( $exists !== true ) { |
945 | return $exists; |
946 | } |
947 | |
948 | return $this->checkMin( (int)$value, 1 ); |
949 | } |
950 | |
951 | /** |
952 | * Pass input automatically if the parent input is not checked |
953 | * Otherwise check that input exists and is not less than 1 |
954 | * |
955 | * @internal For use by the HTMLFormField |
956 | * @param string $value |
957 | * @param mixed[] $formData |
958 | * @return bool|string true on success, string on error |
959 | */ |
960 | public function checkEditsBetweenCount( $value, $formData ) { |
961 | if ( !$formData['list_edits-between'] ) { |
962 | return true; |
963 | } |
964 | |
965 | $exists = $this->checkRequired( $value ); |
966 | if ( $exists !== true ) { |
967 | return $exists; |
968 | } |
969 | |
970 | return $this->checkMin( (int)$value, 1 ); |
971 | } |
972 | |
973 | /** |
974 | * Check the end date exists and is after the start date |
975 | * |
976 | * @internal For use by the HTMLFormField |
977 | * @param string $value |
978 | * @param mixed[] $formData |
979 | * @return bool|string true on success, string on error |
980 | */ |
981 | public function checkListEditsEndDate( $value, $formData ) { |
982 | if ( !$formData['list_edits-between'] ) { |
983 | return true; |
984 | } |
985 | |
986 | $startDate = $this->parseDate( $formData['list_edits-startdate'] ); |
987 | $endDate = $this->parseDate( $value ); |
988 | |
989 | if ( $startDate >= $endDate ) { |
990 | return $this->msg( 'securepoll-htmlform-daterange-end-before-start' )->parseAsBlock(); |
991 | } |
992 | |
993 | return true; |
994 | } |
995 | |
996 | public function processConfig( $formData, $form ) { |
997 | static $props = [ |
998 | 'min-edits', |
999 | 'not-sitewide-blocked', |
1000 | 'not-partial-blocked', |
1001 | 'not-centrally-blocked', |
1002 | 'central-block-threshold', |
1003 | 'not-bot', |
1004 | 'list_populate', |
1005 | 'list_edits-before', |
1006 | 'list_edits-before-count', |
1007 | 'list_edits-between', |
1008 | 'list_edits-between-count', |
1009 | ]; |
1010 | static $dateProps = [ |
1011 | 'max-registration', |
1012 | 'list_edits-before-date', |
1013 | 'list_edits-startdate', |
1014 | 'list_edits-enddate', |
1015 | ]; |
1016 | static $listProps = [ |
1017 | 'list_exclude-groups', |
1018 | 'list_include-groups', |
1019 | ]; |
1020 | static $multiselectProps = [ |
1021 | 'allow-usergroups' |
1022 | ]; |
1023 | |
1024 | static $propPrereqs = [ |
1025 | 'not-centrally-blocked' => [ |
1026 | 'central-block-threshold' |
1027 | ], |
1028 | 'list_edits-before' => [ |
1029 | 'list_edits-before-count', |
1030 | 'list_edits-before-date', |
1031 | ], |
1032 | 'list_edits-between' => [ |
1033 | 'list_edits-between-count', |
1034 | 'list_edits-startdate', |
1035 | 'list_edits-enddate', |
1036 | ] |
1037 | ]; |
1038 | |
1039 | if ( $formData['list_populate'] && |
1040 | !$formData['list_edits-before'] && |
1041 | !$formData['list_edits-between'] && |
1042 | !$formData['list_exclude-groups'] && |
1043 | !$formData['list_include-groups'] |
1044 | ) { |
1045 | return Status::newFatal( 'securepoll-votereligibility-fail-nothing-to-process' ); |
1046 | } |
1047 | |
1048 | $properties = []; |
1049 | $deleteProperties = []; |
1050 | |
1051 | // Unset any properties where the parent property is not checked and |
1052 | // mark them for deletion from the database |
1053 | foreach ( $propPrereqs as $parentProp => $childrenProps ) { |
1054 | if ( $formData[$parentProp] === '' || $formData[$parentProp] === false ) { |
1055 | foreach ( $childrenProps as $childProp ) { |
1056 | $formData[ $childProp ] = ''; |
1057 | $deleteProperties[] = $childProp; |
1058 | } |
1059 | } |
1060 | } |
1061 | |
1062 | foreach ( $props as $prop ) { |
1063 | if ( |
1064 | $formData[$prop] !== '' && |
1065 | $formData[$prop] !== false |
1066 | ) { |
1067 | $properties[$prop] = $formData[$prop]; |
1068 | } else { |
1069 | $deleteProperties[] = $prop; |
1070 | } |
1071 | } |
1072 | |
1073 | foreach ( $dateProps as $prop ) { |
1074 | if ( $formData[$prop] !== '' && $formData[$prop] !== [] ) { |
1075 | $dates = array_map( |
1076 | static function ( $date ) { |
1077 | $date = new DateTime( $date, new DateTimeZone( 'GMT' ) ); |
1078 | |
1079 | return wfTimestamp( TS_MW, $date->format( 'YmdHis' ) ); |
1080 | }, |
1081 | (array)$formData[$prop] |
1082 | ); |
1083 | $properties[$prop] = implode( '|', $dates ); |
1084 | } else { |
1085 | $deleteProperties[] = $prop; |
1086 | } |
1087 | } |
1088 | |
1089 | foreach ( $listProps as $prop ) { |
1090 | if ( $formData[$prop] ) { |
1091 | $names = array_map( |
1092 | static function ( $entry ) { |
1093 | return $entry['group']; |
1094 | }, |
1095 | $formData[$prop] |
1096 | ); |
1097 | sort( $names ); |
1098 | $properties[$prop] = implode( '|', $names ); |
1099 | } else { |
1100 | $deleteProperties[] = $prop; |
1101 | } |
1102 | } |
1103 | |
1104 | foreach ( $multiselectProps as $prop ) { |
1105 | if ( $formData[$prop] ) { |
1106 | $properties[$prop] = implode( '|', explode( "\n", $formData[$prop] ) ); |
1107 | } else { |
1108 | $deleteProperties[] = $prop; |
1109 | } |
1110 | } |
1111 | |
1112 | // De-dupe the $deleteProperties array |
1113 | $deleteProperties = array_unique( $deleteProperties ); |
1114 | |
1115 | $populate = !empty( $properties['list_populate'] ); |
1116 | if ( $populate ) { |
1117 | $properties['need-list'] = 'need-list-' . $this->election->getId(); |
1118 | } |
1119 | |
1120 | $comment = $formData['comment'] ?? ''; |
1121 | |
1122 | $this->saveProperties( $properties, $deleteProperties, $comment ); |
1123 | |
1124 | if ( $populate ) { |
1125 | // Run pushJobsForElection() in a deferred update to give it outer transaction |
1126 | // scope, but keep it presend, so that any errors bubble up to the user. |
1127 | DeferredUpdates::addCallableUpdate( |
1128 | function () { |
1129 | PopulateVoterListJob::pushJobsForElection( $this->election ); |
1130 | }, |
1131 | DeferredUpdates::PRESEND |
1132 | ); |
1133 | } |
1134 | |
1135 | return Status::newGood(); |
1136 | } |
1137 | |
1138 | private function executeEdit( $which ) { |
1139 | $out = $this->specialPage->getOutput(); |
1140 | |
1141 | if ( !isset( self::$lists[$which] ) ) { |
1142 | $out->addWikiMsg( 'securepoll-votereligibility-invalid-list' ); |
1143 | |
1144 | return; |
1145 | } |
1146 | $property = self::$lists[$which]; |
1147 | $name = $this->msg( "securepoll-votereligibility-$which" )->text(); |
1148 | |
1149 | if ( $which === 'voter' ) { |
1150 | $complete = $this->election->getProperty( 'list_complete-count', 0 ); |
1151 | $total = $this->election->getProperty( 'list_total-count', 0 ); |
1152 | if ( $complete !== $total ) { |
1153 | $out->addWikiMsg( 'securepoll-votereligibility-list-is-processing' ); |
1154 | |
1155 | return; |
1156 | } |
1157 | } |
1158 | |
1159 | $out->addModuleStyles( 'ext.securepoll' ); |
1160 | $out->setPageTitleMsg( $this->msg( 'securepoll-votereligibility-edit-title', $name ) ); |
1161 | |
1162 | $formItems = []; |
1163 | |
1164 | $formItems['names'] = [ |
1165 | 'label-message' => 'securepoll-votereligibility-label-names', |
1166 | 'type' => 'textarea', |
1167 | 'rows' => 20, |
1168 | 'default' => implode( "\n", $this->fetchList( $property ) ), |
1169 | ]; |
1170 | |
1171 | if ( $this->specialPage->getConfig()->get( 'SecurePollUseNamespace' ) ) { |
1172 | $formItems['comment'] = [ |
1173 | 'type' => 'text', |
1174 | 'label-message' => 'securepoll-votereligibility-label-comment', |
1175 | 'maxlength' => 250, |
1176 | ]; |
1177 | } |
1178 | |
1179 | $form = new HTMLForm( |
1180 | $formItems, $this->specialPage->getContext(), 'securepoll-votereligibility' |
1181 | ); |
1182 | $form->addHeaderHtml( |
1183 | $this->msg( 'securepoll-votereligibility-edit-header' )->parseAsBlock() |
1184 | ); |
1185 | $form->setDisplayFormat( 'div' ); |
1186 | $form->setSubmitTextMsg( 'securepoll-votereligibility-edit-action' ); |
1187 | $form->setSubmitCallback( |
1188 | function ( $formData, $form ) use ( $property ) { |
1189 | $this->saveList( $property, $formData['names'], $formData['comment'] ?? '' ); |
1190 | |
1191 | return Status::newGood(); |
1192 | } |
1193 | ); |
1194 | $result = $form->show(); |
1195 | |
1196 | if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { |
1197 | $out->setPageTitleMsg( $this->msg( 'securepoll-votereligibility-saved' ) ); |
1198 | $out->addWikiMsg( 'securepoll-votereligibility-saved-text' ); |
1199 | $out->returnToMain( |
1200 | false, |
1201 | SpecialPage::getTitleFor( |
1202 | 'SecurePoll', |
1203 | 'votereligibility/' . $this->election->getId() |
1204 | ) |
1205 | ); |
1206 | } |
1207 | } |
1208 | |
1209 | private function executeClear( $which ) { |
1210 | $out = $this->specialPage->getOutput(); |
1211 | $localWiki = WikiMap::getCurrentWikiId(); |
1212 | |
1213 | if ( !isset( self::$lists[$which] ) ) { |
1214 | $out->addWikiMsg( 'securepoll-votereligibility-invalid-list' ); |
1215 | |
1216 | return; |
1217 | } |
1218 | $property = self::$lists[$which]; |
1219 | $name = $this->msg( "securepoll-votereligibility-$which" )->text(); |
1220 | |
1221 | $out = $this->specialPage->getOutput(); |
1222 | $out->setPageTitleMsg( $this->msg( 'securepoll-votereligibility-clear-title', $name ) ); |
1223 | |
1224 | $wikis = $this->election->getProperty( 'wikis' ); |
1225 | if ( $wikis ) { |
1226 | $wikis = explode( "\n", $wikis ); |
1227 | $i = array_search( $localWiki, $wikis ); |
1228 | if ( $i !== false ) { |
1229 | unset( $wikis[$i] ); |
1230 | } |
1231 | array_unshift( $wikis, $localWiki ); |
1232 | } else { |
1233 | $wikis = [ $localWiki ]; |
1234 | } |
1235 | |
1236 | $dbw = null; |
1237 | foreach ( $wikis as $dbname ) { |
1238 | |
1239 | if ( $dbname === $localWiki ) { |
1240 | $lb = $this->lbFactory->getMainLB(); |
1241 | $dbw = $lb->getConnection( ILoadBalancer::DB_PRIMARY, |
1242 | [], false, ILoadBalancer::CONN_TRX_AUTOCOMMIT ); |
1243 | } else { |
1244 | |
1245 | unset( $dbw ); |
1246 | $lb = $this->lbFactory->getMainLB( $dbname ); |
1247 | $dbw = $lb->getConnection( ILoadBalancer::DB_PRIMARY, |
1248 | [], $dbname, ILoadBalancer::CONN_TRX_AUTOCOMMIT ); |
1249 | } |
1250 | |
1251 | $dbw->startAtomic( __METHOD__ ); |
1252 | |
1253 | $id = $dbw->newSelectQueryBuilder() |
1254 | ->select( 'el_entity' ) |
1255 | ->from( 'securepoll_elections' ) |
1256 | ->where( [ |
1257 | 'el_title' => $this->election->title |
1258 | ] ) |
1259 | ->caller( __METHOD__ ) |
1260 | ->fetchField(); |
1261 | if ( $id ) { |
1262 | $list = $dbw->newSelectQueryBuilder() |
1263 | ->select( 'pr_value' ) |
1264 | ->from( 'securepoll_properties' ) |
1265 | ->where( [ |
1266 | 'pr_entity' => $id, |
1267 | 'pr_key' => $property, |
1268 | ] ) |
1269 | ->caller( __METHOD__ ) |
1270 | ->fetchField(); |
1271 | if ( $list ) { |
1272 | $dbw->newDeleteQueryBuilder() |
1273 | ->deleteFrom( 'securepoll_lists' ) |
1274 | ->where( [ 'li_name' => $list ] ) |
1275 | ->caller( __METHOD__ ) |
1276 | ->execute(); |
1277 | $dbw->newDeleteQueryBuilder() |
1278 | ->deleteFrom( 'securepoll_properties' ) |
1279 | ->where( [ |
1280 | 'pr_entity' => $id, |
1281 | 'pr_key' => $property |
1282 | ] ) |
1283 | ->caller( __METHOD__ ) |
1284 | ->execute(); |
1285 | } |
1286 | |
1287 | if ( $which === 'voter' ) { |
1288 | $dbw->newDeleteQueryBuilder() |
1289 | ->deleteFrom( 'securepoll_properties' ) |
1290 | ->where( [ |
1291 | 'pr_entity' => $id, |
1292 | 'pr_key' => [ |
1293 | 'list_populate', |
1294 | 'list_job-key', |
1295 | 'list_total-count', |
1296 | 'list_complete-count', |
1297 | 'list_job-key', |
1298 | ], |
1299 | ] ) |
1300 | ->caller( __METHOD__ ) |
1301 | ->execute(); |
1302 | } |
1303 | } |
1304 | |
1305 | $dbw->endAtomic( __METHOD__ ); |
1306 | } |
1307 | |
1308 | // Record this election to the SecurePoll namespace, if so configured. |
1309 | if ( $this->specialPage->getConfig()->get( 'SecurePollUseNamespace' ) ) { |
1310 | // Create a new context to bypass caching |
1311 | $context = new Context; |
1312 | $election = $context->getElection( $this->election->getId() ); |
1313 | |
1314 | [ $title, $content ] = SecurePollContentHandler::makeContentFromElection( |
1315 | $election |
1316 | ); |
1317 | $wp = $this->wikiPageFactory->newFromTitle( $title ); |
1318 | $wp->doUserEditContent( |
1319 | $content, |
1320 | $this->specialPage->getUser(), |
1321 | $this->msg( 'securepoll-votereligibility-cleared-comment', $name )->text() |
1322 | ); |
1323 | |
1324 | $title = $this->titleFactory->makeTitle( NS_SECUREPOLL, "{$election->getId()}/list/$property" ); |
1325 | $wp = $this->wikiPageFactory->newFromTitle( $title ); |
1326 | $wp->doUserEditContent( |
1327 | SecurePollContentHandler::makeContent( '[]', $title, 'SecurePoll' ), |
1328 | $this->specialPage->getUser(), |
1329 | $this->msg( 'securepoll-votereligibility-cleared-comment', $name )->text() |
1330 | ); |
1331 | } |
1332 | |
1333 | $out->setPageTitleMsg( $this->msg( 'securepoll-votereligibility-cleared' ) ); |
1334 | $out->addWikiMsg( 'securepoll-votereligibility-cleared-text', $name ); |
1335 | $out->returnToMain( |
1336 | false, |
1337 | SpecialPage::getTitleFor( 'SecurePoll', 'votereligibility/' . $this->election->getId() ) |
1338 | ); |
1339 | } |
1340 | } |