Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 110
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
ListPager
0.00% covered (danger)
0.00%
0 / 110
0.00% covered (danger)
0.00%
0 / 8
1056
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getQueryInfo
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 isFieldSortable
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 formatValue
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
380
 getDefaultSort
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFieldNames
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getRowClass
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\SecurePoll\Pages;
4
5use MediaWiki\Extension\SecurePoll\Entities\Election;
6use MediaWiki\Extension\SecurePoll\User\Voter;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Pager\TablePager;
9use Wikimedia\IPUtils;
10use 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 */
17class 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}