Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
VoteRecord
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 8
306
0.00% covered (danger)
0.00%
0 / 1
 readBlob
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 newFromBlob
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 newFromJson
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 newFromOldBlob
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 newFromBallotData
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getBallotData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getComment
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBlob
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3namespace MediaWiki\Extension\SecurePoll;
4
5use MediaWiki\Status\Status;
6use RuntimeException;
7
8/**
9 * Helper class for interpreting decrypted vote_record field values in a
10 * backwards-compatible way.
11 *
12 * Previously this field stored only the fixed-length ballot records. Now
13 * the ballot record may be wrapped in a JSON object, so that the vote
14 * page may add free text comments. (T300087)
15 */
16class VoteRecord {
17    /** @var string */
18    private $ballotData = '';
19    /** @var string */
20    private $comment = '';
21
22    /**
23     * Factory with Status
24     *
25     * @param string $blob
26     * @return Status
27     */
28    public static function readBlob( string $blob ) {
29        $status = Status::newGood();
30        $voteRecord = self::newFromBlob( $blob );
31        if ( !$blob ) {
32            $status->fatal( 'securepoll-invalid-record' );
33        } else {
34            $status->value = $voteRecord;
35        }
36        return $status;
37    }
38
39    /**
40     * Interpret decrypted field and construct object, return null on error.
41     *
42     * @param string $blob
43     * @return VoteRecord|null
44     */
45    public static function newFromBlob( string $blob ) {
46        if ( ( $blob[0] ?? '' ) === '{' ) {
47            return self::newFromJson( $blob );
48        } else {
49            return self::newFromOldBlob( $blob );
50        }
51    }
52
53    /**
54     * @param string $json
55     * @return VoteRecord|null
56     */
57    private static function newFromJson( $json ) {
58        $record = new self;
59        $data = json_decode( $json, true );
60        if ( !is_array( $data ) ) {
61            wfDebug( __METHOD__ . ': not array' );
62            return null;
63        }
64        if ( !isset( $data['vote'] ) ) {
65            wfDebug( __METHOD__ . ': no vote' );
66            return null;
67        }
68        if ( !is_string( $data['vote'] ) ) {
69            wfDebug( __METHOD__ . ': vote is not string' );
70            return null;
71        }
72        if ( isset( $data['comment'] ) ) {
73            if ( !is_string( $data['comment'] ) ) {
74                wfDebug( __METHOD__ . ': comment is not string' );
75                return null;
76            }
77        }
78        $record->ballotData = $data['vote'];
79        $record->comment = $data['comment'] ?? '';
80        return $record;
81    }
82
83    /**
84     * @param string $blob
85     * @return VoteRecord
86     */
87    private static function newFromOldBlob( $blob ) {
88        $record = new self;
89        $record->ballotData = rtrim( $blob );
90        return $record;
91    }
92
93    /**
94     * Create a record from form data.
95     *
96     * @param string $ballotData
97     * @param string $comment
98     * @return VoteRecord
99     */
100    public static function newFromBallotData( string $ballotData, string $comment ) {
101        $record = new self;
102        $record->ballotData = $ballotData;
103        $record->comment = $comment;
104        return $record;
105    }
106
107    /**
108     * Get the ballot data
109     *
110     * @return string
111     */
112    public function getBallotData(): string {
113        return $this->ballotData;
114    }
115
116    /**
117     * Get the comment. It may be an empty string.
118     *
119     * @return string
120     */
121    public function getComment(): string {
122        return $this->comment;
123    }
124
125    /**
126     * Serialize the vote record
127     *
128     * @return string
129     */
130    public function getBlob(): string {
131        $voteData = [ 'vote' => $this->ballotData ];
132        if ( $this->comment !== '' ) {
133            $voteData['comment'] = $this->comment;
134        }
135        $json = json_encode(
136            $voteData,
137            JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE
138        );
139        if ( !$json ) {
140            throw new RuntimeException( 'JSON encoding of vote record failed' );
141        }
142        return $json;
143    }
144}