Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 71 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
TallyElectionJob | |
0.00% |
0 / 71 |
|
0.00% |
0 / 7 |
156 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
allowRetries | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
run | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
doRun | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
6 | |||
preRun | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
postRun | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
12 | |||
markAsFailed | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\Jobs; |
4 | |
5 | use Job; |
6 | use MediaWiki\Extension\SecurePoll\Context; |
7 | use MediaWiki\Extension\SecurePoll\Entities\Election; |
8 | use MediaWiki\Extension\SecurePoll\Exceptions\InvalidDataException; |
9 | use MediaWiki\Extension\SecurePoll\Talliers\ElectionTallier; |
10 | use Throwable; |
11 | use Wikimedia\Rdbms\IDatabase; |
12 | |
13 | /** |
14 | * Job for tallying an encrypted election. |
15 | */ |
16 | class TallyElectionJob extends Job { |
17 | |
18 | /** @var int */ |
19 | private $electionId; |
20 | |
21 | /** @var Election|bool */ |
22 | private $election; |
23 | |
24 | /** @var IDatabase|null */ |
25 | private $dbw; |
26 | |
27 | /** |
28 | * @inheritDoc |
29 | */ |
30 | public function __construct( $title, $params ) { |
31 | parent::__construct( 'securePollTallyElection', $title, $params ); |
32 | } |
33 | |
34 | /** |
35 | * @inheritDoc |
36 | */ |
37 | public function allowRetries() { |
38 | return false; |
39 | } |
40 | |
41 | /** |
42 | * @inheritDoc |
43 | */ |
44 | public function run(): bool { |
45 | $context = new Context(); |
46 | |
47 | $this->electionId = (int)$this->params['electionId']; |
48 | $this->election = $context->getElection( $this->electionId ); |
49 | |
50 | if ( !$this->election ) { |
51 | $this->setLastError( "Could not get election '$this->electionId'" ); |
52 | |
53 | return false; |
54 | } |
55 | |
56 | $this->dbw = $context->getDB( DB_PRIMARY ); |
57 | |
58 | try { |
59 | $this->preRun(); |
60 | |
61 | return $this->doRun(); |
62 | } catch ( Throwable $e ) { |
63 | $this->markAsFailed( get_class( $e ) . ': ' . $e->getMessage(), __METHOD__ ); |
64 | |
65 | // Return here rather than re-throw the exception so that the explicit transaction |
66 | // round created for this job is not moved into the error state before running |
67 | // TallyElectionJob::postRun(). |
68 | return false; |
69 | } finally { |
70 | $this->postRun(); |
71 | } |
72 | } |
73 | |
74 | /** |
75 | * @return bool |
76 | */ |
77 | private function doRun(): bool { |
78 | $status = $this->election->tally(); |
79 | |
80 | if ( !$status->isOK() ) { |
81 | $this->markAsFailed( $status->getMessage(), __METHOD__ ); |
82 | |
83 | return false; |
84 | } |
85 | |
86 | $tallier = $status->value; |
87 | '@phan-var ElectionTallier $tallier'; /** @var ElectionTallier $tallier */ |
88 | $result = json_encode( $tallier->getJSONResult() ); |
89 | $time = time(); |
90 | |
91 | $this->dbw->newReplaceQueryBuilder() |
92 | ->replaceInto( 'securepoll_properties' ) |
93 | ->uniqueIndexFields( [ 'pr_entity', 'pr_key' ] ) |
94 | ->row( [ |
95 | 'pr_entity' => $this->electionId, |
96 | 'pr_key' => 'tally-result', |
97 | 'pr_value' => $result, |
98 | ] ) |
99 | ->row( [ |
100 | 'pr_entity' => $this->electionId, |
101 | 'pr_key' => 'tally-result-time', |
102 | 'pr_value' => $time, |
103 | ] ) |
104 | ->caller( __METHOD__ ) |
105 | ->execute(); |
106 | |
107 | return true; |
108 | } |
109 | |
110 | /** |
111 | * Initializes the database for tallying the election by removing any previously recorded |
112 | * tallying errors and/or results. |
113 | */ |
114 | private function preRun() { |
115 | $this->dbw->newDeleteQueryBuilder() |
116 | ->deleteFrom( 'securepoll_properties' ) |
117 | ->where( [ |
118 | 'pr_entity' => $this->electionId, |
119 | 'pr_key' => [ |
120 | 'tally-error', |
121 | ], |
122 | ] ) |
123 | ->caller( __METHOD__ ) |
124 | ->execute(); |
125 | } |
126 | |
127 | private function postRun() { |
128 | $this->dbw->newDeleteQueryBuilder() |
129 | ->deleteFrom( 'securepoll_properties' ) |
130 | ->where( [ |
131 | 'pr_entity' => $this->electionId, |
132 | 'pr_key' => 'tally-job-enqueued', |
133 | ] ) |
134 | ->caller( __METHOD__ ) |
135 | ->execute(); |
136 | |
137 | try { |
138 | $crypt = $this->election->getCrypt(); |
139 | if ( $crypt ) { |
140 | $crypt->cleanupDbForTallyJob( $this->electionId, $this->dbw ); |
141 | } |
142 | } catch ( InvalidDataException $e ) { |
143 | // Election::getCrypt() throws InvalidDataException if an election has the "encrypt-type" |
144 | // property set but the corresponding class cannot be instantiated. |
145 | // |
146 | // Swallow this exception for the following reasons: |
147 | // |
148 | // * This job can only be enqueued when the user clicks the "Create tally" button on |
149 | // the tally page. That page does not work in these circumstances. |
150 | // |
151 | // * At this point, the election has been tallied and the result can be displayed to |
152 | // the user. If this exception is caught by the job runner then the explicit |
153 | // transaction round created for this job will not be committed. |
154 | } |
155 | } |
156 | |
157 | /** |
158 | * @param string $message |
159 | * @param string $fname |
160 | */ |
161 | private function markAsFailed( string $message, string $fname ) { |
162 | $this->setLastError( $message ); |
163 | |
164 | $this->dbw->newInsertQueryBuilder() |
165 | ->insertInto( 'securepoll_properties' ) |
166 | ->row( [ |
167 | 'pr_entity' => $this->electionId, |
168 | 'pr_key' => 'tally-error', |
169 | 'pr_value' => $message |
170 | ] ) |
171 | ->caller( $fname ) |
172 | ->execute(); |
173 | } |
174 | } |