Index: trunk/extensions/SecurePoll/includes/talliers/PluralityTallier.php |
— | — | @@ -35,7 +35,7 @@ |
36 | 36 | |
37 | 37 | foreach ( $this->tally as $oid => $rank ) { |
38 | 38 | $option = $this->optionsById[$oid]; |
39 | | - $s .= '<tr><td>' . $option->getMessage( 'text' ) . "</td>\n" . |
| 39 | + $s .= '<tr><td>' . $option->parseMessageInline( 'text' ) . "</td>\n" . |
40 | 40 | '<td>' . $this->tally[$oid] . "</td>\n" . |
41 | 41 | "</tr>\n"; |
42 | 42 | } |
Index: trunk/extensions/SecurePoll/includes/ballots/Ballot.php |
— | — | @@ -176,6 +176,53 @@ |
177 | 177 | function formatStatus( $status ) { |
178 | 178 | return $status->sp_getHTML( $this->usedErrorIds ); |
179 | 179 | } |
| 180 | + |
| 181 | + /** |
| 182 | + * Get the way the voter cast their vote previously, if we're allowed |
| 183 | + * to show that information. |
| 184 | + * @return false on failure or if cast ballots are hidden, or the output |
| 185 | + * of unpackRecord(). |
| 186 | + */ |
| 187 | + function getCurrentVote(){ |
| 188 | + |
| 189 | + if( !$this->election->getOption( 'show-change' ) ){ |
| 190 | + return false; |
| 191 | + } |
| 192 | + |
| 193 | + $auth = $this->election->getAuth(); |
| 194 | + |
| 195 | + # Get voter from session |
| 196 | + $voter = $auth->getVoterFromSession( $this->election ); |
| 197 | + # If there's no session, try creating one. |
| 198 | + # This will fail if the user is not authorised to vote in the election |
| 199 | + if ( !$voter ) { |
| 200 | + $status = $auth->newAutoSession( $this->election ); |
| 201 | + if ( $status->isOK() ) { |
| 202 | + $voter = $status->value; |
| 203 | + } else { |
| 204 | + return false; |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + $store = $this->context->getStore(); |
| 209 | + $status = $store->callbackValidVotes( |
| 210 | + $this->election->info['id'], |
| 211 | + array( $this, 'getCurrentVoteCallback' ), |
| 212 | + $voter->getId() |
| 213 | + ); |
| 214 | + if( !$status->isOK() ){ |
| 215 | + return false; |
| 216 | + } |
| 217 | + |
| 218 | + return isset( $this->currentVote ) |
| 219 | + ? $this->unpackRecord( $this->currentVote ) |
| 220 | + : false; |
| 221 | + } |
| 222 | + |
| 223 | + function getCurrentVoteCallback( $store, $record ){ |
| 224 | + $this->currentVote = $record; |
| 225 | + return Status::newGood(); |
| 226 | + } |
180 | 227 | } |
181 | 228 | |
182 | 229 | class SecurePoll_BallotStatus extends Status { |
Index: trunk/extensions/SecurePoll/includes/entities/Entity.php |
— | — | @@ -12,6 +12,7 @@ |
13 | 13 | */ |
14 | 14 | class SecurePoll_Entity { |
15 | 15 | var $id; |
| 16 | + var $electionId; |
16 | 17 | var $context; |
17 | 18 | var $messagesLoaded = array(); |
18 | 19 | var $properties; |
— | — | @@ -26,7 +27,12 @@ |
27 | 28 | function __construct( $context, $type, $info ) { |
28 | 29 | $this->context = $context; |
29 | 30 | $this->type = $type; |
30 | | - $this->id = isset( $info['id'] ) ? $info['id'] : false; |
| 31 | + $this->id = isset( $info['id'] ) |
| 32 | + ? $info['id'] |
| 33 | + : false; |
| 34 | + $this->electionId = isset( $info['election'] ) |
| 35 | + ? $info['election'] |
| 36 | + : null; |
31 | 37 | } |
32 | 38 | |
33 | 39 | /** |
— | — | @@ -53,6 +59,16 @@ |
54 | 60 | function getId() { |
55 | 61 | return $this->id; |
56 | 62 | } |
| 63 | + |
| 64 | + /** |
| 65 | + * Get the parent election |
| 66 | + * @return Int |
| 67 | + */ |
| 68 | + public function getElection() { |
| 69 | + return $this->electionId !== null |
| 70 | + ? $this->context->getElection( $this->electionId ) |
| 71 | + : null; |
| 72 | + } |
57 | 73 | |
58 | 74 | /** |
59 | 75 | * Get the child entity objects. When the messages of an object are loaded, |
Index: trunk/extensions/SecurePoll/includes/entities/Election.php |
— | — | @@ -53,17 +53,20 @@ |
54 | 54 | */ |
55 | 55 | class SecurePoll_Election extends SecurePoll_Entity { |
56 | 56 | var $questions, $auth, $ballot; |
57 | | - var $title, $ballotType, $tallyType, $primaryLang, $startDate, $endDate, $authType; |
| 57 | + var $id, $title, $ballotType, $tallyType, $primaryLang; |
| 58 | + var $startDate, $endDate, $authType; |
58 | 59 | |
59 | 60 | /** |
60 | 61 | * Constructor. |
61 | 62 | * |
62 | | - * Do not use this constructor directly, instead use SecurePoll_Context::getElection(). |
| 63 | + * Do not use this constructor directly, instead use |
| 64 | + * SecurePoll_Context::getElection(). |
63 | 65 | * |
64 | 66 | * @param $id integer |
65 | 67 | */ |
66 | 68 | function __construct( $context, $info ) { |
67 | 69 | parent::__construct( $context, 'election', $info ); |
| 70 | + $this->id = $info['id']; |
68 | 71 | $this->title = $info['title']; |
69 | 72 | $this->ballotType = $info['ballot']; |
70 | 73 | $this->tallyType = $info['tally']; |
— | — | @@ -85,6 +88,13 @@ |
86 | 89 | 'unqualified-error', |
87 | 90 | ); |
88 | 91 | } |
| 92 | + |
| 93 | + /** |
| 94 | + * Get the election's parent election... hmm... |
| 95 | + */ |
| 96 | + function getElection() { |
| 97 | + return $this->id; |
| 98 | + } |
89 | 99 | |
90 | 100 | /** |
91 | 101 | * Get a list of child entity objects. See SecurePoll_Entity. |
Index: trunk/extensions/SecurePoll/includes/entities/Question.php |
— | — | @@ -18,14 +18,13 @@ |
19 | 19 | foreach ( $info['options'] as $optionInfo ) { |
20 | 20 | $this->options[] = new SecurePoll_Option( $context, $optionInfo ); |
21 | 21 | } |
22 | | - $this->electionId = $info['election']; |
23 | 22 | } |
24 | 23 | |
25 | 24 | /** |
26 | 25 | * Get a list of localisable message names. |
27 | 26 | */ |
28 | 27 | function getMessageNames() { |
29 | | - $ballot = $this->context->getElection( $this->electionId )->getBallot(); |
| 28 | + $ballot = $this->getElection()->getBallot(); |
30 | 29 | return array_merge( $ballot->getMessageNames( $this ), array( 'text' ) ); |
31 | 30 | |
32 | 31 | } |
Index: trunk/extensions/SecurePoll/includes/main/Store.php |
— | — | @@ -32,6 +32,14 @@ |
33 | 33 | * mapping IDs and property keys to values. |
34 | 34 | */ |
35 | 35 | function getProperties( $ids ); |
| 36 | + |
| 37 | + /** |
| 38 | + * Get the type of one or more SecurePoll entities. |
| 39 | + * @param $ids Int |
| 40 | + * @return String |
| 41 | + */ |
| 42 | + function getEntityType( $id ); |
| 43 | + |
36 | 44 | |
37 | 45 | /** |
38 | 46 | * Get information about a set of elections, specifically the data that |
— | — | @@ -204,7 +212,10 @@ |
205 | 213 | ); |
206 | 214 | $options = array(); |
207 | 215 | } |
208 | | - $options[] = array( 'id' => $row->op_entity ); |
| 216 | + $options[] = array( |
| 217 | + 'id' => $row->op_entity, |
| 218 | + 'election' => $row->op_election, |
| 219 | + ); |
209 | 220 | $questionId = $row->qu_entity; |
210 | 221 | $electionId = $row->qu_election; |
211 | 222 | } |
— | — | @@ -218,25 +229,43 @@ |
219 | 230 | return $questions; |
220 | 231 | } |
221 | 232 | |
222 | | - function callbackValidVotes( $electionId, $callback ) { |
| 233 | + function callbackValidVotes( $electionId, $callback, $voterId=null ) { |
223 | 234 | $dbr = $this->getDB(); |
| 235 | + $where = array( |
| 236 | + 'vote_election' => $electionId, |
| 237 | + 'vote_current' => 1, |
| 238 | + 'vote_struck' => 0 |
| 239 | + ); |
| 240 | + if( $voterId !== null ){ |
| 241 | + $where['vote_voter'] = $voterId; |
| 242 | + } |
224 | 243 | $res = $dbr->select( |
225 | 244 | 'securepoll_votes', |
226 | | - array( 'vote_record' ), |
227 | | - array( |
228 | | - 'vote_election' => $electionId, |
229 | | - 'vote_current' => 1, |
230 | | - 'vote_struck' => 0 |
231 | | - ), __METHOD__ |
| 245 | + '*', |
| 246 | + $where, |
| 247 | + __METHOD__ |
232 | 248 | ); |
| 249 | + |
233 | 250 | foreach ( $res as $row ) { |
234 | 251 | $status = call_user_func( $callback, $this, $row->vote_record ); |
235 | | - if ( $status && !$status->isOK() ) { |
| 252 | + if( $status instanceof Status && !$status->isOK() ){ |
236 | 253 | return $status; |
237 | 254 | } |
238 | 255 | } |
239 | 256 | return Status::newGood(); |
240 | 257 | } |
| 258 | + |
| 259 | + function getEntityType( $id ){ |
| 260 | + $db = $this->getDB(); |
| 261 | + $res = $db->selectRow( |
| 262 | + 'securepoll_entity', |
| 263 | + '*', |
| 264 | + array( 'en_id' => $id ), |
| 265 | + __METHOD__ ); |
| 266 | + return $row |
| 267 | + ? $row->en_type |
| 268 | + : false; |
| 269 | + } |
241 | 270 | } |
242 | 271 | |
243 | 272 | /** |
— | — | @@ -325,6 +354,12 @@ |
326 | 355 | } |
327 | 356 | return Status::newGood(); |
328 | 357 | } |
| 358 | + |
| 359 | + function getEntityType( $id ){ |
| 360 | + return array_key_exists( $this->entityInfo[$id] ) |
| 361 | + ? $this->entityInfo[$id]['type'] |
| 362 | + : false; |
| 363 | + } |
329 | 364 | } |
330 | 365 | |
331 | 366 | /** |
— | — | @@ -348,7 +383,7 @@ |
349 | 384 | 'auth' |
350 | 385 | ), |
351 | 386 | 'question' => array( 'id', 'election' ), |
352 | | - 'option' => array( 'id' ), |
| 387 | + 'option' => array( 'id', 'election' ), |
353 | 388 | ); |
354 | 389 | |
355 | 390 | /** The type of each entity child and its corresponding (plural) info element */ |
— | — | @@ -548,6 +583,11 @@ |
549 | 584 | wfDebug( __METHOD__.": missing id element in <$entityType>\n" ); |
550 | 585 | return false; |
551 | 586 | } |
| 587 | + |
| 588 | + # This has to be done after the element is fully parsed, or you |
| 589 | + # have to require 'id' to be above any children in the XML doc. |
| 590 | + $this->addParentIds( $info, $info['type'], $info['id'] ); |
| 591 | + |
552 | 592 | $id = $info['id']; |
553 | 593 | if ( isset( $info['title'] ) ) { |
554 | 594 | $this->idsByName[$info['title']] = $id; |
— | — | @@ -559,6 +599,21 @@ |
560 | 600 | $this->properties[$id] = $properties; |
561 | 601 | return $info; |
562 | 602 | } |
| 603 | + |
| 604 | + /** |
| 605 | + * Propagate parent ids to child elements |
| 606 | + */ |
| 607 | + public function addParentIds( &$info, $key, $id ) { |
| 608 | + foreach ( self::$childTypes[$info['type']] as $childType ) { |
| 609 | + if( isset( $info[$childType] ) ) { |
| 610 | + foreach ( $info[$childType] as &$child ) { |
| 611 | + $child[$key] = $id; |
| 612 | + # Recurse |
| 613 | + $this->addParentIds( $child, $key, $id ); |
| 614 | + } |
| 615 | + } |
| 616 | + } |
| 617 | + } |
563 | 618 | |
564 | 619 | /** |
565 | 620 | * When the cursor is positioned on an element node, this reads the entire |
Index: trunk/extensions/SecurePoll/includes/main/Context.php |
— | — | @@ -21,6 +21,9 @@ |
22 | 22 | |
23 | 23 | /** Message text cache */ |
24 | 24 | var $messageCache = array(); |
| 25 | + |
| 26 | + /** election cache */ |
| 27 | + var $electionCache = array(); |
25 | 28 | |
26 | 29 | /** |
27 | 30 | * Which messages are loaded. 2-d array: language and entity ID, value arbitrary. |
— | — | @@ -91,18 +94,26 @@ |
92 | 95 | $this->messageCache = $this->messagesLoaded = array(); |
93 | 96 | $this->store = $store; |
94 | 97 | } |
| 98 | + |
| 99 | + /** Get the type of a particular entity **/ |
| 100 | + function getEntityType( $id ){ |
| 101 | + return $this->getStore()->getEntityType( $id ); |
| 102 | + } |
95 | 103 | |
96 | 104 | /** |
97 | 105 | * Get an election object from the store, with a given entity ID. Returns |
98 | 106 | * false if it does not exist. |
99 | 107 | */ |
100 | 108 | function getElection( $id ) { |
101 | | - $info = $this->getStore()->getElectionInfo( array( $id ) ); |
102 | | - if ( $info ) { |
103 | | - return $this->newElection( reset( $info ) ); |
104 | | - } else { |
105 | | - return false; |
| 109 | + if( !isset( $this->electionCache[$id] ) ){ |
| 110 | + $info = $this->getStore()->getElectionInfo( array( $id ) ); |
| 111 | + if ( $info ) { |
| 112 | + $this->electionCache[$id] = $this->newElection( reset( $info ) ); |
| 113 | + } else { |
| 114 | + $this->electionCache[$id] = false; |
| 115 | + } |
106 | 116 | } |
| 117 | + return $this->electionCache[$id]; |
107 | 118 | } |
108 | 119 | |
109 | 120 | /** |