Index: trunk/extensions/SecurePoll/test/3way-test.xml |
— | — | @@ -0,0 +1,50 @@ |
| 2 | +<SecurePoll> |
| 3 | +<election> |
| 4 | +<configuration> |
| 5 | +<title>Radio range test 1</title> |
| 6 | +<ballot>radio-range</ballot> |
| 7 | +<tally>histogram-range</tally> |
| 8 | +<primaryLang>en</primaryLang> |
| 9 | +<startDate>2009-07-19T00:00:00Z</startDate> |
| 10 | +<endDate>2019-08-10T00:00:00Z</endDate> |
| 11 | +<id>50</id> |
| 12 | +<property name="admins">Tim</property> |
| 13 | +<message name="title" lang="en">RR1 test</message> |
| 14 | +<message name="intro" lang="en">Click the buttons</message> |
| 15 | +<message name="jump-text" lang="en">RR1 test jump text</message> |
| 16 | +<message name="return-text" lang="en">RR1 test return text</message> |
| 17 | +<auth>local</auth> |
| 18 | +<question> |
| 19 | +<property name="min-score">-1</property> |
| 20 | +<property name="max-score">1</property> |
| 21 | +<property name="column-label-msgs">yes</property> |
| 22 | +<property name="default-score">0</property> |
| 23 | +<message name="column-1" lang="en">Oppose</message> |
| 24 | +<message name="column0" lang="en">Abstain</message> |
| 25 | +<message name="column+1" lang="en">Support</message> |
| 26 | +<id>51</id> |
| 27 | +<message name="text" lang="en">RR1 test question</message> |
| 28 | +<option> |
| 29 | +<id>52</id> |
| 30 | +<message name="text" lang="en">AAA</message> |
| 31 | +<message name="text" lang="fr">AAA fr</message> |
| 32 | +</option> |
| 33 | +<option> |
| 34 | +<id>53</id> |
| 35 | +<message name="text" lang="en">B</message> |
| 36 | +<message name="text" lang="fr">B fr</message> |
| 37 | +</option> |
| 38 | +<option> |
| 39 | +<id>54</id> |
| 40 | +<message name="text" lang="en">C</message> |
| 41 | +<message name="text" lang="fr">C fr</message> |
| 42 | +</option> |
| 43 | +<option> |
| 44 | +<id>55</id> |
| 45 | +<message name="text" lang="en">D</message> |
| 46 | +<message name="text" lang="fr">D fr</message> |
| 47 | +</option> |
| 48 | +</question> |
| 49 | +</configuration> |
| 50 | +</election> |
| 51 | +</SecurePoll> |
Index: trunk/extensions/SecurePoll/test/approval-test.xml |
— | — | @@ -0,0 +1,43 @@ |
| 2 | +<SecurePoll> |
| 3 | +<election> |
| 4 | +<configuration> |
| 5 | +<title>Approval test</title> |
| 6 | +<ballot>approval</ballot> |
| 7 | +<tally>range</tally> |
| 8 | +<primaryLang>en</primaryLang> |
| 9 | +<startDate>2009-07-19T00:00:00Z</startDate> |
| 10 | +<endDate>2019-08-10T00:00:00Z</endDate> |
| 11 | +<id>40</id> |
| 12 | +<property name="admins">Tim</property> |
| 13 | +<message name="title" lang="en">Approval test</message> |
| 14 | +<message name="intro" lang="en">Approval test intro</message> |
| 15 | +<message name="jump-text" lang="en">Approval test jump text</message> |
| 16 | +<message name="return-text" lang="en">Approval test return text</message> |
| 17 | +<auth>local</auth> |
| 18 | +<question> |
| 19 | +<id>41</id> |
| 20 | +<message name="text" lang="en">Approval test question</message> |
| 21 | +<option> |
| 22 | +<id>42</id> |
| 23 | +<message name="text" lang="en">AAAA</message> |
| 24 | +<message name="text" lang="fr">AAAA fr</message> |
| 25 | +</option> |
| 26 | +<option> |
| 27 | +<id>43</id> |
| 28 | +<message name="text" lang="en">B</message> |
| 29 | +<message name="text" lang="fr">B fr</message> |
| 30 | +</option> |
| 31 | +<option> |
| 32 | +<id>44</id> |
| 33 | +<message name="text" lang="en">C</message> |
| 34 | +<message name="text" lang="fr">C fr</message> |
| 35 | +</option> |
| 36 | +<option> |
| 37 | +<id>45</id> |
| 38 | +<message name="text" lang="en">D</message> |
| 39 | +<message name="text" lang="fr">D fr</message> |
| 40 | +</option> |
| 41 | +</question> |
| 42 | +</configuration> |
| 43 | +</election> |
| 44 | +</SecurePoll> |
Index: trunk/extensions/SecurePoll/test/radio-range.xml |
— | — | @@ -0,0 +1,38 @@ |
| 2 | +<SecurePoll> |
| 3 | +<election> |
| 4 | +<configuration> |
| 5 | +<title>Radio range test 2</title> |
| 6 | +<ballot>radio-range</ballot> |
| 7 | +<tally>histogram-range</tally> |
| 8 | +<primaryLang>en</primaryLang> |
| 9 | +<startDate>2009-07-19T00:00:00Z</startDate> |
| 10 | +<endDate>2019-08-10T00:00:00Z</endDate> |
| 11 | +<id>60</id> |
| 12 | +<property name="admins">Tim</property> |
| 13 | +<property name="must-answer-all">yes</property> |
| 14 | +<message name="title" lang="en">RR2 test</message> |
| 15 | +<message name="intro" lang="en">RR2 test intro</message> |
| 16 | +<message name="jump-text" lang="en">RR2 test jump text</message> |
| 17 | +<message name="return-text" lang="en">RR2 test return text</message> |
| 18 | +<auth>local</auth> |
| 19 | +<question> |
| 20 | +<property name="min-score">1</property> |
| 21 | +<property name="max-score">5</property> |
| 22 | +<id>61</id> |
| 23 | +<message name="text" lang="en">Indicate how much you agree with the following statements by choosing a number from 1 to 5, where 1=strongly disagree, 5=strongly agree.</message> |
| 24 | +<option> |
| 25 | +<id>62</id> |
| 26 | +<message name="text" lang="en">I like icecream.</message> |
| 27 | +</option> |
| 28 | +<option> |
| 29 | +<id>63</id> |
| 30 | +<message name="text" lang="en">Kittens are evil.</message> |
| 31 | +</option> |
| 32 | +<option> |
| 33 | +<id>64</id> |
| 34 | +<message name="text" lang="en">Radiative forcing due to a doubling of atmospheric CO<sub>2</sub> is 3.7 W/m<sup>2</sup>.</message> |
| 35 | +</option> |
| 36 | +</question> |
| 37 | +</configuration> |
| 38 | +</election> |
| 39 | +</SecurePoll> |
Index: trunk/extensions/SecurePoll/test/schulze-test.xml |
— | — | @@ -0,0 +1,43 @@ |
| 2 | +<SecurePoll> |
| 3 | +<election> |
| 4 | +<configuration> |
| 5 | +<title>Schulze test</title> |
| 6 | +<ballot>preferential</ballot> |
| 7 | +<tally>schulze</tally> |
| 8 | +<primaryLang>en</primaryLang> |
| 9 | +<startDate>2009-07-19T00:00:00Z</startDate> |
| 10 | +<endDate>2019-08-10T00:00:00Z</endDate> |
| 11 | +<id>11</id> |
| 12 | +<property name="admins">Tim</property> |
| 13 | +<message name="title" lang="en">Schulze test</message> |
| 14 | +<message name="intro" lang="en">Schulze test intro</message> |
| 15 | +<message name="jump-text" lang="en">Schulze test jump text</message> |
| 16 | +<message name="return-text" lang="en">Schulze test return text</message> |
| 17 | +<auth>local</auth> |
| 18 | +<question> |
| 19 | +<id>12</id> |
| 20 | +<message name="text" lang="en">Schulze test question</message> |
| 21 | +<option> |
| 22 | +<id>13</id> |
| 23 | +<message name="text" lang="en">A</message> |
| 24 | +<message name="text" lang="fr">A fr</message> |
| 25 | +</option> |
| 26 | +<option> |
| 27 | +<id>14</id> |
| 28 | +<message name="text" lang="en">B</message> |
| 29 | +<message name="text" lang="fr">B fr</message> |
| 30 | +</option> |
| 31 | +<option> |
| 32 | +<id>15</id> |
| 33 | +<message name="text" lang="en">C</message> |
| 34 | +<message name="text" lang="fr">C fr</message> |
| 35 | +</option> |
| 36 | +<option> |
| 37 | +<id>16</id> |
| 38 | +<message name="text" lang="en">D</message> |
| 39 | +<message name="text" lang="fr">D fr</message> |
| 40 | +</option> |
| 41 | +</question> |
| 42 | +</configuration> |
| 43 | +</election> |
| 44 | +</SecurePoll> |
Index: trunk/extensions/SecurePoll/SecurePoll.i18n.php |
— | — | @@ -62,6 +62,8 @@ |
63 | 63 | 'securepoll-invalid-rank' => 'Invalid rank. You must give candidates a rank between 1 and 999.', |
64 | 64 | 'securepoll-unranked-options' => 'Some options were not ranked. |
65 | 65 | You must give all options a rank between 1 and 999.', |
| 66 | + 'securepoll-invalid-score' => 'The score must be a number between $1 and $2.', |
| 67 | + 'securepoll-unanswered-options' => 'You must give a response for every question.', |
66 | 68 | |
67 | 69 | # Authorisation related |
68 | 70 | 'securepoll-remote-auth-error' => 'Error fetching your account information from the server.', |
Index: trunk/extensions/SecurePoll/SecurePoll.php |
— | — | @@ -49,9 +49,11 @@ |
50 | 50 | |
51 | 51 | $wgAutoloadClasses = $wgAutoloadClasses + array( |
52 | 52 | # ballots |
| 53 | + 'SecurePoll_ApprovalBallot' => "$dir/includes/ballots/ApprovalBallot.php", |
53 | 54 | 'SecurePoll_Ballot' => "$dir/includes/ballots/Ballot.php", |
54 | 55 | 'SecurePoll_ChooseBallot' => "$dir/includes/ballots/ChooseBallot.php", |
55 | 56 | 'SecurePoll_PreferentialBallot' => "$dir/includes/ballots/PreferentialBallot.php", |
| 57 | + 'SecurePoll_RadioRangeBallot' => "$dir/includes/ballots/RadioRangeBallot.php", |
56 | 58 | |
57 | 59 | # crypt |
58 | 60 | 'SecurePoll_Crypt' => "$dir/includes/crypt/Crypt.php", |
Index: trunk/extensions/SecurePoll/includes/pages/VotePage.php |
— | — | @@ -97,7 +97,7 @@ |
98 | 98 | /** |
99 | 99 | * Show the voting form. |
100 | 100 | */ |
101 | | - function showForm() { |
| 101 | + function showForm( $status = false ) { |
102 | 102 | global $wgOut; |
103 | 103 | |
104 | 104 | // Show introduction |
— | — | @@ -114,7 +114,8 @@ |
115 | 115 | |
116 | 116 | $wgOut->addHTML( |
117 | 117 | "<form name=\"securepoll\" id=\"securepoll\" method=\"post\" action=\"$encAction\">\n" . |
118 | | - $this->election->getBallot()->getForm() . |
| 118 | + $this->election->getBallot()->getForm( $status ) . |
| 119 | + "<br/>\n" . |
119 | 120 | "<input name=\"submit\" type=\"submit\" value=\"$encOK\">\n" . |
120 | 121 | "<input type='hidden' name='edit_token' value=\"{$encToken}\" /></td>\n" . |
121 | 122 | "</form>" |
— | — | @@ -130,10 +131,7 @@ |
131 | 132 | $ballot = $this->election->getBallot(); |
132 | 133 | $status = $ballot->submitForm(); |
133 | 134 | if ( !$status->isOK() ) { |
134 | | - $wgOut->addWikiText( '<div class="securepoll-error-box">' . |
135 | | - $status->getWikiText( 'securepoll-bad-ballot-submission' ) . |
136 | | - '</div>' ); |
137 | | - $this->showForm(); |
| 135 | + $this->showForm( $status ); |
138 | 136 | } else { |
139 | 137 | $this->logVote( $status->value ); |
140 | 138 | } |
Index: trunk/extensions/SecurePoll/includes/talliers/Tallier.php |
— | — | @@ -34,7 +34,7 @@ |
35 | 35 | } |
36 | 36 | |
37 | 37 | function convertRanksToHtml( $ranks ) { |
38 | | - $s = "<table class=\"securepoll-results\">"; |
| 38 | + $s = "<table class=\"securepoll-table\">"; |
39 | 39 | $ids = array_keys( $ranks ); |
40 | 40 | foreach ( $ids as $i => $oid ) { |
41 | 41 | $rank = $ranks[$oid]; |
Index: trunk/extensions/SecurePoll/includes/ballots/RadioRangeBallot.php |
— | — | @@ -0,0 +1,213 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * A ballot form for range voting where the number of allowed responses is small, |
| 6 | + * allowing a radio button table interface and histogram tallying. |
| 7 | + * |
| 8 | + * Election properties: |
| 9 | + * must-answer-all |
| 10 | + * |
| 11 | + * Question properties: |
| 12 | + * min-score |
| 13 | + * max-score |
| 14 | + * column-label-msgs |
| 15 | + * |
| 16 | + * Question messages: |
| 17 | + * column-1, column0, column+1, etc. |
| 18 | + */ |
| 19 | +class SecurePoll_RadioRangeBallot extends SecurePoll_Ballot { |
| 20 | + var $columnLabels, $minMax; |
| 21 | + |
| 22 | + function getTallyTypes() { |
| 23 | + return array( 'plurality', 'histogram-range' ); |
| 24 | + } |
| 25 | + |
| 26 | + function getMinMax( $question ) { |
| 27 | + $min = intval( $question->getProperty( 'min-score' ) ); |
| 28 | + $max = intval( $question->getProperty( 'max-score' ) ); |
| 29 | + if ( $max <= $min ) { |
| 30 | + throw new MWException( __METHOD__.': min/max not configured' ); |
| 31 | + } |
| 32 | + return array( $min, $max ); |
| 33 | + } |
| 34 | + |
| 35 | + function getColumnLabels( $question ) { |
| 36 | + list( $min, $max ) = $this->getMinMax( $question ); |
| 37 | + $labels = array(); |
| 38 | + $useMessageLabels = $question->getProperty( 'column-label-msgs' ); |
| 39 | + if ( $useMessageLabels ) { |
| 40 | + for ( $score = $min; $score <= $max; $score++ ) { |
| 41 | + $signedScore = $this->addSign( $question, $score ); |
| 42 | + $labels[$score] = $question->getMessage( "column$signedScore" ); |
| 43 | + } |
| 44 | + } else { |
| 45 | + global $wgLang; |
| 46 | + for ( $score = $min; $score <= $max; $score++ ) { |
| 47 | + $labels[$score] = $wgLang->formatNum( $score ); |
| 48 | + } |
| 49 | + } |
| 50 | + return $labels; |
| 51 | + } |
| 52 | + |
| 53 | + function getMessageNames( $entity ) { |
| 54 | + if ( $entity->getType() !== 'question' ) { |
| 55 | + return array(); |
| 56 | + } |
| 57 | + if ( !$entity->getProperty( 'column-label-msgs' ) ) { |
| 58 | + return array(); |
| 59 | + } |
| 60 | + $msgs = array(); |
| 61 | + list( $min, $max ) = $this->getMinMax( $entity ); |
| 62 | + for ( $score = min; $score <= $max; $score++ ) { |
| 63 | + $signedScore = $this->addSign( $entity, $score ); |
| 64 | + $msgs[] = "column$signedScore"; |
| 65 | + } |
| 66 | + return $msgs; |
| 67 | + } |
| 68 | + |
| 69 | + function addSign( $question, $score ) { |
| 70 | + list( $min, $max ) = $this->getMinMax( $question ); |
| 71 | + if ( $min < 0 && $score > 0 ) { |
| 72 | + return "+$score"; |
| 73 | + } else { |
| 74 | + return $score; |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + function getQuestionForm( $question, $options ) { |
| 79 | + global $wgRequest; |
| 80 | + $name = 'securepoll_q' . $question->getId(); |
| 81 | + list( $min, $max ) = $this->getMinMax( $question ); |
| 82 | + $labels = $this->getColumnLabels( $question ); |
| 83 | + |
| 84 | + $s = "<table class=\"securepoll-ballot-table\">\n" . |
| 85 | + "<tr>\n" . |
| 86 | + "<th> </th>\n"; |
| 87 | + foreach ( $labels as $label ) { |
| 88 | + $s .= Xml::element( 'th', array(), $label ) . "\n"; |
| 89 | + } |
| 90 | + $defaultScore = $question->getProperty( 'default-score' ); |
| 91 | + |
| 92 | + foreach ( $options as $option ) { |
| 93 | + $optionHTML = $option->parseMessageInline( 'text' ); |
| 94 | + $optionId = $option->getId(); |
| 95 | + $inputId = "{$name}_opt{$optionId}"; |
| 96 | + $oldValue = $wgRequest->getVal( $inputId, $defaultScore ); |
| 97 | + $s .= "<tr class=\"securepoll-ballot-row\">\n" . |
| 98 | + Xml::tags( 'td', |
| 99 | + array( 'class' => 'securepoll-ballot-optlabel' ), |
| 100 | + $this->errorLocationIndicator( $inputId ) . $optionHTML |
| 101 | + ); |
| 102 | + |
| 103 | + foreach ( $labels as $score => $label ) { |
| 104 | + $s .= |
| 105 | + Xml::tags( 'td', array(), |
| 106 | + Xml::radio( $inputId, $score, !strcmp( $oldValue, $score ), |
| 107 | + array( 'title' => $label ) ) |
| 108 | + ) . "\n"; |
| 109 | + } |
| 110 | + $s .= "</tr>\n"; |
| 111 | + } |
| 112 | + $s .= "</table>\n"; |
| 113 | + return $s; |
| 114 | + } |
| 115 | + |
| 116 | + function submitQuestion( $question, $status ) { |
| 117 | + global $wgRequest, $wgLang; |
| 118 | + |
| 119 | + $options = $question->getOptions(); |
| 120 | + $record = ''; |
| 121 | + $ok = true; |
| 122 | + list( $min, $max ) = $this->getMinMax( $question ); |
| 123 | + $defaultScore = $question->getProperty( 'default-score' ); |
| 124 | + foreach ( $options as $option ) { |
| 125 | + $id = 'securepoll_q' . $question->getId() . '_opt' . $option->getId(); |
| 126 | + $score = $wgRequest->getVal( $id ); |
| 127 | + |
| 128 | + if ( is_numeric( $score ) ) { |
| 129 | + if ( $score < $min || $score > $max ) { |
| 130 | + $status->sp_fatal( 'securepoll-invalid-score', $id, |
| 131 | + $wgLang->formatNum( $min ), $wgLang->formatNum( $max ) ); |
| 132 | + $ok = false; |
| 133 | + continue; |
| 134 | + } else { |
| 135 | + $score = intval( $score ); |
| 136 | + } |
| 137 | + } elseif ( strval( $score ) === '' ) { |
| 138 | + if ( $this->election->getProperty( 'must-answer-all' ) ) { |
| 139 | + $status->sp_fatal( 'securepoll-unanswered-options', $id ); |
| 140 | + $ok = false; |
| 141 | + continue; |
| 142 | + } else { |
| 143 | + $score = $defaultScore; |
| 144 | + } |
| 145 | + } else { |
| 146 | + $status->sp_fatal( 'securepoll-invalid-score', $id, |
| 147 | + $wgLang->formatNum( $min ), $wgLang->formatNum( $max ) ); |
| 148 | + $ok = false; |
| 149 | + continue; |
| 150 | + } |
| 151 | + $record .= sprintf( 'Q%08X-A%08X-S%+011d--', |
| 152 | + $question->getId(), $option->getId(), $score ); |
| 153 | + } |
| 154 | + if ( $ok ) { |
| 155 | + return $record; |
| 156 | + } |
| 157 | + } |
| 158 | + |
| 159 | + function unpackRecord( $record ) { |
| 160 | + $scores = array(); |
| 161 | + $itemLength = 8 + 8 + 11 + 7; |
| 162 | + $questions = array(); |
| 163 | + foreach ( $this->election->getQuestions() as $question ) { |
| 164 | + $questions[$question->getId()] = $question; |
| 165 | + } |
| 166 | + for ( $offset = 0; $offset < strlen( $record ); $offset += $itemLength ) { |
| 167 | + if ( !preg_match( '/Q([0-9A-F]{8})-A([0-9A-F]{8})-S([+-][0-9]{10})--/A', |
| 168 | + $record, $m, 0, $offset ) ) |
| 169 | + { |
| 170 | + wfDebug( __METHOD__.": regex doesn't match\n" ); |
| 171 | + return false; |
| 172 | + } |
| 173 | + $qid = intval( base_convert( $m[1], 16, 10 ) ); |
| 174 | + $oid = intval( base_convert( $m[2], 16, 10 ) ); |
| 175 | + $score = intval( $m[3] ); |
| 176 | + if ( !isset( $questions[$qid] ) ) { |
| 177 | + wfDebug( __METHOD__.": invalid question ID\n" ); |
| 178 | + return false; |
| 179 | + } |
| 180 | + list( $min, $max ) = $this->getMinMax( $questions[$qid] ); |
| 181 | + if ( $score < $min || $score > $max ) { |
| 182 | + wfDebug( __METHOD__.": score out of range\n" ); |
| 183 | + } |
| 184 | + $scores[$qid][$oid] = $score; |
| 185 | + } |
| 186 | + return $scores; |
| 187 | + } |
| 188 | + |
| 189 | + function convertScores( $scores, $params = array() ) { |
| 190 | + $result = array(); |
| 191 | + foreach ( $this->election->getQuestions() as $question ) { |
| 192 | + $qid = $question->getId(); |
| 193 | + if ( !isset( $scores[$qid] ) ) { |
| 194 | + return false; |
| 195 | + } |
| 196 | + $s = ''; |
| 197 | + $qscores = $scores[$qid]; |
| 198 | + ksort( $qscores ); |
| 199 | + $first = true; |
| 200 | + foreach ( $qscores as $score ) { |
| 201 | + if ( $first ) { |
| 202 | + $first = false; |
| 203 | + } else { |
| 204 | + $s .= ', '; |
| 205 | + } |
| 206 | + $s .= $score; |
| 207 | + } |
| 208 | + $result[$qid] = $s; |
| 209 | + } |
| 210 | + return $result; |
| 211 | + } |
| 212 | +} |
| 213 | + |
| 214 | + |
Property changes on: trunk/extensions/SecurePoll/includes/ballots/RadioRangeBallot.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 215 | + native |
Index: trunk/extensions/SecurePoll/includes/ballots/Ballot.php |
— | — | @@ -16,9 +16,11 @@ |
17 | 17 | /** |
18 | 18 | * Get the HTML form segment for a single question |
19 | 19 | * @param $question SecurePoll_Question |
| 20 | + * @param $options Array of options, in the order they should be displayed |
| 21 | + * @param $prevStatus Status of previous form submission |
20 | 22 | * @return string |
21 | 23 | */ |
22 | | - abstract function getQuestionForm( $question ); |
| 24 | + abstract function getQuestionForm( $question, $options ); |
23 | 25 | |
24 | 26 | /** |
25 | 27 | * Called when the form is submitted. This returns a Status object which, |
— | — | @@ -26,9 +28,32 @@ |
27 | 29 | * preserve voter privacy, voting records should be the same length |
28 | 30 | * regardless of voter choices. |
29 | 31 | */ |
30 | | - abstract function submitForm(); |
| 32 | + function submitForm() { |
| 33 | + $questions = $this->election->getQuestions(); |
| 34 | + $record = ''; |
| 35 | + $status = new SecurePoll_BallotStatus( $this->context ); |
31 | 36 | |
| 37 | + foreach ( $questions as $question ) { |
| 38 | + $record .= $this->submitQuestion( $question, $status ); |
| 39 | + } |
| 40 | + if ( $status->isOK() ) { |
| 41 | + $status->value = $record . "\n"; |
| 42 | + } |
| 43 | + return $status; |
| 44 | + } |
| 45 | + |
32 | 46 | /** |
| 47 | + * Construct a string record for a given question, during form submission. |
| 48 | + * |
| 49 | + * If there is a problem with the form data, the function should set a |
| 50 | + * fatal error in the $status object and return null. |
| 51 | + * |
| 52 | + * @param Status |
| 53 | + * @return string |
| 54 | + */ |
| 55 | + abstract function submitQuestion( $question, $status ); |
| 56 | + |
| 57 | + /** |
33 | 58 | * Unpack a string record into an array format suitable for the tally type |
34 | 59 | */ |
35 | 60 | abstract function unpackRecord( $record ); |
— | — | @@ -60,6 +85,8 @@ |
61 | 86 | return new SecurePoll_PreferentialBallot( $context, $election ); |
62 | 87 | case 'choose': |
63 | 88 | return new SecurePoll_ChooseBallot( $context, $election ); |
| 89 | + case 'radio-range': |
| 90 | + return new SecurePoll_RadioRangeBallot( $context, $election ); |
64 | 91 | default: |
65 | 92 | throw new MWException( "Invalid ballot type: $type" ); |
66 | 93 | } |
— | — | @@ -80,21 +107,128 @@ |
81 | 108 | * they will be added by the VotePage. |
82 | 109 | * @return string |
83 | 110 | */ |
84 | | - function getForm() { |
| 111 | + function getForm( $prevStatus = false ) { |
85 | 112 | global $wgParser, $wgTitle; |
86 | 113 | $questions = $this->election->getQuestions(); |
87 | 114 | if ( $this->election->getProperty( 'shuffle-questions' ) ) { |
88 | 115 | shuffle( $questions ); |
89 | 116 | } |
| 117 | + $shuffleOptions = $this->election->getProperty( 'shuffle-options' ); |
| 118 | + $this->setErrorStatus( $prevStatus ); |
90 | 119 | |
91 | 120 | $s = ''; |
92 | 121 | foreach ( $questions as $question ) { |
| 122 | + $options = $question->getOptions(); |
| 123 | + if ( $shuffleOptions ) { |
| 124 | + shuffle( $options ); |
| 125 | + } |
93 | 126 | $s .= "<hr/>\n" . |
94 | 127 | $question->parseMessage( 'text' ) . |
95 | | - $this->getQuestionForm( $question ) . |
| 128 | + $this->getQuestionForm( $question, $options ) . |
96 | 129 | "\n"; |
97 | 130 | } |
| 131 | + if ( $prevStatus ) { |
| 132 | + $s = $this->formatStatus( $prevStatus ) . $s; |
| 133 | + } |
98 | 134 | return $s; |
99 | 135 | } |
| 136 | + |
| 137 | + function setErrorStatus( $status ) { |
| 138 | + if ( $status ) { |
| 139 | + $this->prevErrorIds = $status->sp_getIds(); |
| 140 | + $this->prevStatus = $status; |
| 141 | + } else { |
| 142 | + $this->prevErrorIds = array(); |
| 143 | + } |
| 144 | + $this->usedErrorIds = array(); |
| 145 | + } |
| 146 | + |
| 147 | + function errorLocationIndicator( $id ) { |
| 148 | + if ( !isset( $this->prevErrorIds[$id] ) ) { |
| 149 | + return ''; |
| 150 | + } |
| 151 | + $this->usedErrorIds[$id] = true; |
| 152 | + return |
| 153 | + Xml::element( 'img', array( |
| 154 | + 'src' => $this->context->getResourceUrl( 'warning-22.png' ), |
| 155 | + 'width' => 22, |
| 156 | + 'height' => 22, |
| 157 | + 'id' => "$id-location", |
| 158 | + 'class' => 'securepoll-error-location', |
| 159 | + 'alt' => '', |
| 160 | + 'title' => $this->prevStatus->sp_getMessageText( $id ) |
| 161 | + ) ); |
| 162 | + } |
| 163 | + |
| 164 | + /** |
| 165 | + * Convert a SecurePoll_BallotStatus object to HTML |
| 166 | + */ |
| 167 | + function formatStatus( $status ) { |
| 168 | + return $status->sp_getHTML( $this->usedErrorIds ); |
| 169 | + } |
100 | 170 | } |
101 | 171 | |
| 172 | +class SecurePoll_BallotStatus extends Status { |
| 173 | + var $sp_context; |
| 174 | + var $sp_ids = array(); |
| 175 | + |
| 176 | + function __construct( $context ) { |
| 177 | + $this->sp_context = $context; |
| 178 | + } |
| 179 | + |
| 180 | + function sp_fatal( $message, $id /*, parameters... */ ) { |
| 181 | + $params = array_slice( func_get_args(), 2 ); |
| 182 | + $this->errors[] = array( |
| 183 | + 'type' => 'error', |
| 184 | + 'securepoll-id' => $id, |
| 185 | + 'message' => $message, |
| 186 | + 'params' => $params ); |
| 187 | + $this->sp_ids[$id] = true; |
| 188 | + $this->ok = false; |
| 189 | + } |
| 190 | + |
| 191 | + function sp_getIds() { |
| 192 | + return $this->sp_ids; |
| 193 | + } |
| 194 | + |
| 195 | + function sp_getHTML( $usedIds ) { |
| 196 | + if ( !$this->errors ) { |
| 197 | + return ''; |
| 198 | + } |
| 199 | + $s = '<ul class="securepoll-error-box">'; |
| 200 | + foreach ( $this->errors as $error ) { |
| 201 | + $text = wfMsgReal( $error['message'], $error['params'] ); |
| 202 | + if ( isset( $error['securepoll-id'] ) ) { |
| 203 | + $id = $error['securepoll-id']; |
| 204 | + if ( isset( $usedIds[$id] ) ) { |
| 205 | + $s .= '<li>' . |
| 206 | + Xml::openElement( 'a', array( |
| 207 | + 'href' => '#' . urlencode( "$id-location" ), |
| 208 | + 'class' => 'securepoll-error-jump' |
| 209 | + ) ) . |
| 210 | + Xml::element( 'img', array( |
| 211 | + 'alt' => '', |
| 212 | + 'src' => $this->sp_context->getResourceUrl( 'down-16.png' ), |
| 213 | + ) ) . |
| 214 | + '</a>' . |
| 215 | + htmlspecialchars( $text ) . |
| 216 | + "</li>\n"; |
| 217 | + continue; |
| 218 | + } |
| 219 | + } |
| 220 | + $s .= '<li>' . htmlspecialchars( $text ) . "</li>\n"; |
| 221 | + } |
| 222 | + $s .= "</ul>\n"; |
| 223 | + $s .= '<script type="text/javascript"> securepoll_ballot_setup(); </script>'; |
| 224 | + return $s; |
| 225 | + } |
| 226 | + |
| 227 | + function sp_getMessageText( $id ) { |
| 228 | + foreach ( $this->errors as $error ) { |
| 229 | + if ( $error['securepoll-id'] !== $id ) { |
| 230 | + continue; |
| 231 | + } |
| 232 | + return wfMsgReal( $error['message'], $error['params'] ); |
| 233 | + } |
| 234 | + } |
| 235 | +} |
Index: trunk/extensions/SecurePoll/includes/ballots/ChooseBallot.php |
— | — | @@ -3,10 +3,6 @@ |
4 | 4 | /** |
5 | 5 | * A ballot class which asks the user to choose one answer only from the |
6 | 6 | * given options, for each question. |
7 | | - * |
8 | | - * The following election properties are used: |
9 | | - * shuffle-questions when present and true, the questions are shown in random order |
10 | | - * shuffle-options when present and true, the options are shown in random order |
11 | 7 | */ |
12 | 8 | class SecurePoll_ChooseBallot extends SecurePoll_Ballot { |
13 | 9 | /** |
— | — | @@ -21,13 +17,10 @@ |
22 | 18 | /** |
23 | 19 | * Get the HTML form segment for a single question |
24 | 20 | * @param $question SecurePoll_Question |
| 21 | + * @param $options Array of options, in the order they should be displayed |
25 | 22 | * @return string |
26 | 23 | */ |
27 | | - function getQuestionForm( $question ) { |
28 | | - $options = $question->getChildren(); |
29 | | - if ( $this->election->getProperty( 'shuffle-options' ) ) { |
30 | | - shuffle( $options ); |
31 | | - } |
| 24 | + function getQuestionForm( $question, $options ) { |
32 | 25 | $name = 'securepoll_q' . $question->getId(); |
33 | 26 | $s = ''; |
34 | 27 | foreach ( $options as $option ) { |
— | — | @@ -44,23 +37,14 @@ |
45 | 38 | return $s; |
46 | 39 | } |
47 | 40 | |
48 | | - /** |
49 | | - * Called when the form is submitted. |
50 | | - * @return Status |
51 | | - */ |
52 | | - function submitForm() { |
| 41 | + function submitQuestion( $question, $status ) { |
53 | 42 | global $wgRequest; |
54 | | - $questions = $this->election->getQuestions(); |
55 | | - $record = ''; |
56 | | - foreach ( $questions as $question ) { |
57 | | - $result = $wgRequest->getInt( 'securepoll_q' . $question->getId() ); |
58 | | - if ( !$result ) { |
59 | | - return Status::newFatal( 'securepoll-unanswered-questions' ); |
60 | | - } |
61 | | - $record .= $this->packRecord( $question->getId(), $result ); |
| 43 | + $result = $wgRequest->getInt( 'securepoll_q' . $question->getId() ); |
| 44 | + if ( !$result ) { |
| 45 | + $status->fatal( 'securepoll-unanswered-questions' ); |
| 46 | + } else { |
| 47 | + return $this->packRecord( $question->getId(), $result ); |
62 | 48 | } |
63 | | - $record .= "\n"; |
64 | | - return Status::newGood( $record ); |
65 | 49 | } |
66 | 50 | |
67 | 51 | function packRecord( $qid, $oid ) { |
— | — | @@ -82,7 +66,7 @@ |
83 | 67 | return $result; |
84 | 68 | } |
85 | 69 | |
86 | | - function convertScores( $scores, $options = array() ) { |
| 70 | + function convertScores( $scores, $params = array() ) { |
87 | 71 | $s = ''; |
88 | 72 | foreach ( $this->election->getQuestions() as $question ) { |
89 | 73 | $qid = $question->getId(); |
— | — | @@ -92,7 +76,7 @@ |
93 | 77 | if ( $s !== '' ) { |
94 | 78 | $s .= '; '; |
95 | 79 | } |
96 | | - $oid = keys( $scores ); |
| 80 | + $oid = key( $scores ); |
97 | 81 | $option = $this->election->getOption( $oid ); |
98 | 82 | $s .= $option->getMessage( 'name' ); |
99 | 83 | } |
Index: trunk/extensions/SecurePoll/includes/ballots/PreferentialBallot.php |
— | — | @@ -12,12 +12,8 @@ |
13 | 13 | return array( 'schulze' ); |
14 | 14 | } |
15 | 15 | |
16 | | - function getQuestionForm( $question ) { |
| 16 | + function getQuestionForm( $question, $options ) { |
17 | 17 | global $wgRequest; |
18 | | - $options = $question->getChildren(); |
19 | | - if ( $this->election->getProperty( 'shuffle-options' ) ) { |
20 | | - shuffle( $options ); |
21 | | - } |
22 | 18 | $name = 'securepoll_q' . $question->getId(); |
23 | 19 | $s = ''; |
24 | 20 | foreach ( $options as $option ) { |
— | — | @@ -32,6 +28,7 @@ |
33 | 29 | 'maxlength' => 3, |
34 | 30 | ) ) . |
35 | 31 | ' ' . |
| 32 | + $this->errorLocationIndicator( $inputId ) . |
36 | 33 | Xml::tags( 'label', array( 'for' => $inputId ), $optionHTML ) . |
37 | 34 | ' ' . |
38 | 35 | "</div>\n"; |
— | — | @@ -39,44 +36,43 @@ |
40 | 37 | return $s; |
41 | 38 | } |
42 | 39 | |
43 | | - function submitForm() { |
| 40 | + function submitQuestion( $question, $status ) { |
44 | 41 | global $wgRequest; |
45 | | - $questions = $this->election->getQuestions(); |
| 42 | + |
| 43 | + $options = $question->getOptions(); |
46 | 44 | $record = ''; |
47 | | - $status = Status::newGood(); |
| 45 | + $ok = true; |
| 46 | + foreach ( $options as $option ) { |
| 47 | + $id = 'securepoll_q' . $question->getId() . '_opt' . $option->getId(); |
| 48 | + $rank = $wgRequest->getVal( $id ); |
48 | 49 | |
49 | | - foreach ( $questions as $question ) { |
50 | | - $options = $question->getOptions(); |
51 | | - foreach ( $options as $option ) { |
52 | | - $id = 'securepoll_q' . $question->getId() . '_opt' . $option->getId(); |
53 | | - $rank = $wgRequest->getVal( $id ); |
54 | | - |
55 | | - if ( is_numeric( $rank ) ) { |
56 | | - if ( $rank <= 0 || $rank >= 1000 ) { |
57 | | - $status->fatal( 'securepoll-invalid-rank', $id ); |
58 | | - continue; |
59 | | - } else { |
60 | | - $rank = intval( $rank ); |
61 | | - } |
62 | | - } elseif ( strval( $rank ) === '' ) { |
63 | | - if ( $this->election->getProperty( 'must-rank-all' ) ) { |
64 | | - $status->fatal( 'securepoll-unranked-options', $id ); |
65 | | - continue; |
66 | | - } else { |
67 | | - $rank = 1000; |
68 | | - } |
| 50 | + if ( is_numeric( $rank ) ) { |
| 51 | + if ( $rank <= 0 || $rank >= 1000 ) { |
| 52 | + $status->sp_fatal( 'securepoll-invalid-rank', $id ); |
| 53 | + $ok = false; |
| 54 | + continue; |
69 | 55 | } else { |
70 | | - $status->fatal( 'securepoll-invalid-rank', $id ); |
| 56 | + $rank = intval( $rank ); |
| 57 | + } |
| 58 | + } elseif ( strval( $rank ) === '' ) { |
| 59 | + if ( $this->election->getProperty( 'must-rank-all' ) ) { |
| 60 | + $status->sp_fatal( 'securepoll-unranked-options', $id ); |
| 61 | + $ok = false; |
71 | 62 | continue; |
| 63 | + } else { |
| 64 | + $rank = 1000; |
72 | 65 | } |
73 | | - $record .= sprintf( 'Q%08X-A%08X-R%08X--', |
74 | | - $question->getId(), $option->getId(), $rank ); |
| 66 | + } else { |
| 67 | + $status->sp_fatal( 'securepoll-invalid-rank', $id ); |
| 68 | + $ok = false; |
| 69 | + continue; |
75 | 70 | } |
| 71 | + $record .= sprintf( 'Q%08X-A%08X-R%08X--', |
| 72 | + $question->getId(), $option->getId(), $rank ); |
76 | 73 | } |
77 | | - if ( $status->isOK() ) { |
78 | | - $status->value = $record . "\n"; |
| 74 | + if ( $ok ) { |
| 75 | + return $record; |
79 | 76 | } |
80 | | - return $status; |
81 | 77 | } |
82 | 78 | |
83 | 79 | function unpackRecord( $record ) { |
— | — | @@ -97,7 +93,7 @@ |
98 | 94 | return $ranks; |
99 | 95 | } |
100 | 96 | |
101 | | - function convertScores( $scores, $options = array() ) { |
| 97 | + function convertScores( $scores, $params = array() ) { |
102 | 98 | $result = array(); |
103 | 99 | foreach ( $this->election->getQuestions() as $question ) { |
104 | 100 | $qid = $question->getId(); |
Index: trunk/extensions/SecurePoll/includes/ballots/ApprovalBallot.php |
— | — | @@ -0,0 +1,88 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * Checkbox approval voting. |
| 6 | + */ |
| 7 | +class SecurePoll_ApprovalBallot extends SecurePoll_Ballot { |
| 8 | + function getTallyTypes() { |
| 9 | + return array( 'plurality' ); |
| 10 | + } |
| 11 | + |
| 12 | + function getQuestionForm( $question, $options ) { |
| 13 | + global $wgRequest; |
| 14 | + $name = 'securepoll_q' . $question->getId(); |
| 15 | + $s = ''; |
| 16 | + foreach ( $options as $option ) { |
| 17 | + $optionHTML = $option->parseMessageInline( 'text' ); |
| 18 | + $optionId = $option->getId(); |
| 19 | + $inputId = "{$name}_opt{$optionId}"; |
| 20 | + $oldValue = $wgRequest->getBool( $inputId ); |
| 21 | + $s .= |
| 22 | + '<div class="securepoll-option-approval">' . |
| 23 | + Xml::check( $inputId, $oldValue, array( 'id' => $inputId ) ) . |
| 24 | + ' ' . |
| 25 | + Xml::tags( 'label', array( 'for' => $inputId ), $optionHTML ) . |
| 26 | + ' ' . |
| 27 | + "</div>\n"; |
| 28 | + } |
| 29 | + return $s; |
| 30 | + } |
| 31 | + |
| 32 | + function submitQuestion( $question, $status ) { |
| 33 | + global $wgRequest; |
| 34 | + |
| 35 | + $options = $question->getOptions(); |
| 36 | + $record = ''; |
| 37 | + foreach ( $options as $option ) { |
| 38 | + $id = 'securepoll_q' . $question->getId() . '_opt' . $option->getId(); |
| 39 | + $checked = $wgRequest->getBool( $id ); |
| 40 | + $record .= sprintf( 'Q%08X-A%08X-%s--', |
| 41 | + $question->getId(), $option->getId(), $checked ? 'y' : 'n' ); |
| 42 | + } |
| 43 | + return $record; |
| 44 | + } |
| 45 | + |
| 46 | + function unpackRecord( $record ) { |
| 47 | + $scores = array(); |
| 48 | + $itemLength = 2*8 + 7; |
| 49 | + for ( $offset = 0; $offset < strlen( $record ); $offset += $itemLength ) { |
| 50 | + if ( !preg_match( '/Q([0-9A-F]{8})-A([0-9A-F]{8})-([yn])--/A', |
| 51 | + $record, $m, 0, $offset ) ) |
| 52 | + { |
| 53 | + wfDebug( __METHOD__.": regex doesn't match\n" ); |
| 54 | + return false; |
| 55 | + } |
| 56 | + $qid = intval( base_convert( $m[1], 16, 10 ) ); |
| 57 | + $oid = intval( base_convert( $m[2], 16, 10 ) ); |
| 58 | + $score = ( $m[3] === 'y' ) ? 1 : 0; |
| 59 | + $scores[$qid][$oid] = $score; |
| 60 | + } |
| 61 | + return $scores; |
| 62 | + } |
| 63 | + |
| 64 | + function convertScores( $scores, $params = array() ) { |
| 65 | + $result = array(); |
| 66 | + foreach ( $this->election->getQuestions() as $question ) { |
| 67 | + $qid = $question->getId(); |
| 68 | + if ( !isset( $scores[$qid] ) ) { |
| 69 | + return false; |
| 70 | + } |
| 71 | + $s = ''; |
| 72 | + $qscores = $scores[$qid]; |
| 73 | + ksort( $qscores ); |
| 74 | + $first = true; |
| 75 | + foreach ( $qscores as $score ) { |
| 76 | + if ( $first ) { |
| 77 | + $first = false; |
| 78 | + } else { |
| 79 | + $s .= ', '; |
| 80 | + } |
| 81 | + $s .= $score ? 'y' : 'n'; |
| 82 | + } |
| 83 | + $result[$qid] = $s; |
| 84 | + } |
| 85 | + return $result; |
| 86 | + } |
| 87 | + |
| 88 | +} |
| 89 | + |
Property changes on: trunk/extensions/SecurePoll/includes/ballots/ApprovalBallot.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 90 | + native |
Index: trunk/extensions/SecurePoll/includes/entities/Entity.php |
— | — | @@ -209,25 +209,25 @@ |
210 | 210 | /** |
211 | 211 | * Get configuration XML. Overridden by most subclasses. |
212 | 212 | */ |
213 | | - function getConfXml( $options = array() ) { |
| 213 | + function getConfXml( $params = array() ) { |
214 | 214 | return "<{$this->type}>\n" . |
215 | | - $this->getConfXmlEntityStuff( $options ) . |
| 215 | + $this->getConfXmlEntityStuff( $params ) . |
216 | 216 | "</{$this->type}>\n"; |
217 | 217 | } |
218 | 218 | |
219 | 219 | /** |
220 | 220 | * Get an XML snippet giving the messages and properties |
221 | 221 | */ |
222 | | - function getConfXmlEntityStuff( $options = array() ) { |
| 222 | + function getConfXmlEntityStuff( $params = array() ) { |
223 | 223 | $s = Xml::element( 'id', array(), $this->getId() ) . "\n"; |
224 | | - $blacklist = $this->getPropertyDumpBlacklist( $options ); |
| 224 | + $blacklist = $this->getPropertyDumpBlacklist( $params ); |
225 | 225 | foreach ( $this->getAllProperties() as $name => $value ) { |
226 | 226 | if ( !in_array( $name, $blacklist ) ) { |
227 | 227 | $s .= Xml::element( 'property', array( 'name' => $name ), $value ) . "\n"; |
228 | 228 | } |
229 | 229 | } |
230 | | - if ( isset( $options['langs'] ) ) { |
231 | | - $langs = $options['langs']; |
| 230 | + if ( isset( $params['langs'] ) ) { |
| 231 | + $langs = $params['langs']; |
232 | 232 | } else { |
233 | 233 | $langs = $this->context->languages; |
234 | 234 | } |
— | — | @@ -250,7 +250,7 @@ |
251 | 251 | * Get property names which aren't included in an XML dump. |
252 | 252 | * Overloaded by Election. |
253 | 253 | */ |
254 | | - function getPropertyDumpBlacklist( $options = array() ) { |
| 254 | + function getPropertyDumpBlacklist( $params = array() ) { |
255 | 255 | return array(); |
256 | 256 | } |
257 | 257 | |
Index: trunk/extensions/SecurePoll/includes/entities/Election.php |
— | — | @@ -323,7 +323,7 @@ |
324 | 324 | /** |
325 | 325 | * Get an XML snippet describing the configuration of this object |
326 | 326 | */ |
327 | | - function getConfXml( $options = array() ) { |
| 327 | + function getConfXml( $params = array() ) { |
328 | 328 | $s = "<configuration>\n" . |
329 | 329 | Xml::element( 'title', array(), $this->title ) . "\n" . |
330 | 330 | Xml::element( 'ballot', array(), $this->ballotType ) . "\n" . |
— | — | @@ -331,11 +331,11 @@ |
332 | 332 | Xml::element( 'primaryLang', array(), $this->primaryLang ) . "\n" . |
333 | 333 | Xml::element( 'startDate', array(), wfTimestamp( TS_ISO_8601, $this->startDate ) ) . "\n" . |
334 | 334 | Xml::element( 'endDate', array(), wfTimestamp( TS_ISO_8601, $this->endDate ) ) . "\n" . |
335 | | - $this->getConfXmlEntityStuff( $options ); |
| 335 | + $this->getConfXmlEntityStuff( $params ); |
336 | 336 | |
337 | 337 | # If we're making a jump dump, we need to add some extra properties, and |
338 | 338 | # override the auth type |
339 | | - if ( !empty( $options['jump'] ) ) { |
| 339 | + if ( !empty( $params['jump'] ) ) { |
340 | 340 | $s .= |
341 | 341 | Xml::element( 'auth', array(), 'local' ) . "\n" . |
342 | 342 | Xml::element( 'property', |
— | — | @@ -351,7 +351,7 @@ |
352 | 352 | } |
353 | 353 | |
354 | 354 | foreach ( $this->getQuestions() as $question ) { |
355 | | - $s .= $question->getConfXml( $options ); |
| 355 | + $s .= $question->getConfXml( $params ); |
356 | 356 | } |
357 | 357 | $s .= "</configuration>\n"; |
358 | 358 | return $s; |
— | — | @@ -360,8 +360,8 @@ |
361 | 361 | /** |
362 | 362 | * Get property names which aren't included in an XML dump |
363 | 363 | */ |
364 | | - function getPropertyDumpBlacklist( $options = array() ) { |
365 | | - if ( !empty( $options['jump'] ) ) { |
| 364 | + function getPropertyDumpBlacklist( $params = array() ) { |
| 365 | + if ( !empty( $params['jump'] ) ) { |
366 | 366 | return array( |
367 | 367 | 'gpg-encrypt-key', |
368 | 368 | 'gpg-sign-key', |
Index: trunk/extensions/SecurePoll/includes/entities/Question.php |
— | — | @@ -38,10 +38,10 @@ |
39 | 39 | return $this->options; |
40 | 40 | } |
41 | 41 | |
42 | | - function getConfXml( $options = array() ) { |
43 | | - $s = "<question>\n" . $this->getConfXmlEntityStuff( $options ); |
| 42 | + function getConfXml( $params = array() ) { |
| 43 | + $s = "<question>\n" . $this->getConfXmlEntityStuff( $params ); |
44 | 44 | foreach ( $this->getOptions() as $option ) { |
45 | | - $s .= $option->getConfXml( $options ); |
| 45 | + $s .= $option->getConfXml( $params ); |
46 | 46 | } |
47 | 47 | $s .= "</question>\n"; |
48 | 48 | return $s; |
Index: trunk/extensions/SecurePoll/includes/main/Context.php |
— | — | @@ -82,11 +82,13 @@ |
83 | 83 | /** Set the store class */ |
84 | 84 | function setStoreClass( $class ) { |
85 | 85 | $this->store = null; |
| 86 | + $this->messageCache = $this->messagesLoaded = array(); |
86 | 87 | $this->storeClass = $class; |
87 | 88 | } |
88 | 89 | |
89 | 90 | /** Set the store object. Overrides any previous store class. */ |
90 | 91 | function setStore( $store ) { |
| 92 | + $this->messageCache = $this->messagesLoaded = array(); |
91 | 93 | $this->store = $store; |
92 | 94 | } |
93 | 95 | |
— | — | @@ -311,4 +313,9 @@ |
312 | 314 | echo $s; |
313 | 315 | } |
314 | 316 | } |
| 317 | + |
| 318 | + function getResourceUrl( $resource ) { |
| 319 | + global $wgScriptPath; |
| 320 | + return "$wgScriptPath/extensions/SecurePoll/resources/$resource"; |
| 321 | + } |
315 | 322 | } |
Index: trunk/extensions/SecurePoll/resources/SecurePoll.css |
— | — | @@ -45,23 +45,52 @@ |
46 | 46 | .securepoll-option-preferential { |
47 | 47 | margin-bottom: 0.5em; |
48 | 48 | } |
49 | | -.securepoll-results { |
| 49 | + |
| 50 | +.securepoll-table { |
50 | 51 | margin: 1em 1em 1em 0; |
51 | 52 | background: #f9f9f9; |
52 | | - border: 1px #aaa solid; |
| 53 | + border: thin #aaa solid; |
53 | 54 | border-collapse: collapse; |
54 | 55 | } |
55 | | -.securepoll-results th, .securepoll-results td { |
56 | | - border: 1px #aaa solid; |
| 56 | +.securepoll-table th, .securepoll-table td { |
| 57 | + border: thin #aaa solid; |
57 | 58 | padding: 0.4em; |
58 | 59 | } |
59 | | -.securepoll-results th { |
| 60 | +.securepoll-table th { |
60 | 61 | background: #f2f2f2; |
61 | 62 | text-align: center; |
62 | 63 | } |
63 | | -.securepoll-results caption { |
| 64 | +.securepoll-table caption { |
64 | 65 | font-weight: bold; |
65 | 66 | } |
66 | | -.securepoll-results-row-heading { |
67 | | - background: #f2f2f2; |
| 67 | + |
| 68 | +.securepoll-ballot-table { |
| 69 | + border-collapse: collapse; |
68 | 70 | } |
| 71 | +.securepoll-ballot-table th { |
| 72 | + font-weight: bold; |
| 73 | + text-align: center; |
| 74 | + border: thin #999 solid; |
| 75 | + padding: 0 1.5em 0 1.5em; |
| 76 | +} |
| 77 | +.securepoll-ballot-table td { |
| 78 | + text-align: center; |
| 79 | + border-left: thin #999 solid; |
| 80 | + border-right: thin #999 solid; |
| 81 | + border-bottom: thin #bbb dotted; |
| 82 | + padding: 0 1.5em 0 1.5em; |
| 83 | +} |
| 84 | +.securepoll-ballot-table td.securepoll-ballot-optlabel { /* High specificity */ |
| 85 | + text-align: left; |
| 86 | +} |
| 87 | +.securepoll-ballot-row:hover { |
| 88 | + background: #eeeeff; |
| 89 | +} |
| 90 | +.securepoll-error-location { |
| 91 | + margin: 1px 1em 1px 1px; |
| 92 | + border: 2px transparent solid; |
| 93 | + padding: 1px; |
| 94 | +} |
| 95 | +.securepoll-error-jump { |
| 96 | + margin-right: 0.5em; |
| 97 | +} |
Index: trunk/extensions/SecurePoll/resources/SecurePoll.js |
— | — | @@ -109,3 +109,53 @@ |
110 | 110 | securepoll_strike_popup( event, action == 'strike' ? 'unstrike' : 'strike', voteId ); |
111 | 111 | } |
112 | 112 | } |
| 113 | + |
| 114 | +function securepoll_ballot_setup() { |
| 115 | + if ( !document.getElementsByTagName ) { |
| 116 | + return; |
| 117 | + } |
| 118 | + var anchors = document.getElementsByTagName( 'a' ); |
| 119 | + for ( var i = 0; i < anchors.length; i++ ) { |
| 120 | + var elt = anchors.item( i ); |
| 121 | + if ( elt.className != 'securepoll-error-jump' ) { |
| 122 | + continue; |
| 123 | + } |
| 124 | + if ( elt.addEventListener ) { |
| 125 | + elt.addEventListener( 'click', |
| 126 | + function() { |
| 127 | + securepoll_error_jump( this, anchors ); |
| 128 | + }, |
| 129 | + false ); |
| 130 | + } else { |
| 131 | + elt.attachEvent( 'onclick', securepoll_error_jump ); |
| 132 | + } |
| 133 | + } |
| 134 | +} |
| 135 | + |
| 136 | +/** |
| 137 | + * TODO: make prettier |
| 138 | + */ |
| 139 | +function securepoll_error_jump( source, anchors ) { |
| 140 | + for ( var i = 0; i < anchors.length; i++ ) { |
| 141 | + var anchor = anchors.item( i ); |
| 142 | + if ( anchor.className != 'securepoll-error-jump' ) { |
| 143 | + continue; |
| 144 | + } |
| 145 | + var id = anchor.getAttribute( 'href' ).substr( 1 ); |
| 146 | + var elt = document.getElementById( id ); |
| 147 | + if ( !elt ) { |
| 148 | + continue; |
| 149 | + } |
| 150 | + |
| 151 | + try { |
| 152 | + if ( anchor == source ) { |
| 153 | + elt.style.borderColor = '#ff0000'; |
| 154 | + elt.style.backgroundColor = '#ffcc99'; |
| 155 | + } else { |
| 156 | + elt.style.backgroundColor = 'transparent'; |
| 157 | + elt.style.borderColor = 'transparent'; |
| 158 | + } |
| 159 | + } catch ( e ) {} |
| 160 | + } |
| 161 | +} |
| 162 | + |
Index: trunk/extensions/SecurePoll/resources/warning-22.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/SecurePoll/resources/warning-22.png |
___________________________________________________________________ |
Name: svn:mime-type |
113 | 163 | + image/png |
Index: trunk/extensions/SecurePoll/resources/down-16.png |
Cannot display: file marked as a binary type. |
svn:mime-type = image/png |
Property changes on: trunk/extensions/SecurePoll/resources/down-16.png |
___________________________________________________________________ |
Name: svn:mime-type |
114 | 164 | + image/png |
Index: trunk/extensions/SecurePoll/SecurePoll.sql |
— | — | @@ -90,6 +90,7 @@ |
91 | 91 | |
92 | 92 | -- Options for answering a given question, see Option.php |
93 | 93 | -- FIXME: needs op_election index for import.php |
| 94 | +-- FIXME: needs op_index column for determining the order if shuffle is off |
94 | 95 | CREATE TABLE /*_*/securepoll_options ( |
95 | 96 | -- securepoll_entity.en_id |
96 | 97 | op_entity int not null primary key, |