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    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}