Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.82% |
36 / 44 |
|
20.00% |
1 / 5 |
CRAP | |
0.00% |
0 / 1 |
Tallier | |
81.82% |
36 / 44 |
|
20.00% |
1 / 5 |
19.95 | |
0.00% |
0 / 1 |
addVote | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
loadJSONResult | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getJSONResult | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getHtmlResult | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getTextResult | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
finishTally | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
factory | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getCreateDescriptors | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
__construct | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
2.02 | |||
convertRanksToHtml | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
6.01 | |||
convertRanksToText | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
7.02 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\Talliers; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Extension\SecurePoll\Context; |
7 | use MediaWiki\Extension\SecurePoll\Entities\Election; |
8 | use MediaWiki\Extension\SecurePoll\Entities\Question; |
9 | use MediaWiki\Xml\Xml; |
10 | |
11 | /** |
12 | * Base class for objects which tally individual questions. |
13 | * See ElectionTallier for an object which can tally multiple |
14 | * questions. |
15 | */ |
16 | abstract class Tallier { |
17 | /** @var Context */ |
18 | public $context; |
19 | /** @var Question */ |
20 | public $question; |
21 | /** @var ElectionTallier */ |
22 | public $electionTallier; |
23 | /** @var Election */ |
24 | public $election; |
25 | /** @var array */ |
26 | public $optionsById = []; |
27 | |
28 | /** |
29 | * Transforms individual ballots into an aggregated dataset that then gets consumed by finishTally() |
30 | * |
31 | * @param array $scores |
32 | * @return bool |
33 | */ |
34 | abstract public function addVote( $scores ); |
35 | |
36 | /** |
37 | * Restores results from getJSONResult |
38 | * |
39 | * @param array $data |
40 | */ |
41 | abstract public function loadJSONResult( array $data ); |
42 | |
43 | /** |
44 | * Get a simple array structure representing results for this tally. Should |
45 | * only be called after execute(). |
46 | * This array MUST contain all required information for getHtmlResult or getTextResult to run after it is loaded. |
47 | * @return array |
48 | */ |
49 | abstract public function getJSONResult(); |
50 | |
51 | /** |
52 | * Build string result from tallier's stored values |
53 | * @return string |
54 | */ |
55 | abstract public function getHtmlResult(); |
56 | |
57 | /** |
58 | * Get text formatted results for this tally. Should only be called after |
59 | * execute(). |
60 | * @return string |
61 | */ |
62 | abstract public function getTextResult(); |
63 | |
64 | /** |
65 | * See inherit doc. |
66 | */ |
67 | abstract public function finishTally(); |
68 | |
69 | /** @var array<string,class-string<Tallier>> */ |
70 | public static $tallierTypes = [ |
71 | 'plurality' => PluralityTallier::class, |
72 | 'schulze' => SchulzeTallier::class, |
73 | 'histogram-range' => HistogramRangeTallier::class, |
74 | 'droop-quota' => STVTallier::class, |
75 | ]; |
76 | |
77 | /** |
78 | * @param Context $context |
79 | * @param string $type |
80 | * @param ElectionTallier $electionTallier |
81 | * @param Question $question |
82 | * @return Tallier |
83 | * @throws InvalidArgumentException |
84 | */ |
85 | public static function factory( $context, $type, $electionTallier, $question ) { |
86 | if ( !isset( self::$tallierTypes[$type] ) ) { |
87 | throw new InvalidArgumentException( "Invalid tallier type: $type" ); |
88 | } |
89 | $class = self::$tallierTypes[$type]; |
90 | |
91 | return new $class( $context, $electionTallier, $question ); |
92 | } |
93 | |
94 | /** |
95 | * Return descriptors for any properties this type requires for poll |
96 | * creation, for the election, questions, and options. |
97 | * |
98 | * The returned array should have three keys, "election", "question", and |
99 | * "option", each mapping to an array of HTMLForm descriptors. |
100 | * |
101 | * The descriptors should have an additional key, "SecurePoll_type", with |
102 | * the value being "property" or "message". |
103 | * |
104 | * @return array |
105 | */ |
106 | public static function getCreateDescriptors() { |
107 | return [ |
108 | 'election' => [], |
109 | 'question' => [], |
110 | 'option' => [], |
111 | ]; |
112 | } |
113 | |
114 | /** |
115 | * @param Context $context |
116 | * @param ElectionTallier $electionTallier |
117 | * @param Question $question |
118 | */ |
119 | public function __construct( $context, $electionTallier, $question ) { |
120 | $this->context = $context; |
121 | $this->question = $question; |
122 | $this->electionTallier = $electionTallier; |
123 | $this->election = $electionTallier->election; |
124 | foreach ( $this->question->getOptions() as $option ) { |
125 | $this->optionsById[$option->getId()] = $option; |
126 | } |
127 | } |
128 | |
129 | /** |
130 | * Build the result into HTML to display |
131 | * @param array $ranks |
132 | * @return string |
133 | */ |
134 | public function convertRanksToHtml( $ranks ) { |
135 | $s = "<table class=\"securepoll-table\">"; |
136 | $ids = array_keys( $ranks ); |
137 | foreach ( $ids as $i => $oid ) { |
138 | $rank = $ranks[$oid]; |
139 | $prevRank = isset( $ids[$i - 1] ) ? $ranks[$ids[$i - 1]] : false; |
140 | $nextRank = isset( $ids[$i + 1] ) ? $ranks[$ids[$i + 1]] : false; |
141 | if ( $rank === $prevRank || $rank === $nextRank ) { |
142 | $rank .= '*'; |
143 | } |
144 | |
145 | $option = $this->optionsById[$oid]; |
146 | $s .= "<tr>" . Xml::element( 'td', [], $rank ) . Xml::openElement( |
147 | 'td', |
148 | [] |
149 | ) . $option->parseMessage( 'text', false ) . Xml::closeElement( 'td' ) . "</tr>\n"; |
150 | } |
151 | $s .= "</table>"; |
152 | |
153 | return $s; |
154 | } |
155 | |
156 | /** |
157 | * Build the result into HTML to display |
158 | * @param array $ranks |
159 | * @return string |
160 | */ |
161 | public function convertRanksToText( $ranks ) { |
162 | $s = ''; |
163 | $ids = array_keys( $ranks ); |
164 | $colWidth = 6; |
165 | foreach ( $this->optionsById as $option ) { |
166 | $colWidth = max( $colWidth, $option->getMessage( 'text' ) ); |
167 | } |
168 | |
169 | foreach ( $ids as $i => $oid ) { |
170 | $rank = $ranks[$oid]; |
171 | $prevRank = isset( $ids[$i - 1] ) ? $ranks[$ids[$i - 1]] : false; |
172 | $nextRank = isset( $ids[$i + 1] ) ? $ranks[$ids[$i + 1]] : false; |
173 | if ( $rank === $prevRank || $rank === $nextRank ) { |
174 | $rank .= '*'; |
175 | } |
176 | |
177 | $option = $this->optionsById[$oid]; |
178 | $s .= str_pad( $rank, 6 ) . ' | ' . $option->getMessage( 'text' ) . "\n"; |
179 | } |
180 | |
181 | return $s; |
182 | } |
183 | } |