Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ListPage
0.00% covered (danger)
0.00%
0 / 116
0.00% covered (danger)
0.00%
0 / 4
182
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 88
0.00% covered (danger)
0.00%
0 / 1
72
 strike
0.00% covered (danger)
0.00%
0 / 25
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 JobQueueGroup;
6use JobSpecification;
7use MediaWiki\Extension\SecurePoll\Entities\Election;
8use MediaWiki\Extension\SecurePoll\SpecialSecurePoll;
9use MediaWiki\Status\Status;
10use MediaWiki\Title\Title;
11
12/**
13 * SecurePoll subpage to show a list of votes for a given election.
14 * Provides an administrator interface for striking fraudulent votes.
15 */
16class ListPage extends ActionPage {
17    /** @var Election */
18    public $election;
19
20    /** @var JobQueueGroup */
21    private $jobQueueGroup;
22
23    /**
24     * @param SpecialSecurePoll $specialPage
25     * @param JobQueueGroup $jobQueueGroup
26     */
27    public function __construct(
28        SpecialSecurePoll $specialPage,
29        JobQueueGroup $jobQueueGroup
30    ) {
31        parent::__construct( $specialPage );
32        $this->jobQueueGroup = $jobQueueGroup;
33    }
34
35    /**
36     * Execute the subpage.
37     * @param array $params Array of subpage parameters.
38     */
39    public function execute( $params ) {
40        $out = $this->specialPage->getOutput();
41        $out->enableOOUI();
42
43        if ( !count( $params ) ) {
44            $out->addWikiMsg( 'securepoll-too-few-params' );
45
46            return;
47        }
48
49        $electionId = intval( $params[0] );
50        $this->election = $this->context->getElection( $electionId );
51        if ( !$this->election ) {
52            $out->addWikiMsg( 'securepoll-invalid-election', $electionId );
53
54            return;
55        }
56        $this->initLanguage( $this->specialPage->getUser(), $this->election );
57
58        $out->setPageTitleMsg( $this->msg( 'securepoll-list-title', $this->election->getMessage( 'title' ) ) );
59
60        $isAdmin = $this->election->isAdmin( $this->specialPage->getUser() );
61
62        if ( $this->election->getProperty( 'voter-privacy' ) && !$isAdmin ) {
63            $out->addWikiMsg( 'securepoll-list-private' );
64
65            return;
66        }
67
68        $dbr = $this->election->context->getDB( DB_REPLICA );
69
70        $res = $dbr->newSelectQueryBuilder()
71            ->select( 'vote_voter' )
72            ->distinct()
73            ->from( 'securepoll_votes' )
74            ->where( [
75                'vote_election' => $this->election->getId(),
76                'vote_struck' => 0
77            ] )
78            ->caller( __METHOD__ )
79            ->fetchResultSet();
80        $distinct_voters = $res->numRows();
81
82        $res = $dbr->newSelectQueryBuilder()
83            ->select( 'vote_id' )
84            ->from( 'securepoll_votes' )
85            ->where( [
86                'vote_election' => $this->election->getId()
87            ] )
88            ->caller( __METHOD__ )
89            ->fetchResultSet();
90        $all_votes = $res->numRows();
91
92        $res = $dbr->newSelectQueryBuilder()
93            ->select( 'vote_id' )
94            ->from( 'securepoll_votes' )
95            ->where( [
96                'vote_election' => $this->election->getId(),
97                'vote_current' => 0
98            ] )
99            ->caller( __METHOD__ )
100            ->fetchResultSet();
101        $not_current_votes = $res->numRows();
102
103        $res = $dbr->newSelectQueryBuilder()
104            ->select( 'vote_id' )
105            ->from( 'securepoll_votes' )
106            ->where( [
107                'vote_election' => $this->election->getId(),
108                'vote_struck' => 1
109            ] )
110            ->caller( __METHOD__ )
111            ->fetchResultSet();
112        $struck_votes = $res->numRows();
113
114        $out->addHTML(
115            '<div id="mw-poll-stats">' . $this->msg( 'securepoll-voter-stats' )->numParams(
116                $distinct_voters
117            )->parseAsBlock() . $this->msg( 'securepoll-vote-stats' )->numParams(
118                    $all_votes,
119                    $not_current_votes,
120                    $struck_votes
121                )->parseAsBlock() . '</div>'
122        );
123
124        $pager = new ListPager( $this );
125
126        // If an admin is viewing the votes, log it
127        $securePollUseLogging = $this->specialPage->getConfig()->get( 'SecurePollUseLogging' );
128        if ( $isAdmin && $securePollUseLogging ) {
129            $fields = [
130                'spl_election_id' => $electionId,
131                'spl_user' => $this->specialPage->getUser()->getId(),
132                'spl_type' => self::LOG_TYPE_VIEWVOTES,
133
134            ];
135            $this->jobQueueGroup->push(
136                new JobSpecification(
137                    'securePollLogAdminAction',
138                    [ 'fields' => $fields ],
139                    [],
140                    $this->getTitle()
141                )
142            );
143        }
144
145        $out->addHTML( $pager->getLimitForm() . $pager->getNavigationBar() );
146        $out->addParserOutputContent( $pager->getBodyOutput() );
147        $out->addHTML( $pager->getNavigationBar() );
148        if ( $isAdmin ) {
149            $out->addJsConfigVars( 'SecurePollSubPage', 'list' );
150            $out->addModules( 'ext.securepoll.htmlform' );
151        }
152    }
153
154    /**
155     * The strike/unstrike backend.
156     * @param string $action strike or unstrike
157     * @param int $voteId The vote ID
158     * @param string $reason The reason
159     * @return Status
160     */
161    public function strike( $action, $voteId, $reason ) {
162        $dbw = $this->context->getDB();
163        // this still gives the securepoll-need-admin error when an admin tries to
164        // delete a nonexistent vote.
165        if ( !$this->election->isAdmin( $this->specialPage->getUser() ) ) {
166            return Status::newFatal( 'securepoll-need-admin' );
167        }
168        if ( $action != 'strike' ) {
169            $action = 'unstrike';
170        }
171
172        $dbw->startAtomic( __METHOD__ );
173        // Add it to the strike log
174        $dbw->newInsertQueryBuilder()
175            ->insertInto( 'securepoll_strike' )
176            ->row( [
177                'st_vote' => $voteId,
178                'st_timestamp' => wfTimestampNow(),
179                'st_action' => $action,
180                'st_reason' => $reason,
181                'st_user' => $this->specialPage->getUser()->getId()
182            ] )
183            ->caller( __METHOD__ )
184            ->execute();
185        // Update the status cache
186        $dbw->newUpdateQueryBuilder()
187            ->update( 'securepoll_votes' )
188            ->set( [ 'vote_struck' => intval( $action == 'strike' ) ] )
189            ->where( [ 'vote_id' => $voteId ] )
190            ->caller( __METHOD__ )
191            ->execute();
192        $dbw->endAtomic( __METHOD__ );
193
194        return Status::newGood();
195    }
196
197    /**
198     * @return Title object
199     */
200    public function getTitle() {
201        return $this->specialPage->getPageTitle( 'list/' . $this->election->getId() );
202    }
203}