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