Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 85 |
|
0.00% |
0 / 17 |
CRAP | |
0.00% |
0 / 1 |
Voter | |
0.00% |
0 / 85 |
|
0.00% |
0 / 17 |
650 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
newFromId | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
newFromRow | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
createVoter | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
getId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDomain | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUrl | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getElectionId | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProperty | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isRemote | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
decodeProperties | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
encodeProperties | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doCookieCheck | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
30 | |||
addCookieDup | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\User; |
4 | |
5 | use MediaWiki\Extension\SecurePoll\Context; |
6 | use MediaWiki\WikiMap\WikiMap; |
7 | use stdClass; |
8 | |
9 | /** |
10 | * Class representing a voter. A voter is associated with one election only. Voter |
11 | * properties include a snapshot of heuristic qualifications such as edit count. |
12 | */ |
13 | class Voter { |
14 | /** @var int|null */ |
15 | public $id; |
16 | /** @var int|null */ |
17 | public $electionId; |
18 | /** @var string|null */ |
19 | public $name; |
20 | /** @var string|null */ |
21 | public $domain; |
22 | /** @var string|null */ |
23 | public $wiki; |
24 | /** @var string|null */ |
25 | public $type; |
26 | /** @var string|null */ |
27 | public $url; |
28 | /** @var array|null */ |
29 | public $properties = []; |
30 | /** @var Context */ |
31 | public $context; |
32 | |
33 | /** @var string[] */ |
34 | private static $paramNames = [ |
35 | 'id', |
36 | 'electionId', |
37 | 'name', |
38 | 'domain', |
39 | 'wiki', |
40 | 'type', |
41 | 'url', |
42 | 'properties' |
43 | ]; |
44 | |
45 | /** |
46 | * Create a voter from the given associative array of parameters |
47 | * @param Context $context |
48 | * @param array $params |
49 | */ |
50 | public function __construct( $context, $params ) { |
51 | $this->context = $context; |
52 | foreach ( self::$paramNames as $name ) { |
53 | if ( isset( $params[$name] ) ) { |
54 | $this->$name = $params[$name]; |
55 | } |
56 | } |
57 | } |
58 | |
59 | /** |
60 | * Create a voter object from the database |
61 | * @param Context $context |
62 | * @param int $id |
63 | * @param int $index DB_PRIMARY or DB_REPLICA |
64 | * @return Voter|bool false if the ID is not valid |
65 | */ |
66 | public static function newFromId( $context, $id, $index = DB_PRIMARY ) { |
67 | $db = $context->getDB( $index ); |
68 | $row = $db->newSelectQueryBuilder() |
69 | ->select( '*' ) |
70 | ->from( 'securepoll_voters' ) |
71 | ->where( [ 'voter_id' => $id ] ) |
72 | ->caller( __METHOD__ ) |
73 | ->fetchRow(); |
74 | if ( !$row ) { |
75 | return false; |
76 | } |
77 | |
78 | return self::newFromRow( $context, $row ); |
79 | } |
80 | |
81 | /** |
82 | * Create a voter from a DB result row |
83 | * @param Context $context |
84 | * @param stdClass $row |
85 | * @return self |
86 | */ |
87 | public static function newFromRow( $context, $row ) { |
88 | return new self( |
89 | $context, [ |
90 | 'id' => $row->voter_id, |
91 | 'electionId' => $row->voter_election, |
92 | 'name' => $row->voter_name, |
93 | 'domain' => $row->voter_domain, |
94 | 'type' => $row->voter_type, |
95 | 'url' => $row->voter_url, |
96 | 'properties' => self::decodeProperties( $row->voter_properties ) |
97 | ] |
98 | ); |
99 | } |
100 | |
101 | /** |
102 | * Create a voter with the given parameters. Assumes the voter does not exist, |
103 | * and inserts it into the database. |
104 | * |
105 | * The row needs to be locked before this function is called, to avoid |
106 | * duplicate key errors. |
107 | * @param Context $context |
108 | * @param array $params |
109 | * @return self |
110 | */ |
111 | public static function createVoter( $context, $params ) { |
112 | $db = $context->getDB(); |
113 | $row = [ |
114 | 'voter_election' => $params['electionId'], |
115 | 'voter_name' => $params['name'], |
116 | 'voter_type' => $params['type'], |
117 | 'voter_domain' => $params['domain'], |
118 | 'voter_url' => $params['url'], |
119 | 'voter_properties' => self::encodeProperties( $params['properties'] ) |
120 | ]; |
121 | $db->newInsertQueryBuilder() |
122 | ->insertInto( 'securepoll_voters' ) |
123 | ->row( $row ) |
124 | ->caller( __METHOD__ ) |
125 | ->execute(); |
126 | $params['id'] = $db->insertId(); |
127 | |
128 | return new self( $context, $params ); |
129 | } |
130 | |
131 | /** |
132 | * Get the voter ID |
133 | * @return int |
134 | */ |
135 | public function getId() { |
136 | return $this->id; |
137 | } |
138 | |
139 | /** |
140 | * Get the voter name. This is a short, ambiguous name appropriate for |
141 | * display. |
142 | * @return string |
143 | */ |
144 | public function getName() { |
145 | return $this->name; |
146 | } |
147 | |
148 | /** |
149 | * Get the authorization type. |
150 | * @return string |
151 | */ |
152 | public function getType() { |
153 | return $this->type; |
154 | } |
155 | |
156 | /** |
157 | * Get the voter domain. The name and domain, taken together, should usually be |
158 | * unique, although this is not strictly necessary. |
159 | * @return string |
160 | */ |
161 | public function getDomain() { |
162 | return $this->domain; |
163 | } |
164 | |
165 | /** |
166 | * Get a URL uniquely identifying the underlying user. |
167 | * @return string |
168 | */ |
169 | public function getUrl() { |
170 | return $this->url; |
171 | } |
172 | |
173 | /** |
174 | * Get the associated election ID |
175 | * @return int |
176 | */ |
177 | public function getElectionId() { |
178 | return $this->electionId; |
179 | } |
180 | |
181 | /** |
182 | * Get the voter's preferred language |
183 | * @return mixed |
184 | */ |
185 | public function getLanguage() { |
186 | return $this->getProperty( 'language', 'en' ); |
187 | } |
188 | |
189 | /** |
190 | * Get a property from the property blob |
191 | * @param string $name |
192 | * @param string|false $default |
193 | * @return mixed |
194 | */ |
195 | public function getProperty( $name, $default = false ) { |
196 | return $this->properties[$name] ?? $default; |
197 | } |
198 | |
199 | /** |
200 | * Returns true if the voter is a guest user. |
201 | * @return bool |
202 | */ |
203 | public function isRemote() { |
204 | return $this->type !== 'local'; |
205 | } |
206 | |
207 | /** |
208 | * Decode the properties blob to produce an associative array. |
209 | * @param string $blob |
210 | * @return array |
211 | */ |
212 | public static function decodeProperties( $blob ) { |
213 | if ( strval( $blob ) == '' ) { |
214 | return []; |
215 | } else { |
216 | return unserialize( $blob ); |
217 | } |
218 | } |
219 | |
220 | /** |
221 | * Encode an associative array of properties to a blob suitable for storing |
222 | * in the database. |
223 | * @param array $props |
224 | * @return string |
225 | */ |
226 | public static function encodeProperties( $props ) { |
227 | return serialize( $props ); |
228 | } |
229 | |
230 | public function doCookieCheck() { |
231 | $cookieName = WikiMap::getCurrentWikiId() . '_securepoll_check'; |
232 | if ( isset( $_COOKIE[$cookieName] ) ) { |
233 | $otherVoterId = intval( $_COOKIE[$cookieName] ); |
234 | if ( $otherVoterId != $this->getId() ) { |
235 | $otherVoter = self::newFromId( $this->context, $otherVoterId ); |
236 | if ( $otherVoter && $otherVoter->getElectionId() == $this->getElectionId() ) { |
237 | $this->addCookieDup( $otherVoterId ); |
238 | } |
239 | } |
240 | } else { |
241 | setcookie( $cookieName, (string)$this->getId(), time() + 86400 * 30 ); |
242 | } |
243 | } |
244 | |
245 | /** |
246 | * Flag a duplicate voter |
247 | * @param int $voterId |
248 | */ |
249 | public function addCookieDup( $voterId ) { |
250 | $dbw = $this->context->getDB(); |
251 | # Insert the log record |
252 | $dbw->newInsertQueryBuilder() |
253 | ->insertInto( 'securepoll_cookie_match' ) |
254 | ->row( [ |
255 | 'cm_election' => $this->getElectionId(), |
256 | 'cm_voter_1' => $this->getId(), |
257 | 'cm_voter_2' => $voterId, |
258 | 'cm_timestamp' => wfTimestampNow() |
259 | ] ) |
260 | ->caller( __METHOD__ ) |
261 | ->execute(); |
262 | |
263 | # Update the denormalized fields |
264 | $dbw->newUpdateQueryBuilder() |
265 | ->update( 'securepoll_votes' ) |
266 | ->set( [ 'vote_cookie_dup' => 1 ] ) |
267 | ->where( [ |
268 | 'vote_election' => $this->getElectionId(), |
269 | 'vote_voter' => [ |
270 | $this->getId(), |
271 | $voterId |
272 | ] |
273 | ] ) |
274 | ->caller( __METHOD__ ) |
275 | ->execute(); |
276 | } |
277 | |
278 | } |