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