Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 133
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 / 133
0.00% covered (danger)
0.00%
0 / 3
132
0.00% covered (danger)
0.00%
0 / 1
 execute
0.00% covered (danger)
0.00%
0 / 129
0.00% covered (danger)
0.00%
0 / 1
90
 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 Wikimedia\IPUtils;
8use Xml;
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">' . $this->detailEntry(
80                'securepoll-header-id',
81                $row->vote_id
82            ) . $this->detailEntry(
83                'securepoll-header-timestamp',
84                $row->vote_timestamp
85            ) . $this->detailEntry(
86                'securepoll-header-voter-name',
87                $row->voter_name
88            ) . $this->detailEntry(
89                'securepoll-header-voter-type',
90                $row->voter_type
91            ) . $this->detailEntry(
92                'securepoll-header-voter-domain',
93                $row->voter_domain
94            ) . $this->detailEntry( 'securepoll-header-url', $row->voter_url ) . $this->detailEntry(
95                'securepoll-header-ip',
96                $vote_ip
97            ) . $this->detailEntry( 'securepoll-header-xff', $vote_xff ) . $this->detailEntry(
98                'securepoll-header-ua',
99                $vote_ua
100            ) . $this->detailEntry(
101                'securepoll-header-token-match',
102                $row->vote_token_match
103            ) . '</table>'
104        );
105
106        // Show voter properties
107        $out->addHTML(
108            '<h2>' . $this->msg( 'securepoll-voter-properties' )->escaped() . "</h2>\n"
109        );
110        $out->addHTML( '<table class="mw-datatable TablePager">' );
111        $props = Voter::decodeProperties( $row->voter_properties );
112        foreach ( $props as $name => $value ) {
113            if ( is_array( $value ) ) {
114                $value = implode( ', ', $value );
115            }
116            $out->addHTML(
117                '<td class="securepoll-detail-header">' . htmlspecialchars(
118                    $name
119                ) . "</td>\n" . '<td>' . htmlspecialchars( (string)$value ) . "</td></tr>\n"
120            );
121        }
122        $out->addHTML( '</table>' );
123
124        // Show cookie dups
125        $voterId = intval( $row->voter_id );
126        $res = $db->newUnionQueryBuilder()
127            ->add(
128                $db->newSelectQueryBuilder()
129                    ->select( [
130                        'voter' => 'cm_voter_2',
131                        'cm_timestamp',
132                    ] )
133                    ->from( 'securepoll_cookie_match' )
134                    ->where( [
135                        'cm_voter_1' => $voterId,
136                    ] )
137            )
138            ->add(
139                $db->newSelectQueryBuilder()
140                    ->select( [
141                        'voter' => 'cm_voter_1',
142                        'cm_timestamp',
143                    ] )
144                    ->from( 'securepoll_cookie_match' )
145                    ->where( [
146                        'cm_voter_2' => $voterId,
147                    ] )
148            )
149            ->caller( __METHOD__ )
150            ->fetchResultSet();
151        if ( $res->numRows() ) {
152            $lang = $this->specialPage->getLanguage();
153            $out->addHTML(
154                '<h2>' . $this->msg( 'securepoll-cookie-dup-list' )->escaped() . '</h2>'
155            );
156            $out->addHTML( '<table class="mw-datatable TablePager">' );
157            foreach ( $res as $row ) {
158                $voter = $this->context->getVoter( $row->voter, DB_REPLICA );
159                $out->addHTML(
160                    '<tr>' . '<td>' . htmlspecialchars(
161                        $lang->timeanddate( $row->cm_timestamp )
162                    ) . '</td>' . '<td>' . Xml::element(
163                        'a',
164                        [ 'href' => $voter->getUrl() ],
165                        $voter->getName() . '@' . $voter->getDomain()
166                    ) . '</td></tr>'
167                );
168            }
169            $out->addHTML( '</table>' );
170        }
171
172        // Show strike log
173        $out->addHTML( '<h2>' . $this->msg( 'securepoll-strike-log' )->escaped() . "</h2>\n" );
174        $pager = new StrikePager( $this, $this->voteId );
175        $out->addParserOutputContent(
176            $pager->getFullOutput()
177        );
178    }
179
180    /**
181     * Get a table row with a given header message and value
182     * @param string $header
183     * @param string $value
184     * @return string
185     */
186    public function detailEntry( $header, $value ) {
187        return "<tr>\n" . "<td class=\"securepoll-detail-header\">" . $this->msg(
188                $header
189            )->escaped() . "</td>\n" . '<td>' . htmlspecialchars( $value ) . "</td></tr>\n";
190    }
191
192    /**
193     * Get a Title object for the current subpage.
194     * @return Title
195     */
196    public function getTitle() {
197        return $this->specialPage->getPageTitle( 'details/' . $this->voteId );
198    }
199}