Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
DetailsPage
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 3
156
0.00% covered (danger)
0.00%
0 / 1
 execute
0.00% covered (danger)
0.00%
0 / 121
0.00% covered (danger)
0.00%
0 / 1
110
 detailEntry
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 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\User\Voter;
6use MediaWiki\Title\Title;
7use MediaWiki\Xml\Xml;
8use Wikimedia\IPUtils;
9
10/**
11 * Special:SecurePoll subpage for showing the details of a given vote to an administrator.
12 */
13class DetailsPage extends ActionPage {
14    /** @var int|null */
15    public $voteId;
16
17    /**
18     * Execute the subpage.
19     * @param array $params Array of subpage parameters.
20     */
21    public function execute( $params ) {
22        $out = $this->specialPage->getOutput();
23
24        if ( !count( $params ) ) {
25            $out->addWikiMsg( 'securepoll-too-few-params' );
26
27            return;
28        }
29
30        $this->voteId = intval( $params[0] );
31
32        $db = $this->context->getDB();
33        $row = $db->newSelectQueryBuilder()
34            ->select( '*' )
35            ->from( 'securepoll_votes' )
36            ->join( 'securepoll_elections', null, 'vote_election=el_entity' )
37            ->join( 'securepoll_voters', null, 'vote_voter=voter_id' )
38            ->where( [ 'vote_id' => $this->voteId ] )
39            ->caller( __METHOD__ )
40            ->fetchRow();
41        if ( !$row ) {
42            $out->addWikiMsg( 'securepoll-invalid-vote', $this->voteId );
43
44            return;
45        }
46
47        $this->election = $this->context->newElectionFromRow( $row );
48        $this->initLanguage( $this->specialPage->getUser(), $this->election );
49
50        $vote_ip = '';
51        $vote_xff = '';
52        $vote_ua = '';
53        if ( $row->el_end_date >= wfTimestamp(
54                TS_MW,
55                time() - ( $this->specialPage->getConfig()->get( 'SecurePollKeepPrivateInfoDays' ) * 24 * 60 * 60 )
56            )
57        ) {
58            $vote_ip = IPUtils::formatHex( $row->vote_ip );
59            $vote_xff = $row->vote_xff;
60            $vote_ua = $row->vote_ua;
61        }
62
63        $this->specialPage->setSubtitle(
64            [
65                $this->specialPage->getPageTitle( 'list/' . $this->election->getId() ),
66                $this->msg( 'securepoll-list-title', $this->election->getMessage( 'title' ) )->text()
67            ]
68        );
69
70        if ( !$this->election->isAdmin( $this->specialPage->getUser() ) ) {
71            $out->addWikiMsg( 'securepoll-need-admin' );
72
73            return;
74        }
75        // Show vote properties
76        $out->setPageTitleMsg( $this->msg( 'securepoll-details-title', $this->voteId ) );
77
78        $out->addHTML(
79            '<table class="mw-datatable TablePager">' .
80            $this->detailEntry( 'securepoll-header-id', $row->vote_id ) .
81            $this->detailEntry( 'securepoll-header-timestamp', $row->vote_timestamp ) .
82            $this->detailEntry( 'securepoll-header-voter-name', $row->voter_name ) .
83            $this->detailEntry( 'securepoll-header-voter-type', $row->voter_type ) .
84            $this->detailEntry( 'securepoll-header-voter-domain', $row->voter_domain ) .
85            $this->detailEntry( 'securepoll-header-url', $row->voter_url )
86        );
87
88        if ( $this->specialPage->getAuthority()->isAllowed( 'securepoll-view-voter-pii' ) ) {
89            $out->addHTML(
90                $this->detailEntry( 'securepoll-header-ip', $vote_ip ) .
91                $this->detailEntry( 'securepoll-header-xff', $vote_xff ) .
92                $this->detailEntry( 'securepoll-header-ua', $vote_ua )
93            );
94        }
95
96        $out->addHTML(
97            $this->detailEntry( 'securepoll-header-token-match', $row->vote_token_match ) .
98            '</table>'
99        );
100
101        // Show voter properties
102        $out->addHTML(
103            '<h2>' . $this->msg( 'securepoll-voter-properties' )->escaped() . "</h2>\n"
104        );
105        $out->addHTML( '<table class="mw-datatable TablePager">' );
106        $props = Voter::decodeProperties( $row->voter_properties );
107        foreach ( $props as $name => $value ) {
108            if ( is_array( $value ) ) {
109                $value = implode( ', ', $value );
110            }
111            $out->addHTML(
112                '<td class="securepoll-detail-header">' . htmlspecialchars(
113                    $name
114                ) . "</td>\n" . '<td>' . htmlspecialchars( (string)$value ) . "</td></tr>\n"
115            );
116        }
117        $out->addHTML( '</table>' );
118
119        // Show cookie dups
120        $voterId = intval( $row->voter_id );
121        $res = $db->newUnionQueryBuilder()
122            ->add(
123                $db->newSelectQueryBuilder()
124                    ->select( [
125                        'voter' => 'cm_voter_2',
126                        'cm_timestamp',
127                    ] )
128                    ->from( 'securepoll_cookie_match' )
129                    ->where( [
130                        'cm_voter_1' => $voterId,
131                    ] )
132            )
133            ->add(
134                $db->newSelectQueryBuilder()
135                    ->select( [
136                        'voter' => 'cm_voter_1',
137                        'cm_timestamp',
138                    ] )
139                    ->from( 'securepoll_cookie_match' )
140                    ->where( [
141                        'cm_voter_2' => $voterId,
142                    ] )
143            )
144            ->caller( __METHOD__ )
145            ->fetchResultSet();
146        if ( $res->numRows() ) {
147            $lang = $this->specialPage->getLanguage();
148            $out->addHTML(
149                '<h2>' . $this->msg( 'securepoll-cookie-dup-list' )->escaped() . '</h2>'
150            );
151            $out->addHTML( '<table class="mw-datatable TablePager">' );
152            foreach ( $res as $row ) {
153                $voter = $this->context->getVoter( $row->voter, DB_REPLICA );
154                $out->addHTML(
155                    '<tr>' . '<td>' . htmlspecialchars(
156                        $lang->timeanddate( $row->cm_timestamp )
157                    ) . '</td>' . '<td>' . Xml::element(
158                        'a',
159                        [ 'href' => $voter->getUrl() ],
160                        $voter->getName() . '@' . $voter->getDomain()
161                    ) . '</td></tr>'
162                );
163            }
164            $out->addHTML( '</table>' );
165        }
166
167        // Show strike log
168        $out->addHTML( '<h2>' . $this->msg( 'securepoll-strike-log' )->escaped() . "</h2>\n" );
169        $pager = new StrikePager( $this, $this->voteId );
170        $out->addParserOutputContent(
171            $pager->getFullOutput()
172        );
173    }
174
175    /**
176     * Get a table row with a given header message and value
177     * @param string $header
178     * @param string $value
179     * @return string
180     */
181    public function detailEntry( $header, $value ) {
182        return "<tr>\n" . "<td class=\"securepoll-detail-header\">" . $this->msg(
183                $header
184            )->escaped() . "</td>\n" . '<td>' . htmlspecialchars( $value ) . "</td></tr>\n";
185    }
186
187    /**
188     * Get a Title object for the current subpage.
189     * @return Title
190     */
191    public function getTitle() {
192        return $this->specialPage->getPageTitle( 'details/' . $this->voteId );
193    }
194}