Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 130
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportCsv
0.00% covered (danger)
0.00%
0 / 130
0.00% covered (danger)
0.00%
0 / 18
1892
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 validateRunId
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
42
 getUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setUser
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 importFromFile
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 importFromArray
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
132
 isValidQId
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getInputHash
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 addValidatedResult
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getCsvColumnHeader
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 processInput
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 deleteRun
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getWarnings
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isOverwrite
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setOverwrite
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getResults
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRunId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\MediaWikiServices;
4
5class ImportCsv {
6
7    /**
8     * @var array
9     */
10    private static $columnHeaders = [ 'queryId', 'formulaId' ];
11    /**
12     * @var bool|int|string
13     */
14    private $runId = false;
15    /**
16     * @var string[]
17     */
18    private $warnings = [];
19    /**
20     * @var array[]
21     */
22    private $results = [];
23    /**
24     * @var bool[] Array mapping numeric qId values to either true (exists) or false (doesn't exist)
25     */
26    private $validQIds = [];
27    /**
28     * @var bool
29     */
30    private $overwrite = false;
31    /**
32     * @var User
33     */
34    private $user;
35
36    /**
37     * @param User $user
38     */
39    function __construct( User $user ) {
40        $this->user = $user;
41    }
42
43    /**
44     * @param resource $csvFile
45     * @param int|null $runId
46     * @param bool $overwrite
47     *
48     * @return string|true|null
49     */
50    public function execute( $csvFile, $runId = null, $overwrite = false ) {
51        $this->overwrite = $overwrite;
52        $runId = $this->validateRunId( $runId );
53        if ( $runId !== false ) {
54            $success = $this->importFromFile( $csvFile );
55            if ( $success == true ) {
56                return $this->processInput();
57            }
58        } else {
59            return "Error: Invalid runId.";
60        }
61    }
62
63    /**
64     * @param string $run
65     * @return bool|int|string
66     */
67    function validateRunId( $run ) {
68        if ( $run == '' ) {
69            return date( 'Y-m-d H:i:s (e)' );
70        }
71        $dbw = MediaWikiServices::getInstance()
72            ->getConnectionProvider()
73            ->getPrimaryDatabase();
74        $uID = $this->getUser()->getId();
75        if ( is_int( $run ) ) {
76            $runId = $dbw->selectField( 'math_wmc_runs', 'runId',
77                [ 'isDraft' => true, 'userID' => $uID, 'runId' => $run ] );
78        } else {
79            $runId = $dbw->selectField( 'math_wmc_runs', 'runId',
80                [ 'isDraft' => true, 'userID' => $uID, 'runName' => $run ] );
81        }
82        if ( !$runId ) {
83            $exists = $dbw->selectField( 'math_wmc_runs', 'runId',
84                    [ 'userID' => $uID, 'runName' => $run ] );
85            if ( !$exists ) {
86                $success = $dbw->insert( 'math_wmc_runs',
87                        [ 'isDraft' => true, 'userID' => $uID, 'runName' => $run ] );
88                if ( $success ) {
89                    $this->runId = $dbw->insertId();
90                    $this->warnings[] = wfMessage( 'math-wmc-RunAdded', $run, $this->runId )->text();
91                } else {
92                    $this->runId = false;
93                    $this->warnings[] = wfMessage( 'math-wmc-RunAddError', $run )->text();
94                }
95            } else {
96                $this->warnings[] = wfMessage( 'math-wmc-RunAddExist', $run, $exists )->text();
97                $this->runId = false;
98            }
99        } else {
100            $this->runId = $runId;
101        }
102        return $this->runId;
103    }
104
105    /**
106     * @return User
107     */
108    public function getUser() {
109        return $this->user;
110    }
111
112    /**
113     * @param User $user
114     */
115    public function setUser( $user ) {
116        $this->user = $user;
117    }
118
119    /**
120     * @param resource|null $csv_file
121     * @return null
122     * @throws Exception
123     */
124    public function importFromFile( $csv_file ) {
125        if ( $csv_file === null ) {
126            return wfMessage( 'emptyfile' )->text();
127        }
128
129        $table = [];
130
131        $line = fgetcsv( $csv_file );
132        if ( $line === null ) {
133            throw new Exception( "Problem processing the csv file." );
134        }
135        while ( $line !== false ) {
136            array_push( $table, $line );
137            $line = fgetcsv( $csv_file );
138        }
139        fclose( $csv_file );
140
141        // Get rid of the "byte order mark", if it's there - this is
142        // a three-character string sometimes put at the beginning
143        // of files to indicate its encoding.
144        // Code copied from:
145        // http://www.dotvoid.com/2010/04/detecting-utf-bom-byte-order-mark/
146        $byteOrderMark = pack( "CCC", 0xef, 0xbb, 0xbf );
147        if ( strncmp( $table[0][0], $byteOrderMark, 3 ) == 0 ) {
148            $table[0][0] = substr( $table[0][0], 3 );
149            // If there were quotation marks around this value,
150            // they didn't get removed, so remove them now.
151            $table[0][0] = trim( $table[0][0], '"' );
152        }
153        return $this->importFromArray( $table );
154    }
155
156    /**
157     * @param array $table
158     * @return null|string
159     */
160    public function importFromArray( $table ) {
161        global $wgMathWmcMaxResults;
162
163        // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName
164        define( 'ImportPattern', '/math\.(\d+)\.(\d+)/' );
165
166        // check header line
167        $uploadedHeaders = $table[0];
168        if ( $uploadedHeaders != self::$columnHeaders ) {
169            return wfMessage( 'math-wmc-bad-header' )->text();
170        }
171        $rank = 0;
172        $lastQueryID = 0;
173        foreach ( $table as $i => $line ) {
174            if ( $i == 0 ) {
175                continue;
176            }
177            $pId = false;
178            $eId = false;
179            $fHash = false;
180            $qId = trim( $line[0] );
181            $fId = trim( $line[1] );
182            $qValid = $this->isValidQId( $qId );
183            if ( $qValid == false ) {
184                $this->warnings[] = wfMessage( 'math-wmc-wrong-query-reference', $i, $qId )->text();
185            }
186            if ( preg_match( ImportPattern, $fId, $m ) ) {
187                $pId = (int)$m[1];
188                $eId = (int)$m[2];
189                $fHash = $this->getInputHash( $pId, $eId );
190                if ( $fHash == false ) {
191                    $this->warnings[] = wfMessage( 'math-wmc-wrong-formula-reference', $i, $pId, $eId )->text();
192                }
193            } else {
194                $this->warnings[] = wfMessage( 'math-wmc-malformed-formula-reference', $i, $fId,
195                    ImportPattern )->text();
196            }
197            if ( $qValid === true && $fHash !== false ) {
198                // a valid result has been submitted
199                if ( $qId == $lastQueryID ) {
200                    $rank++;
201                } else {
202                    $lastQueryID = $qId;
203                    $rank = 1;
204                }
205                if ( $rank <= $wgMathWmcMaxResults ) {
206                    $this->addValidatedResult( $qId, $pId, $eId, $fHash, $rank );
207                } else {
208                    $this->warnings[] = wfMessage( 'math-wmc-too-many-results', $i, $qId, $fId, $rank,
209                        $wgMathWmcMaxResults )->text();
210                }
211            }
212        }
213        return true;
214    }
215
216    /**
217     * @param int $qId
218     * @return bool
219     */
220    private function isValidQId( $qId ) {
221        if ( array_key_exists( $qId, $this->validQIds ) ) {
222            return $this->validQIds[$qId];
223        }
224        $dbr = MediaWikiServices::getInstance()
225            ->getConnectionProvider()
226            ->getReplicaDatabase();
227        if ( $dbr->selectField( 'math_wmc_ref', 'qId', [ 'qId' => $qId ] ) ) {
228            $this->validQIds[$qId] = true;
229            return true;
230        } else {
231            $this->validQIds[$qId] = false;
232            return false;
233        }
234    }
235
236    /**
237     * @param int $pId
238     * @param string $eId
239     * @return string|false
240     */
241    private function getInputHash( $pId, $eId ) {
242        $dbr = MediaWikiServices::getInstance()
243            ->getConnectionProvider()
244            ->getReplicaDatabase();
245        return $dbr->selectField( 'mathindex', 'mathindex_inputhash',
246            [ 'mathindex_revision_id' => $pId, 'mathindex_anchor' => $eId ] );
247    }
248
249    /**
250     * @param int $qId
251     * @param int $pId
252     * @param string $eId
253     * @param string $fHash
254     * @param int $rank
255     */
256    private function addValidatedResult( $qId, $pId, $eId, $fHash, $rank ) {
257        $this->results[] = [
258            'runId' => $this->runId,
259            'qId' => $qId,
260            'oldId' => $pId,
261            'fId' => $eId,
262            'rank' => $rank,
263            'math_inputhash' => $fHash
264        ];
265    }
266
267    /**
268     * @return string
269     */
270    public static function getCsvColumnHeader() {
271        return implode( ',', self::$columnHeaders );
272    }
273
274    /**
275     * @return true
276     */
277    function processInput() {
278        $this->deleteRun( $this->runId );
279        $dbw = MediaWikiServices::getInstance()
280            ->getConnectionProvider()
281            ->getPrimaryDatabase();
282        $dbw->begin( __METHOD__ );
283        foreach ( $this->results as $result ) {
284            $dbw->insert( 'math_wmc_results', $result );
285        }
286        $dbw->commit( __METHOD__ );
287        return true;
288    }
289
290    /**
291     * @param string $runID
292     */
293    public function deleteRun( $runID ) {
294        if ( $this->overwrite ) {
295            $dbw = MediaWikiServices::getInstance()
296                ->getConnectionProvider()
297                ->getPrimaryDatabase();
298            $dbw->delete( 'math_wmc_results', [ 'runId' => $runID ] );
299        }
300    }
301
302    /**
303     * @return string[]
304     */
305    public function getWarnings() {
306        return $this->warnings;
307    }
308
309    /**
310     * @return bool
311     */
312    public function isOverwrite() {
313        return $this->overwrite;
314    }
315
316    /**
317     * @param bool $overwrite
318     */
319    public function setOverwrite( $overwrite ) {
320        $this->overwrite = $overwrite;
321    }
322
323    /**
324     * @return array[]
325     */
326    public function getResults() {
327        return $this->results;
328    }
329
330    /**
331     * @return int
332     */
333    public function getRunId() {
334        return $this->runId;
335    }
336
337}