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 | public function __construct( $listPage ) { |
61 | $this->listPage = $listPage; |
62 | $this->election = $listPage->election; |
63 | |
64 | $user = $this->getUser(); |
65 | |
66 | $this->isAdmin = $this->election->isAdmin( $user ); |
67 | |
68 | $permissionManager = MediaWikiServices::getInstance()->getPermissionManager(); |
69 | $this->includeVoterPii = |
70 | $this->isAdmin && $permissionManager->userHasRight( $user, 'securepoll-view-voter-pii' ); |
71 | |
72 | parent::__construct(); |
73 | } |
74 | |
75 | public function getQueryInfo() { |
76 | return [ |
77 | 'tables' => 'securepoll_votes', |
78 | 'fields' => '*', |
79 | 'conds' => [ |
80 | 'vote_election' => $this->listPage->election->getId() |
81 | ], |
82 | 'options' => [] |
83 | ]; |
84 | } |
85 | |
86 | protected function isFieldSortable( $field ) { |
87 | return in_array( |
88 | $field, |
89 | [ |
90 | 'vote_voter_name', |
91 | 'vote_voter_domain', |
92 | 'vote_id', |
93 | 'vote_ip' |
94 | ] |
95 | ); |
96 | } |
97 | |
98 | /** |
99 | * @param string $name |
100 | * @param string $value |
101 | * @return string HTML |
102 | */ |
103 | public function formatValue( $name, $value ) { |
104 | $config = $this->listPage->specialPage->getConfig(); |
105 | $securePollKeepPrivateInfoDays = $config->get( 'SecurePollKeepPrivateInfoDays' ); |
106 | |
107 | switch ( $name ) { |
108 | case 'vote_timestamp': |
109 | return 'Should be impossible (T298434)'; |
110 | case 'vote_id': |
111 | if ( $this->isAdmin ) { |
112 | return htmlspecialchars( $this->getLanguage()->timeanddate( $this->mCurrentRow->vote_timestamp ) ); |
113 | } else { |
114 | return htmlspecialchars( $this->getLanguage()->date( $this->mCurrentRow->vote_timestamp ) ); |
115 | } |
116 | case 'vote_ip': |
117 | if ( $this->election->endDate < wfTimestamp( |
118 | TS_MW, |
119 | time() - ( $securePollKeepPrivateInfoDays * 24 * 60 * 60 ) |
120 | ) |
121 | ) { |
122 | return ''; |
123 | } else { |
124 | return htmlspecialchars( IPUtils::formatHex( $value ) ); |
125 | } |
126 | case 'vote_ua': |
127 | case 'vote_xff': |
128 | if ( $this->election->endDate < wfTimestamp( |
129 | TS_MW, |
130 | time() - ( $securePollKeepPrivateInfoDays * 24 * 60 * 60 ) |
131 | ) |
132 | ) { |
133 | return ''; |
134 | } else { |
135 | return htmlspecialchars( $value ); |
136 | } |
137 | case 'vote_cookie_dup': |
138 | $value = !$value; |
139 | if ( $value ) { |
140 | return ''; |
141 | } else { |
142 | return $this->msg( 'securepoll-vote-duplicate' )->escaped(); |
143 | } |
144 | case 'vote_token_match': |
145 | if ( $value ) { |
146 | return ''; |
147 | } else { |
148 | return $this->msg( 'securepoll-vote-csrf' )->escaped(); |
149 | } |
150 | case 'details': |
151 | $voteId = intval( $this->mCurrentRow->vote_id ); |
152 | $title = $this->listPage->specialPage->getPageTitle( "details/$voteId" ); |
153 | |
154 | return Xml::element( |
155 | 'a', |
156 | [ 'href' => $title->getLocalURL() ], |
157 | $this->msg( 'securepoll-details-link' )->text() |
158 | ); |
159 | case 'strike': |
160 | $voteId = intval( $this->mCurrentRow->vote_id ); |
161 | if ( $this->mCurrentRow->vote_struck ) { |
162 | $label = $this->msg( 'securepoll-unstrike-button' )->text(); |
163 | $action = "unstrike"; |
164 | } else { |
165 | $label = $this->msg( 'securepoll-strike-button' )->text(); |
166 | $action = "strike"; |
167 | } |
168 | $id = 'securepoll-popup-' . $voteId; |
169 | |
170 | return ( new ButtonWidget( [ |
171 | 'id' => $id, |
172 | 'label' => $label, |
173 | ] ) )->setAttributes( [ |
174 | 'data-action' => $action, |
175 | 'data-voteId' => $voteId, |
176 | ] ); |
177 | case 'vote_voter_name': |
178 | $msg = Voter::newFromId( |
179 | $this->listPage->context, |
180 | $this->mCurrentRow->vote_voter, |
181 | DB_REPLICA |
182 | )->isRemote() |
183 | ? 'securepoll-voter-name-remote' |
184 | : 'securepoll-voter-name-local'; |
185 | |
186 | return $this->msg( |
187 | $msg, |
188 | [ wfEscapeWikitext( $value ), $value ] |
189 | )->parse(); |
190 | |
191 | default: |
192 | return htmlspecialchars( $value ); |
193 | } |
194 | } |
195 | |
196 | public function getDefaultSort() { |
197 | // See T298434 |
198 | return 'vote_id'; |
199 | } |
200 | |
201 | protected function getFieldNames() { |
202 | $names = []; |
203 | if ( $this->isAdmin ) { |
204 | $fields = self::$adminFields; |
205 | |
206 | if ( $this->includeVoterPii ) { |
207 | $fields += self::$piiFields; |
208 | } |
209 | } else { |
210 | $fields = self::$publicFields; |
211 | } |
212 | |
213 | if ( !$this->election->getProperty( 'prompt-active-wiki' ) ) { |
214 | unset( $fields['vote_voter_domain'] ); |
215 | } |
216 | |
217 | foreach ( $fields as $field => $headerMessageName ) { |
218 | $names[$field] = $this->msg( $headerMessageName )->text(); |
219 | } |
220 | |
221 | return $names; |
222 | } |
223 | |
224 | protected function getRowClass( $row ) { |
225 | $classes = []; |
226 | if ( !$row->vote_current ) { |
227 | $classes[] = 'securepoll-old-vote'; |
228 | } |
229 | if ( $row->vote_struck ) { |
230 | $classes[] = 'securepoll-struck-vote'; |
231 | } |
232 | |
233 | return implode( ' ', $classes ); |
234 | } |
235 | |
236 | public function getTitle() { |
237 | return $this->listPage->getTitle(); |
238 | } |
239 | } |