Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 112
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 / 112
0.00% covered (danger)
0.00%
0 / 8
1122
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 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 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 MediaWiki\Xml\Xml;
10use OOUI\ButtonWidget;
11use 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 */
18class 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}