Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 103 |
|
0.00% |
0 / 30 |
CRAP | |
0.00% |
0 / 1 |
Context | |
0.00% |
0 / 103 |
|
0.00% |
0 / 30 |
2652 | |
0.00% |
0 / 1 |
newFromXmlFile | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getParserOptions | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getStore | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getSpecialTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setStore | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getEntityType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getElection | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getElectionByTitle | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
newElectionFromRow | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
newVoterFromRow | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createVoter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getVoter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRandom | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setLanguages | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMessages | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
getMessage | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getDB | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newElection | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newQuestion | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newOption | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newCrypt | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newTallier | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newBallot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getBallotTypesForTally | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getBallotTypesForVote | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
newAuth | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newVoter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newElectionTallier | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
varDump | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
72 | |||
getResourceUrl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll; |
4 | |
5 | use MediaWiki\Extension\SecurePoll\Ballots\Ballot; |
6 | use MediaWiki\Extension\SecurePoll\Crypt\Crypt; |
7 | use MediaWiki\Extension\SecurePoll\Crypt\Random; |
8 | use MediaWiki\Extension\SecurePoll\Entities\Election; |
9 | use MediaWiki\Extension\SecurePoll\Entities\Option; |
10 | use MediaWiki\Extension\SecurePoll\Entities\Question; |
11 | use MediaWiki\Extension\SecurePoll\Store\DBStore; |
12 | use MediaWiki\Extension\SecurePoll\Store\Store; |
13 | use MediaWiki\Extension\SecurePoll\Store\XMLStore; |
14 | use MediaWiki\Extension\SecurePoll\Talliers\ElectionTallier; |
15 | use MediaWiki\Extension\SecurePoll\Talliers\Tallier; |
16 | use MediaWiki\Extension\SecurePoll\User\Auth; |
17 | use MediaWiki\Extension\SecurePoll\User\LocalAuth; |
18 | use MediaWiki\Extension\SecurePoll\User\RemoteMWAuth; |
19 | use MediaWiki\Extension\SecurePoll\User\Voter; |
20 | use MediaWiki\MediaWikiServices; |
21 | use MediaWiki\SpecialPage\SpecialPage; |
22 | use MediaWiki\Title\Title; |
23 | use ParserOptions; |
24 | use RequestContext; |
25 | use stdClass; |
26 | use Wikimedia\Rdbms\IDatabase; |
27 | |
28 | /** |
29 | * This object contains caches and various items of processing context for |
30 | * SecurePoll. It manages instances of long-lived objects such as the |
31 | * Store subclass. |
32 | * |
33 | * Long-lived data should be stored here, rather than in global variables or |
34 | * static member variables. |
35 | * |
36 | * A context object is passed to almost all SecurePoll constructors. This class |
37 | * provides factory functions for these objects. |
38 | * |
39 | * For debugging purposes, a var_dump() workalike which omits context objects |
40 | * is available as $context->varDump(). |
41 | */ |
42 | class Context { |
43 | /** @var string[] Language fallback sequence */ |
44 | public $languages = [ 'en' ]; |
45 | |
46 | /** |
47 | * Message text cache |
48 | * @var string[][][] |
49 | */ |
50 | public $messageCache = []; |
51 | |
52 | /** @var array election cache */ |
53 | public $electionCache = []; |
54 | |
55 | /** |
56 | * @var array Which messages are loaded. 2-d array: language and entity ID, value arbitrary. |
57 | */ |
58 | public $messagesLoaded = []; |
59 | |
60 | /** @var ParserOptions|null ParserOptions instance used for message parsing */ |
61 | public $parserOptions; |
62 | |
63 | /** |
64 | * Key value store of data needed for a decryption method. Data is added and |
65 | * used by a decryption object and persists across different decryption |
66 | * object instances. |
67 | * |
68 | * @var array |
69 | */ |
70 | public $decryptData = []; |
71 | |
72 | /** @var Store|null The store object */ |
73 | public $store; |
74 | |
75 | /** @var Random|null The Random instance */ |
76 | public $random; |
77 | |
78 | /** @var string[] */ |
79 | private $ballotTypesForVote; |
80 | |
81 | /** |
82 | * Create a new Context with an XML file as the storage backend. |
83 | * Returns false if there was a problem with the file, like a parse error. |
84 | * @param string $fileName |
85 | * @return bool|self |
86 | */ |
87 | public static function newFromXmlFile( $fileName ) { |
88 | $context = new self; |
89 | $store = new XMLStore( $fileName ); |
90 | $context->setStore( $store ); |
91 | $success = $store->readFile(); |
92 | if ( $success ) { |
93 | return $context; |
94 | } else { |
95 | return false; |
96 | } |
97 | } |
98 | |
99 | /** |
100 | * Get the ParserOptions instance |
101 | * @return ParserOptions |
102 | */ |
103 | public function getParserOptions() { |
104 | if ( !$this->parserOptions ) { |
105 | $this->parserOptions = ParserOptions::newFromUser( |
106 | RequestContext::getMain()->getUser() |
107 | ); |
108 | } |
109 | |
110 | return $this->parserOptions; |
111 | } |
112 | |
113 | /** |
114 | * Get the Store instance |
115 | * @return Store |
116 | */ |
117 | public function getStore() { |
118 | if ( !isset( $this->store ) ) { |
119 | $this->store = new DBStore( |
120 | MediaWikiServices::getInstance()->getDBLoadBalancer(), |
121 | false |
122 | ); |
123 | } |
124 | |
125 | return $this->store; |
126 | } |
127 | |
128 | /** |
129 | * Get a Title object for Special:SecurePoll |
130 | * @param string|false $subpage |
131 | * @return Title |
132 | */ |
133 | public function getSpecialTitle( $subpage = false ) { |
134 | return SpecialPage::getTitleFor( 'SecurePoll', $subpage ); |
135 | } |
136 | |
137 | /** |
138 | * Set the store object. Overrides any previous store class. |
139 | * @param Store $store |
140 | */ |
141 | public function setStore( $store ) { |
142 | $this->messageCache = $this->messagesLoaded = []; |
143 | $this->store = $store; |
144 | } |
145 | |
146 | /** |
147 | * Get the type of a particular entity |
148 | * @param int $id |
149 | * @return string|false |
150 | */ |
151 | public function getEntityType( $id ) { |
152 | return $this->getStore()->getEntityType( $id ); |
153 | } |
154 | |
155 | /** |
156 | * Get an election object from the store, with a given entity ID. Returns |
157 | * false if it does not exist. |
158 | * @param int $id |
159 | * @return Election|bool |
160 | */ |
161 | public function getElection( $id ) { |
162 | if ( !isset( $this->electionCache[$id] ) ) { |
163 | $info = $this->getStore()->getElectionInfo( [ $id ] ); |
164 | if ( $info ) { |
165 | $this->electionCache[$id] = $this->newElection( reset( $info ) ); |
166 | } else { |
167 | $this->electionCache[$id] = false; |
168 | } |
169 | } |
170 | |
171 | return $this->electionCache[$id]; |
172 | } |
173 | |
174 | /** |
175 | * Get an election object from the store, with a given name. Returns false |
176 | * if there is no such election. |
177 | * @param string $name |
178 | * @return Election|false |
179 | */ |
180 | public function getElectionByTitle( $name ) { |
181 | $info = $this->getStore()->getElectionInfoByTitle( [ $name ] ); |
182 | if ( $info ) { |
183 | return $this->newElection( reset( $info ) ); |
184 | } else { |
185 | return false; |
186 | } |
187 | } |
188 | |
189 | /** |
190 | * Get an election object from a securepoll_elections DB row. This will fail |
191 | * if the current store class does not support database operations. |
192 | * @param stdClass $row |
193 | * @return Election |
194 | */ |
195 | public function newElectionFromRow( $row ) { |
196 | $info = $this->getStore()->decodeElectionRow( $row ); |
197 | |
198 | return $this->newElection( $info ); |
199 | } |
200 | |
201 | /** |
202 | * Get a voter object from a securepoll_voters row |
203 | * @param stdClass $row |
204 | * @return Voter |
205 | */ |
206 | public function newVoterFromRow( $row ) { |
207 | return Voter::newFromRow( $this, $row ); |
208 | } |
209 | |
210 | /** |
211 | * Create a voter with the given parameters. Assumes the voter does not exist, |
212 | * and inserts it into the database. |
213 | * |
214 | * The row needs to be locked before this function is called, to avoid |
215 | * duplicate key errors. |
216 | * @param array $params |
217 | * @return Voter |
218 | */ |
219 | public function createVoter( $params ) { |
220 | return Voter::createVoter( $this, $params ); |
221 | } |
222 | |
223 | /** |
224 | * Create a voter object from the database |
225 | * @param int $id |
226 | * @param int $index DB_PRIMARY or DB_REPLICA |
227 | * @return Voter|false false if the ID is not valid |
228 | */ |
229 | public function getVoter( $id, $index = DB_PRIMARY ) { |
230 | return Voter::newFromId( $this, $id, $index ); |
231 | } |
232 | |
233 | /** |
234 | * Get a Random instance. This provides cryptographic random |
235 | * number generation. |
236 | * @return Random |
237 | */ |
238 | public function getRandom() { |
239 | if ( !$this->random ) { |
240 | $this->random = new Random; |
241 | } |
242 | |
243 | return $this->random; |
244 | } |
245 | |
246 | /** |
247 | * Set the global language fallback sequence. |
248 | * |
249 | * @param array $languages A list of language codes. When a message is |
250 | * requested, the first code in the array will be tried first, followed |
251 | * by the subsequent codes. |
252 | */ |
253 | public function setLanguages( $languages ) { |
254 | $this->languages = $languages; |
255 | } |
256 | |
257 | /** |
258 | * Get some messages from the backend store or the cache. |
259 | * This is an internal interface for Entity, generally you |
260 | * should use Entity::getMessage() instead. |
261 | * |
262 | * @param string $lang Language code |
263 | * @param array $ids Entity IDs |
264 | * @return string[][] |
265 | */ |
266 | public function getMessages( $lang, $ids ) { |
267 | if ( isset( $this->messagesLoaded[$lang] ) ) { |
268 | $cacheRow = $this->messagesLoaded[$lang]; |
269 | $uncachedIds = array_flip( $ids ); |
270 | foreach ( $uncachedIds as $id => $unused ) { |
271 | if ( isset( $cacheRow[$id] ) ) { |
272 | unset( $uncachedIds[$id] ); |
273 | } |
274 | } |
275 | if ( count( $uncachedIds ) ) { |
276 | $messages = $this->getStore()->getMessages( $lang, array_keys( $uncachedIds ) ); |
277 | $this->messageCache[$lang] += $messages; |
278 | $this->messagesLoaded[$lang] += $uncachedIds; |
279 | } |
280 | |
281 | return array_intersect_key( $this->messageCache[$lang], array_flip( $ids ) ); |
282 | } else { |
283 | $this->messagesLoaded[$lang] = $ids; |
284 | $this->messageCache[$lang] = $this->getStore()->getMessages( $lang, $ids ); |
285 | |
286 | return $this->messageCache[$lang]; |
287 | } |
288 | } |
289 | |
290 | /** |
291 | * Get a particular message. |
292 | * This is an internal interface for Entity, generally you |
293 | * should use Entity::getMessage() instead. |
294 | * |
295 | * @param string $lang Language code |
296 | * @param string|int $id Entity ID |
297 | * @param string $key Message key |
298 | * @return string|false |
299 | */ |
300 | public function getMessage( $lang, $id, $key ) { |
301 | if ( !isset( $this->messagesLoaded[$lang][$id] ) ) { |
302 | $this->getMessages( $lang, [ $id ] ); |
303 | } |
304 | |
305 | return $this->messageCache[$lang][$id][$key] ?? false; |
306 | } |
307 | |
308 | /** |
309 | * Get a database object, or throw an exception if the current store object |
310 | * does not support database operations. |
311 | * @param int $index DB_PRIMARY or DB_REPLICA |
312 | * @return IDatabase |
313 | */ |
314 | public function getDB( $index = DB_PRIMARY ) { |
315 | return $this->getStore()->getDB( $index ); |
316 | } |
317 | |
318 | /** |
319 | * @param array $info |
320 | * @return Election |
321 | */ |
322 | public function newElection( $info ) { |
323 | return new Election( $this, $info ); |
324 | } |
325 | |
326 | /** |
327 | * @param array $info |
328 | * @return Question |
329 | */ |
330 | public function newQuestion( $info ) { |
331 | return new Question( $this, $info ); |
332 | } |
333 | |
334 | /** |
335 | * @param array $info |
336 | * @return Option |
337 | */ |
338 | public function newOption( $info ) { |
339 | return new Option( $this, $info ); |
340 | } |
341 | |
342 | /** |
343 | * @param string $type |
344 | * @param Election $election |
345 | * @return Crypt|false False when encryption type is set to "none" |
346 | */ |
347 | public function newCrypt( $type, $election ) { |
348 | return Crypt::factory( $this, $type, $election ); |
349 | } |
350 | |
351 | /** |
352 | * @param string $type |
353 | * @param ElectionTallier $electionTallier |
354 | * @param Question $question |
355 | * @return Tallier |
356 | */ |
357 | public function newTallier( $type, $electionTallier, $question ) { |
358 | return Tallier::factory( $this, $type, $electionTallier, $question ); |
359 | } |
360 | |
361 | /** |
362 | * @param string $type |
363 | * @param Election $election |
364 | * @return Ballot |
365 | */ |
366 | public function newBallot( $type, $election ) { |
367 | return Ballot::factory( $this, $type, $election ); |
368 | } |
369 | |
370 | /** |
371 | * Get a map of ballot type names to classes. Include all the ballot |
372 | * types that may be found in the securepoll_votes table. |
373 | * |
374 | * @return string[] |
375 | */ |
376 | public function getBallotTypesForTally() { |
377 | return Ballot::BALLOT_TYPES; |
378 | } |
379 | |
380 | /** |
381 | * Get a map of ballot type names to classes, for all ballot types which |
382 | * are valid for new votes and election creation. |
383 | * |
384 | * @return string[] |
385 | */ |
386 | public function getBallotTypesForVote() { |
387 | if ( $this->ballotTypesForVote === null ) { |
388 | $types = $this->getBallotTypesForTally(); |
389 | |
390 | // Archived (T300087) |
391 | unset( $types['radio-range-comment' ] ); |
392 | |
393 | // Remove STV from options if flag is not set |
394 | if ( !RequestContext::getMain()->getConfig()->get( 'SecurePollSingleTransferableVoteEnabled' ) ) { |
395 | unset( $types['stv'] ); |
396 | } |
397 | $this->ballotTypesForVote = $types; |
398 | } |
399 | return $this->ballotTypesForVote; |
400 | } |
401 | |
402 | /** |
403 | * @param string $type |
404 | * @return LocalAuth|RemoteMWAuth |
405 | */ |
406 | public function newAuth( $type ) { |
407 | return Auth::factory( $this, $type ); |
408 | } |
409 | |
410 | /** |
411 | * @param array $params |
412 | * @return Voter |
413 | */ |
414 | public function newVoter( $params ) { |
415 | return new Voter( $this, $params ); |
416 | } |
417 | |
418 | /** |
419 | * @param Election $election |
420 | * @return ElectionTallier |
421 | */ |
422 | public function newElectionTallier( $election ) { |
423 | return new ElectionTallier( $this, $election ); |
424 | } |
425 | |
426 | /** |
427 | * Debugging function to output a representation of a mixed-type variable, |
428 | * but omitting the $obj->context member variables for brevity. |
429 | * |
430 | * @param mixed $var |
431 | * @param bool $return True to return the text instead of echoing |
432 | * @param int $level Recursion level, leave this as zero when calling. |
433 | * @return string|void |
434 | */ |
435 | public function varDump( $var, $return = false, $level = 0 ) { |
436 | $tab = ' '; |
437 | $indent = str_repeat( $tab, $level ); |
438 | if ( is_array( $var ) ) { |
439 | $s = "array(\n"; |
440 | foreach ( $var as $key => $value ) { |
441 | $s .= "$indent$tab" . $this->varDump( |
442 | $key, |
443 | true, |
444 | $level + 1 |
445 | ) . " => " . $this->varDump( $value, true, $level + 1 ) . ",\n"; |
446 | } |
447 | $s .= "{$indent})"; |
448 | } elseif ( is_object( $var ) ) { |
449 | $props = (array)$var; |
450 | $s = get_class( $var ) . " {\n"; |
451 | foreach ( $props as $key => $value ) { |
452 | $s .= "$indent$tab" . $this->varDump( $key, true, $level + 1 ) . " => "; |
453 | if ( $key === 'context' ) { |
454 | $s .= "[CONTEXT],\n"; |
455 | } else { |
456 | $s .= $this->varDump( $value, true, $level + 1 ) . ",\n"; |
457 | } |
458 | } |
459 | $s .= "{$indent}}"; |
460 | } else { |
461 | $s = var_export( $var, true ); |
462 | } |
463 | if ( $level == 0 ) { |
464 | $s .= "\n"; |
465 | } |
466 | if ( $return ) { |
467 | return $s; |
468 | } else { |
469 | echo $s; |
470 | } |
471 | } |
472 | |
473 | /** |
474 | * @param string $resource |
475 | * @return string |
476 | */ |
477 | public function getResourceUrl( $resource ) { |
478 | global $wgExtensionAssetsPath; |
479 | |
480 | return "$wgExtensionAssetsPath/SecurePoll/resources/$resource"; |
481 | } |
482 | } |