Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 110 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ListPager | |
0.00% |
0 / 110 |
|
0.00% |
0 / 8 |
1056 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getQueryInfo | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
isFieldSortable | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
formatValue | |
0.00% |
0 / 68 |
|
0.00% |
0 / 1 |
380 | |||
getDefaultSort | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getFieldNames | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
getRowClass | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getTitle | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\SecurePoll\Pages; |
4 | |
5 | use MediaWiki\Extension\SecurePoll\Entities\Election; |
6 | use MediaWiki\Extension\SecurePoll\User\Voter; |
7 | use MediaWiki\MediaWikiServices; |
8 | use MediaWiki\Pager\TablePager; |
9 | use Wikimedia\IPUtils; |
10 | use Xml; |
11 | |
12 | /** |
13 | * A TablePager for showing a list of votes in a given election. |
14 | * Shows much more information, including voter's Personally Identifiable Information, and a strike/unstrike interface, |
15 | * if the global user is an admin. |
16 | */ |
17 | class ListPager extends TablePager { |
18 | /** @var ListPage */ |
19 | public $listPage; |
20 | /** @var bool */ |
21 | public $isAdmin; |
22 | /** @var Election */ |
23 | public $election; |
24 | |
25 | /** @var string[] */ |
26 | public static $publicFields = [ |
27 | // See T298434 |
28 | 'vote_id' => 'securepoll-header-date', |
29 | 'vote_voter_name' => 'securepoll-header-voter-name', |
30 | 'vote_voter_domain' => 'securepoll-header-voter-domain', |
31 | ]; |
32 | |
33 | /** @var string[] */ |
34 | public static $adminFields = [ |
35 | // See T298434 |
36 | 'details' => 'securepoll-header-details', |
37 | 'strike' => 'securepoll-header-strike', |
38 | 'vote_id' => 'securepoll-header-timestamp', |
39 | 'vote_voter_name' => 'securepoll-header-voter-name', |
40 | 'vote_voter_domain' => 'securepoll-header-voter-domain', |
41 | 'vote_token_match' => 'securepoll-header-token-match', |
42 | 'vote_cookie_dup' => 'securepoll-header-cookie-dup', |
43 | ]; |
44 | |
45 | /** @var string[] */ |
46 | public static $piiFields = [ |
47 | 'vote_ip' => 'securepoll-header-ip', |
48 | 'vote_xff' => 'securepoll-header-xff', |
49 | 'vote_ua' => 'securepoll-header-ua', |
50 | ]; |
51 | |
52 | /** |
53 | * Whether to include voter's Personally Identifiable Information. |
54 | * |
55 | * @var bool |
56 | */ |
57 | private $includeVoterPii; |
58 | |
59 | public function __construct( $listPage ) { |
60 | $this->listPage = $listPage; |
61 | $this->election = $listPage->election; |
62 | |
63 | $user = $this->getUser(); |
64 | |
65 | $this->isAdmin = $this->election->isAdmin( $user ); |
66 | |
67 | $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
68 | $this->includeVoterPii = |
69 | $this->isAdmin && $permissionManager->userHasRight( $user, 'securepoll-view-voter-pii' ); |
70 | |
71 | parent::__construct(); |
72 | } |
73 | |
74 | public function getQueryInfo() { |
75 | return [ |
76 | 'tables' => 'securepoll_votes', |
77 | 'fields' => '*', |
78 | 'conds' => [ |
79 | 'vote_election' => $this->listPage->election->getId() |
80 | ], |
81 | 'options' => [] |
82 | ]; |
83 | } |
84 | |
85 | protected function isFieldSortable( $field ) { |
86 | return in_array( |
87 | $field, |
88 | [ |
89 | 'vote_voter_name', |
90 | 'vote_voter_domain', |
91 | 'vote_id', |
92 | 'vote_ip' |
93 | ] |
94 | ); |
95 | } |
96 | |
97 | /** |
98 | * @param string $name |
99 | * @param string $value |
100 | * @return string HTML |
101 | */ |
102 | public function formatValue( $name, $value ) { |
103 | $config = $this->listPage->specialPage->getConfig(); |
104 | $securePollKeepPrivateInfoDays = $config->get( 'SecurePollKeepPrivateInfoDays' ); |
105 | |
106 | switch ( $name ) { |
107 | case 'vote_timestamp': |
108 | return 'Should be impossible (T298434)'; |
109 | case 'vote_id': |
110 | if ( $this->isAdmin ) { |
111 | return htmlspecialchars( $this->getLanguage()->timeanddate( $this->mCurrentRow->vote_timestamp ) ); |
112 | } else { |
113 | return htmlspecialchars( $this->getLanguage()->date( $this->mCurrentRow->vote_timestamp ) ); |
114 | } |
115 | case 'vote_ip': |
116 | if ( $this->election->endDate < wfTimestamp( |
117 | TS_MW, |
118 | time() - ( $securePollKeepPrivateInfoDays * 24 * 60 * 60 ) |
119 | ) |
120 | ) { |
121 | return ''; |
122 | } else { |
123 | return htmlspecialchars( IPUtils::formatHex( $value ) ); |
124 | } |
125 | case 'vote_ua': |
126 | case 'vote_xff': |
127 | if ( $this->election->endDate < wfTimestamp( |
128 | TS_MW, |
129 | time() - ( $securePollKeepPrivateInfoDays * 24 * 60 * 60 ) |
130 | ) |
131 | ) { |
132 | return ''; |
133 | } else { |
134 | return htmlspecialchars( $value ); |
135 | } |
136 | case 'vote_cookie_dup': |
137 | $value = !$value; |
138 | if ( $value ) { |
139 | return ''; |
140 | } else { |
141 | return $this->msg( 'securepoll-vote-duplicate' )->escaped(); |
142 | } |
143 | case 'vote_token_match': |
144 | if ( $value ) { |
145 | return ''; |
146 | } else { |
147 | return $this->msg( 'securepoll-vote-csrf' )->escaped(); |
148 | } |
149 | case 'details': |
150 | $voteId = intval( $this->mCurrentRow->vote_id ); |
151 | $title = $this->listPage->specialPage->getPageTitle( "details/$voteId" ); |
152 | |
153 | return Xml::element( |
154 | 'a', |
155 | [ 'href' => $title->getLocalURL() ], |
156 | $this->msg( 'securepoll-details-link' )->text() |
157 | ); |
158 | case 'strike': |
159 | $voteId = intval( $this->mCurrentRow->vote_id ); |
160 | if ( $this->mCurrentRow->vote_struck ) { |
161 | $label = $this->msg( 'securepoll-unstrike-button' )->text(); |
162 | $action = "unstrike"; |
163 | } else { |
164 | $label = $this->msg( 'securepoll-strike-button' )->text(); |
165 | $action = "strike"; |
166 | } |
167 | $id = 'securepoll-popup-' . $voteId; |
168 | |
169 | return ( new \OOUI\ButtonWidget( [ |
170 | 'id' => $id, |
171 | 'label' => $label, |
172 | ] ) )->setAttributes( [ |
173 | 'data-action' => $action, |
174 | 'data-voteId' => $voteId, |
175 | ] ); |
176 | case 'vote_voter_name': |
177 | $msg = Voter::newFromId( |
178 | $this->listPage->context, |
179 | $this->mCurrentRow->vote_voter, |
180 | DB_REPLICA |
181 | )->isRemote() |
182 | ? 'securepoll-voter-name-remote' |
183 | : 'securepoll-voter-name-local'; |
184 | |
185 | return $this->msg( |
186 | $msg, |
187 | [ wfEscapeWikitext( $value ), $value ] |
188 | )->parse(); |
189 | |
190 | default: |
191 | return htmlspecialchars( $value ); |
192 | } |
193 | } |
194 | |
195 | public function getDefaultSort() { |
196 | // See T298434 |
197 | return 'vote_id'; |
198 | } |
199 | |
200 | protected function getFieldNames() { |
201 | $names = []; |
202 | if ( $this->isAdmin ) { |
203 | $fields = self::$adminFields; |
204 | |
205 | if ( $this->includeVoterPii ) { |
206 | $fields += self::$piiFields; |
207 | } |
208 | } else { |
209 | $fields = self::$publicFields; |
210 | } |
211 | |
212 | foreach ( $fields as $field => $headerMessageName ) { |
213 | $names[$field] = $this->msg( $headerMessageName )->text(); |
214 | } |
215 | |
216 | return $names; |
217 | } |
218 | |
219 | protected function getRowClass( $row ) { |
220 | $classes = []; |
221 | if ( !$row->vote_current ) { |
222 | $classes[] = 'securepoll-old-vote'; |
223 | } |
224 | if ( $row->vote_struck ) { |
225 | $classes[] = 'securepoll-struck-vote'; |
226 | } |
227 | |
228 | return implode( ' ', $classes ); |
229 | } |
230 | |
231 | public function getTitle() { |
232 | return $this->listPage->getTitle(); |
233 | } |
234 | } |