Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.82% covered (warning)
81.82%
36 / 44
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Tallier
81.82% covered (warning)
81.82%
36 / 44
20.00% covered (danger)
20.00%
1 / 5
19.95
0.00% covered (danger)
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% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCreateDescriptors
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 convertRanksToHtml
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
6.01
 convertRanksToText
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
1<?php
2
3namespace MediaWiki\Extension\SecurePoll\Talliers;
4
5use InvalidArgumentException;
6use MediaWiki\Extension\SecurePoll\Context;
7use MediaWiki\Extension\SecurePoll\Entities\Election;
8use MediaWiki\Extension\SecurePoll\Entities\Question;
9use 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 */
16abstract 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}