Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
16.03% |
63 / 393 |
|
17.65% |
9 / 51 |
CRAP | |
0.00% |
0 / 1 |
MathObject | |
16.03% |
63 / 393 |
|
17.65% |
9 / 51 |
6507.70 | |
0.00% |
0 / 1 |
hash2md5 | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
findSimilarPages | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
20 | |||
cloneFromRenderer | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
constructformpage | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
newFromRevisionText | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
dbIndexFieldsArray | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
constructformpagerow | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
30 | |||
extractMathTagsFromWikiText | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
updateStatistics | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
2 | |||
getStatusCode | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setStatusCode | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getTimestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setTimestamp | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getLog | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setLog | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getIndexTimestamp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getObservations | |
0.00% |
0 / 55 |
|
0.00% |
0 / 1 |
90 | |||
getInputHash | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getRevisionID | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setRevisionID | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
updateObservations | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
12 | |||
getNouns | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 | |||
getPageTitle | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getAllOccurrences | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
isCurrent | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
printLink2Page | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getAnchorID | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setAnchorID | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
render | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSvg | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
addIdentifierTitle | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
getRevision | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getRelations | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
30 | |||
getMathTableName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTexInfo | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getWikiText | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getMathMlAltText | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getSvgWidth | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getSvgHeight | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getReSizedSvgLink | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
getRbi | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPostData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setPostData | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRenderingTime | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setRenderingTime | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
dbDebugOutArray | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
writeToCache | |
57.14% |
20 / 35 |
|
0.00% |
0 / 1 |
5.26 | |||
readFromCache | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
2 | |||
dbInArray | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
dbOutArray | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
initializeFromCache | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
5.20 |
1 | <?php |
2 | |
3 | use MediaWiki\Deferred\DeferredUpdates; |
4 | use MediaWiki\Extension\Math\MathMathML; |
5 | use MediaWiki\Extension\Math\MathRenderer; |
6 | use MediaWiki\Logger\LoggerFactory; |
7 | use MediaWiki\MediaWikiServices; |
8 | use MediaWiki\Revision\RevisionRecord; |
9 | use MediaWiki\Title\Title; |
10 | use Wikimedia\Rdbms\IResultWrapper; |
11 | |
12 | class MathObject extends MathMathML { |
13 | |
14 | // DEBUG VARIABLES |
15 | // Available, if Math extension runs in debug mode ($wgMathDebug = true) only. |
16 | public const MODE_2_USER_OPTION = [ |
17 | 'native' => 8, |
18 | 'latexml' => 7, |
19 | 'mathml' => 5, |
20 | 'source' => 3 |
21 | ]; |
22 | /** @var int LaTeXML return code (will be available in future Mathoid versions as well) */ |
23 | private $statusCode = 0; |
24 | /** @var string|null Timestamp of the last modification of the database entry */ |
25 | private $timestamp; |
26 | /** @var string messages generated during conversion of mathematical content */ |
27 | private $log = ''; |
28 | |
29 | /** @var string */ |
30 | protected $postData = ''; |
31 | /** @var string */ |
32 | protected $anchorID; |
33 | /** @var int */ |
34 | protected $revisionID = 0; |
35 | /** @var string|null */ |
36 | protected $index_timestamp = null; |
37 | /** @var string|null */ |
38 | protected $mathTableName = null; |
39 | /** @var int */ |
40 | protected $renderingTime = 0; |
41 | |
42 | public static function hash2md5( $hash ) { |
43 | // TODO: make MathRenderer::dbHash2md5 public |
44 | $dbr = MediaWikiServices::getInstance() |
45 | ->getConnectionProvider() |
46 | ->getReplicaDatabase(); |
47 | $xhash = unpack( 'H32md5', $dbr->decodeBlob( $hash ) . " " ); |
48 | return $xhash['md5']; |
49 | } |
50 | |
51 | public static function findSimilarPages( $pid ): void { |
52 | global $wgOut; |
53 | $out = ""; |
54 | $dbr = MediaWikiServices::getInstance() |
55 | ->getConnectionProvider() |
56 | ->getReplicaDatabase(); |
57 | try { |
58 | $res = $dbr->select( 'mathpagesimilarity', |
59 | [ |
60 | 'pagesimilarity_A as A', |
61 | 'pagesimilarity_B as B', |
62 | 'pagesimilarity_Value as V' |
63 | ], |
64 | "pagesimilarity_A=$pid OR pagesimilarity_B=$pid", __METHOD__, |
65 | [ "ORDER BY" => 'V DESC', "LIMIT" => 10 ] |
66 | ); |
67 | $revisionLookup = MediaWikiServices::getInstance() |
68 | ->getRevisionLookup(); |
69 | |
70 | foreach ( $res as $row ) { |
71 | if ( $row->A == $pid ) { |
72 | $other = $row->B; |
73 | } else { |
74 | $other = $row->A; |
75 | } |
76 | $revLinkTarget = $revisionLookup->getRevisionById( $other ) |
77 | ->getPageAsLinkTarget(); |
78 | $revTitle = Title::newFromLinkTarget( $revLinkTarget ); |
79 | $out .= '# [[' . $revTitle . ']] similarity ' . |
80 | $row->V * 100 . "%\n"; |
81 | // .' ( pageid'.$other.'/'.$row->A.')' ); |
82 | } |
83 | $wgOut->addWikiTextAsInterface( $out ); |
84 | } catch ( Exception $e ) { |
85 | $wgOut->addHTML( "DatabaseProblem" ); |
86 | } |
87 | } |
88 | |
89 | public static function cloneFromRenderer( MathRenderer $renderer ): MathObject { |
90 | $instance = new self( $renderer->getUserInputTex() ); |
91 | $instance->setMathml( $renderer->getMathml() ); |
92 | $instance->setSvg( $renderer->getSvg() ); |
93 | $instance->setMode( $renderer->getMode() ); |
94 | $instance->setMathStyle( $renderer->getMathStyle() ); |
95 | if ( $renderer->rbi ) { |
96 | $instance->setRestbaseInterface( $renderer->rbi ); |
97 | } |
98 | $instance->setInputType( $renderer->getInputType() ); |
99 | return $instance; |
100 | } |
101 | |
102 | /** |
103 | * @param int $pid |
104 | * @param string $eid |
105 | * @return self instance |
106 | */ |
107 | public static function constructformpage( $pid, $eid ) { |
108 | $dbr = MediaWikiServices::getInstance() |
109 | ->getConnectionProvider() |
110 | ->getReplicaDatabase(); |
111 | $res = $dbr->selectRow( |
112 | [ 'mathindex' ], self::dbIndexFieldsArray(), 'mathindex_revision_id = ' . $pid |
113 | . ' AND mathindex_anchor= "' . $eid . '"' ); |
114 | return self::constructformpagerow( $res ); |
115 | } |
116 | |
117 | /** |
118 | * @param int $oldId |
119 | * @param string $eid |
120 | * |
121 | * @return self |
122 | */ |
123 | public static function newFromRevisionText( $oldId, $eid ): MathObject { |
124 | $gen = MathIdGenerator::newFromRevisionId( $oldId ); |
125 | $tag = $gen->getTagFromId( $eid ); |
126 | if ( !$tag ) { |
127 | throw new RuntimeException( "$eid not found in revision text $oldId" ); |
128 | } |
129 | $mo = |
130 | new self( $tag[MathIdGenerator::CONTENT_POS], $tag[MathIdGenerator::ATTRIB_POS] ); |
131 | $mo->setRevisionID( $oldId ); |
132 | return $mo; |
133 | } |
134 | |
135 | /** |
136 | * @return string[] |
137 | */ |
138 | private static function dbIndexFieldsArray(): array { |
139 | global $wgMathDebug; |
140 | $in = [ |
141 | 'mathindex_revision_id', |
142 | 'mathindex_anchor', |
143 | 'mathindex_inputhash' |
144 | ]; |
145 | if ( $wgMathDebug ) { |
146 | $debug_in = [ |
147 | 'mathindex_timestamp' |
148 | ]; |
149 | $in = array_merge( $in, $debug_in ); |
150 | } |
151 | return $in; |
152 | } |
153 | |
154 | /** |
155 | * @param stdClass $res |
156 | * @return bool|self |
157 | */ |
158 | public static function constructformpagerow( $res ) { |
159 | global $wgMathDebug; |
160 | if ( $res && $res->mathindex_revision_id > 0 ) { |
161 | $instance = new static(); |
162 | $instance->setRevisionID( $res->mathindex_revision_id ); |
163 | $instance->setAnchorID( $res->mathindex_anchor ); |
164 | if ( $wgMathDebug && isset( $res->mathindex_timestamp ) ) { |
165 | $instance->index_timestamp = $res->mathindex_timestamp; |
166 | } |
167 | $instance->inputHash = $res->mathindex_inputhash; |
168 | $instance->readFromCache(); |
169 | return $instance; |
170 | } else { |
171 | return false; |
172 | } |
173 | } |
174 | |
175 | public static function extractMathTagsFromWikiText( string $wikiText ): array { |
176 | $idGenerator = new MathIdGenerator( $wikiText ); |
177 | return $idGenerator->getMathTags(); |
178 | } |
179 | |
180 | public static function updateStatistics() { |
181 | $dbw = MediaWikiServices::getInstance() |
182 | ->getConnectionProvider() |
183 | ->getPrimaryDatabase(); |
184 | $dbw->query( 'TRUNCATE TABLE `mathvarstat`' ); |
185 | // phpcs:disable Generic.Files.LineLength |
186 | $dbw->query( "INSERT INTO `mathvarstat` (`varstat_featurename` , `varstat_featuretype`, `varstat_featurecount`) " |
187 | . |
188 | "SELECT `mathobservation_featurename` , `mathobservation_featuretype` , count( * ) AS CNT " |
189 | . "FROM `mathobservation` " |
190 | . "JOIN mathindex ON `mathobservation_inputhash` = mathindex_inputhash " |
191 | . "GROUP BY `mathobservation_featurename` , `mathobservation_featuretype` " |
192 | . "ORDER BY CNT DESC" ); |
193 | $dbw->query( 'TRUNCATE TABLE `mathrevisionstat`' ); |
194 | $dbw->query( 'INSERT INTO `mathrevisionstat`(`revstat_featureid`,`revstat_revid`,`revstat_featurecount`) ' |
195 | . 'SELECT varstat_id, mathindex_revision_id, count(*) AS CNT FROM `mathobservation` ' |
196 | . 'JOIN mathindex ON `mathobservation_inputhash` = mathindex_inputhash ' |
197 | . |
198 | 'JOIN mathvarstat ON varstat_featurename = `mathobservation_featurename` AND varstat_featuretype = `mathobservation_featuretype` ' |
199 | . |
200 | 'GROUP BY `mathobservation_featurename`, `mathobservation_featuretype`, mathindex_revision_id ORDER BY CNT DESC' ); |
201 | // phpcs:enable Generic.Files.LineLength |
202 | } |
203 | |
204 | public function getStatusCode(): int { |
205 | return $this->statusCode; |
206 | } |
207 | |
208 | public function setStatusCode( int $statusCode ): MathObject { |
209 | $this->statusCode = $statusCode; |
210 | return $this; |
211 | } |
212 | |
213 | public function getTimestamp(): ?string { |
214 | return $this->timestamp; |
215 | } |
216 | |
217 | public function setTimestamp( string $timestamp ): MathObject { |
218 | $this->timestamp = $timestamp; |
219 | return $this; |
220 | } |
221 | |
222 | public function getLog(): string { |
223 | return $this->log; |
224 | } |
225 | |
226 | public function setLog( string $log ): MathObject { |
227 | $this->changed = true; |
228 | $this->log = $log; |
229 | return $this; |
230 | } |
231 | |
232 | public function getIndexTimestamp(): ?string { |
233 | return $this->index_timestamp; |
234 | } |
235 | |
236 | public function getObservations( bool $update = true ): void { |
237 | global $wgOut; |
238 | $dbr = MediaWikiServices::getInstance() |
239 | ->getConnectionProvider() |
240 | ->getReplicaDatabase(); |
241 | try { |
242 | $res = $dbr->select( [ "mathobservation", "mathvarstat", 'mathrevisionstat' ], |
243 | [ |
244 | "mathobservation_featurename", |
245 | "mathobservation_featuretype", |
246 | 'varstat_featurecount', |
247 | 'revstat_featurecount', |
248 | "count(*) as localcnt" |
249 | ], |
250 | [ |
251 | "mathobservation_inputhash" => $this->getInputHash(), |
252 | 'varstat_featurename = mathobservation_featurename', |
253 | 'varstat_featuretype = mathobservation_featuretype', |
254 | 'revstat_revid' => $this->getRevisionID(), |
255 | 'revstat_featureid = varstat_id' |
256 | ], |
257 | __METHOD__, [ |
258 | 'GROUP BY' => [ |
259 | 'mathobservation_featurename', |
260 | 'mathobservation_featuretype', |
261 | 'varstat_featurecount', |
262 | 'revstat_featurecount', |
263 | ], |
264 | 'ORDER BY' => 'varstat_featurecount' |
265 | ] |
266 | ); |
267 | } catch ( Exception $e ) { |
268 | $wgOut->addHTML( "Database problem" . $e->getMessage() ); |
269 | return; |
270 | } |
271 | $wgOut->addWikiTextAsInterface( $res->numRows() . 'results' ); |
272 | if ( $res->numRows() == 0 ) { |
273 | if ( $update ) { |
274 | $this->updateObservations(); |
275 | $this->getObservations( false ); |
276 | } else { |
277 | $wgOut->addWikiTextAsInterface( |
278 | "no statistics present please run the maintenance script ExtractFeatures.php" |
279 | ); |
280 | } |
281 | } |
282 | $wgOut->addWikiTextAsInterface( $res->numRows() . ' results' ); |
283 | if ( $res ) { |
284 | foreach ( $res as $row ) { |
285 | $featureName = $row->mathobservation_featurename; |
286 | if ( bin2hex( $featureName ) == 'e281a2' ) { |
287 | $featureName = 'invisibe-times'; |
288 | } |
289 | $wgOut->addWikiTextAsInterface( '*' . $row->mathobservation_featuretype . ' <code>' . |
290 | $featureName . '</code> (' . $row->localcnt . '/' . |
291 | $row->pagestat_featurecount . |
292 | "/" . $row->varstat_featurecount . ')' ); |
293 | $identifiers = $this->getNouns( $row->mathobservation_featurename ); |
294 | if ( $identifiers ) { |
295 | foreach ( $identifiers as $identifier ) { |
296 | $wgOut->addWikiTextAsInterface( '**' . $identifier->noun . '(' . |
297 | $identifier->evidence . ')' ); |
298 | } |
299 | } else { |
300 | $wgOut->addWikiTextAsInterface( '** not found' ); |
301 | } |
302 | } |
303 | } |
304 | } |
305 | |
306 | public function getInputHash(): string { |
307 | if ( $this->inputHash ) { |
308 | return $this->inputHash; |
309 | } else { |
310 | return parent::getInputHash(); |
311 | } |
312 | } |
313 | |
314 | public function getRevisionID(): int { |
315 | return $this->revisionID; |
316 | } |
317 | |
318 | public function setRevisionID( int $ID ): void { |
319 | $this->revisionID = $ID; |
320 | } |
321 | |
322 | public function updateObservations( $dbw = null ) { |
323 | $this->readFromCache(); |
324 | preg_match_all( |
325 | "#<(mi|mo|mtext)( ([^>].*?))?>(.*?)(<!--.*-->)?</\\1>#u", $this->getMathml(), $rule, |
326 | PREG_SET_ORDER |
327 | ); |
328 | |
329 | $dbw = $dbw ?: MediaWikiServices::getInstance() |
330 | ->getConnectionProvider() |
331 | ->getPrimaryDatabase(); |
332 | |
333 | $dbw->startAtomic( __METHOD__ ); |
334 | $dbw->delete( "mathobservation", |
335 | [ "mathobservation_inputhash" => $this->getInputHash() ] ); |
336 | LoggerFactory::getInstance( |
337 | 'MathSearch' |
338 | )->warning( 'delete obervations for ' . bin2hex( $this->getInputHash() ) ); |
339 | foreach ( $rule as $feature ) { |
340 | $dbw->insert( "mathobservation", [ |
341 | "mathobservation_inputhash" => $this->getInputHash(), |
342 | "mathobservation_featurename" => trim( $feature[4] ), |
343 | "mathobservation_featuretype" => $feature[1], |
344 | ] ); |
345 | LoggerFactory::getInstance( |
346 | 'MathSearch' |
347 | )->warning( 'insert observation for ' . bin2hex( $this->getInputHash() ) |
348 | . trim( $feature[4] ) ); |
349 | } |
350 | $dbw->endAtomic( __METHOD__ ); |
351 | } |
352 | |
353 | /** |
354 | * @param string $identifier |
355 | * @return bool|IResultWrapper |
356 | */ |
357 | public function getNouns( string $identifier ) { |
358 | $dbr = MediaWikiServices::getInstance() |
359 | ->getConnectionProvider() |
360 | ->getReplicaDatabase(); |
361 | $pageName = $this->getPageTitle(); |
362 | if ( $pageName === false ) { |
363 | return false; |
364 | } |
365 | return $dbr->select( 'mathidentifier', |
366 | [ 'noun', 'evidence' ], |
367 | [ 'pageTitle' => $pageName, 'identifier' => $identifier ], |
368 | __METHOD__, |
369 | [ 'ORDER BY' => 'evidence DESC', 'LIMIT' => 5 ] |
370 | ); |
371 | } |
372 | |
373 | public function getPageTitle() { |
374 | $revisionRecord = MediaWikiServices::getInstance() |
375 | ->getRevisionLookup() |
376 | ->getRevisionById( $this->getRevisionID() ); |
377 | if ( $revisionRecord ) { |
378 | $linkTarget = $revisionRecord->getPageAsLinkTarget(); |
379 | $title = Title::newFromLinkTarget( $linkTarget ); |
380 | return (string)$title; |
381 | } else { |
382 | return false; |
383 | } |
384 | } |
385 | |
386 | /** |
387 | * Gets all occurences of the tex. |
388 | * |
389 | * @param bool $currentOnly |
390 | * |
391 | * @return self[] |
392 | */ |
393 | public function getAllOccurrences( bool $currentOnly = true ): array { |
394 | $out = []; |
395 | $dbr = MediaWikiServices::getInstance() |
396 | ->getConnectionProvider() |
397 | ->getReplicaDatabase(); |
398 | $res = $dbr->select( |
399 | 'mathindex', self::dbIndexFieldsArray(), |
400 | [ 'mathindex_inputhash' => $this->getInputHash() ] |
401 | ); |
402 | |
403 | foreach ( $res as $row ) { |
404 | $var = self::constructformpagerow( $row ); |
405 | if ( $var ) { |
406 | if ( $currentOnly === false || $var->isCurrent() ) { |
407 | array_push( $out, $var ); |
408 | } |
409 | } |
410 | } |
411 | return $out; |
412 | } |
413 | |
414 | /** |
415 | * @return bool |
416 | */ |
417 | public function isCurrent(): bool { |
418 | $revisionRecord = MediaWikiServices::getInstance() |
419 | ->getRevisionLookup() |
420 | ->getRevisionById( $this->revisionID ); |
421 | if ( $revisionRecord === null ) { |
422 | return false; |
423 | } else { |
424 | return $revisionRecord->isCurrent(); |
425 | } |
426 | } |
427 | |
428 | /** |
429 | * @param bool $hidePage |
430 | * |
431 | * @return string |
432 | */ |
433 | public function printLink2Page( bool $hidePage = true ): string { |
434 | $pageString = $hidePage ? "" : $this->getPageTitle() . " "; |
435 | $anchor = MathSearchHooks::generateMathAnchorString( |
436 | $this->getRevisionID(), $this->getAnchorID() |
437 | ); |
438 | return "[[{$this->getPageTitle()}{$anchor}|{$pageString}Eq: {$this->getAnchorID()}]]"; |
439 | } |
440 | |
441 | public function getAnchorID(): string { |
442 | return $this->anchorID; |
443 | } |
444 | |
445 | public function setAnchorID( string $id ) { |
446 | $this->anchorID = $id; |
447 | } |
448 | |
449 | /** @inheritDoc */ |
450 | public function render( $forceReRendering = false ) { |
451 | } |
452 | |
453 | /** @inheritDoc */ |
454 | public function getSvg( $render = 'render' ) { |
455 | if ( $render === 'force' ) { |
456 | $md = new MathoidDriver( $this->getUserInputTex(), $this->getInputType() ); |
457 | return $md->getSvg(); |
458 | } |
459 | return parent::getSvg( $render ); // TODO: Change the autogenerated stub |
460 | } |
461 | |
462 | public function addIdentifierTitle( $arg ): string { |
463 | // return '<mi>X</mi>'; |
464 | $attribs = preg_replace( '/title\s*=\s*"(.*)"/', '', $arg[2] ); |
465 | $content = $arg[4]; |
466 | $nouns = $this->getNouns( $content ); |
467 | $title = 'not set'; |
468 | if ( $nouns ) { |
469 | foreach ( $nouns as $identifier ) { |
470 | $title .= '**' . $identifier->noun . '(' . $identifier->evidence . ')'; |
471 | } |
472 | } else { |
473 | $title = '** not found'; |
474 | } |
475 | return '<' . $arg[1] . " title=\"$title\"" . $attribs . '>' . $arg[4] . '</' . $arg[1] . |
476 | '>'; |
477 | } |
478 | |
479 | /** |
480 | * @return null|Revision |
481 | */ |
482 | public function getRevision(): ?RevisionRecord { |
483 | $revisionRecord = MediaWikiServices::getInstance() |
484 | ->getRevisionLookup() |
485 | ->getRevisionById( $this->revisionId ); |
486 | // TODO replace this public method with one returning RevisionRecord instead |
487 | if ( $revisionRecord ) { |
488 | return new Revision( $revisionRecord ); |
489 | } |
490 | return null; |
491 | } |
492 | |
493 | /** |
494 | * @return array<string,stdClass[]> |
495 | */ |
496 | public function getRelations(): array { |
497 | $dbr = MediaWikiServices::getInstance() |
498 | ->getConnectionProvider() |
499 | ->getReplicaDatabase(); |
500 | $selection = $dbr->select( 'mathsemantics', [ 'identifier', 'evidence', 'noun' ], |
501 | [ 'revision_id' => $this->revisionID ], __METHOD__, |
502 | [ 'ORDER BY' => 'evidence desc' ] ); |
503 | if ( $selection ) { |
504 | $result = []; |
505 | foreach ( $selection as $row ) { |
506 | $key = $row->identifier; |
507 | if ( !isset( $result[$key] ) ) { |
508 | $result[$key] = []; |
509 | } |
510 | $result[$key][] = (object)[ 'definition' => $row->noun ]; |
511 | } |
512 | return $result; |
513 | } else { |
514 | $m = new MathosphereDriver( $this->revisionID ); |
515 | if ( $m->analyze() ) { |
516 | return $m->getRelations(); |
517 | } else { |
518 | LoggerFactory::getInstance( 'MathSearch' ) |
519 | ->error( 'Error contacting mathosphere.' ); |
520 | return []; |
521 | } |
522 | } |
523 | } |
524 | |
525 | protected function getMathTableName(): string { |
526 | return 'mathlog'; |
527 | } |
528 | |
529 | /** |
530 | * @return MathoidDriver|false |
531 | */ |
532 | public function getTexInfo() { |
533 | $m = new MathoidDriver( $this->userInputTex ); |
534 | if ( $m->texvcInfo() ) { |
535 | return $m; |
536 | } else { |
537 | return false; |
538 | } |
539 | } |
540 | |
541 | public function getWikiText(): string { |
542 | $attributes = ''; |
543 | foreach ( $this->params as $key => $value ) { |
544 | if ( $key ) { |
545 | $attributes .= " $key=\"$value\""; |
546 | } else { |
547 | $attributes .= " $value"; |
548 | } |
549 | } |
550 | return "<math$attributes>{$this->userInputTex}</math>"; |
551 | } |
552 | |
553 | public function getMathMlAltText() { |
554 | $mml = $this->getMathml(); |
555 | if ( preg_match( '/<math.+alttext="(.*?)".*>/', $mml, $res ) ) { |
556 | return $res[1]; |
557 | } |
558 | return ''; |
559 | } |
560 | |
561 | public static function getSvgWidth( $svg ) { |
562 | if ( preg_match( "/width=\"(.*?)(ex|px|em)?\"/", $svg, $matches ) ) { |
563 | return $matches; |
564 | } |
565 | return 0; |
566 | } |
567 | |
568 | public static function getSvgHeight( $svg ) { |
569 | if ( preg_match( "/height=\"(.*?)(ex|px|em)?\"/", $svg, $matches ) ) { |
570 | return $matches; |
571 | } |
572 | return 0; |
573 | } |
574 | |
575 | /** |
576 | * @param MathRenderer $renderer |
577 | * @param float $factor |
578 | * |
579 | * @return string |
580 | */ |
581 | public static function getReSizedSvgLink( MathRenderer $renderer, $factor = 2 ): string { |
582 | $width = self::getSvgWidth( $renderer->getSvg() ); |
583 | $width = $width[1] * $factor . $width[2]; |
584 | $height = self::getSvgHeight( $renderer->getSvg() ); |
585 | $height = $height[1] * $factor . $height[2]; |
586 | $reflector = new ReflectionObject( $renderer ); |
587 | $method = $reflector->getMethod( 'getFallbackImage' ); |
588 | $method->setAccessible( true ); |
589 | $fbi = $method->invoke( $renderer ); |
590 | $fbi = preg_replace( "/width: (.*?)(ex|px|em)/", "width: $width", $fbi ); |
591 | return preg_replace( "/height: (.*?)(ex|px|em)/", "height: $height", $fbi ); |
592 | } |
593 | |
594 | public function getRbi(): \MediaWiki\Extension\Math\MathRestbaseInterface { |
595 | return $this->rbi; |
596 | } |
597 | |
598 | /** |
599 | * @return string |
600 | */ |
601 | public function getPostData(): string { |
602 | return $this->postData; |
603 | } |
604 | |
605 | /** |
606 | * @param string $postData |
607 | */ |
608 | public function setPostData( $postData ) { |
609 | $this->postData = $postData; |
610 | } |
611 | |
612 | /** |
613 | * @return int time in ms |
614 | */ |
615 | public function getRenderingTime(): int { |
616 | return $this->renderingTime; |
617 | } |
618 | |
619 | /** |
620 | * @param int|float $renderingTime either in ms as int or seconds as float |
621 | */ |
622 | public function setRenderingTime( $renderingTime ) { |
623 | if ( is_float( $renderingTime ) ) { |
624 | $this->renderingTime = (int)( $renderingTime * 1000 ); |
625 | } elseif ( is_int( $renderingTime ) ) { |
626 | $this->renderingTime = $renderingTime; |
627 | } else { |
628 | throw new MWException( __METHOD__ . ': does not support type ' . gettype( $renderingTime ) ); |
629 | } |
630 | } |
631 | |
632 | /** |
633 | * Gets an array that matches the variables of the class to the debug database columns |
634 | * @return array |
635 | */ |
636 | protected function dbDebugOutArray(): array { |
637 | return [ |
638 | 'math_log' => $this->getLog(), |
639 | 'math_mode' => self::MODE_2_USER_OPTION[ $this->getMode() ], |
640 | 'math_post' => $this->getPostData(), |
641 | 'math_rederingtime' => $this->getRenderingTime(), |
642 | ]; |
643 | } |
644 | |
645 | public function writeToCache() { |
646 | # Now save it back to the DB: |
647 | if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) { |
648 | return; |
649 | } |
650 | $outArray = $this->dbOutArray(); |
651 | $mathTableName = 'mathlog'; |
652 | $fname = __METHOD__; |
653 | if ( $this->isInDatabase() ) { |
654 | $this->debug( 'Update database entry' ); |
655 | $inputHash = $this->getInputHash(); |
656 | DeferredUpdates::addCallableUpdate( function () use ( |
657 | $outArray, $inputHash, $mathTableName, $fname |
658 | ) { |
659 | $dbw = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->getPrimaryDatabase(); |
660 | |
661 | $dbw->update( $mathTableName, $outArray, |
662 | [ 'math_inputhash' => $inputHash ], $fname ); |
663 | $this->logger->debug( |
664 | 'Row updated after db transaction was idle: ' . |
665 | var_export( $outArray, true ) . " to database" ); |
666 | } ); |
667 | } else { |
668 | $this->storedInCache = true; |
669 | $this->debug( 'Store new entry in database' ); |
670 | DeferredUpdates::addCallableUpdate( function () use ( |
671 | $outArray, $mathTableName, $fname |
672 | ) { |
673 | $dbw = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->getPrimaryDatabase(); |
674 | $dbw->insert( $mathTableName, $outArray, $fname, [ 'IGNORE' ] ); |
675 | LoggerFactory::getInstance( 'Math' )->debug( |
676 | 'Row inserted after db transaction was idle {out}.', |
677 | [ |
678 | 'out' => var_export( $outArray, true ), |
679 | ] |
680 | ); |
681 | if ( $dbw->affectedRows() == 0 ) { |
682 | // That's the price for the delayed update. |
683 | $this->logger->warning( |
684 | 'Entry could not be written. Might be changed in between.' ); |
685 | } |
686 | } ); |
687 | } |
688 | } |
689 | |
690 | public function readFromCache(): bool { |
691 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
692 | $rpage = $dbr->selectRow( 'mathlog', |
693 | $this->dbInArray(), |
694 | [ 'math_inputhash' => $this->getInputHash() ], |
695 | __METHOD__ ); |
696 | if ( $rpage !== false ) { |
697 | $this->initializeFromCache( $rpage ); |
698 | $this->storedInCache = true; |
699 | return true; |
700 | } else { |
701 | # Missing from the database and/or the render cache |
702 | $this->storedInCache = false; |
703 | return false; |
704 | } |
705 | } |
706 | |
707 | protected function dbInArray() { |
708 | $out = MathRenderer::dbInArray(); |
709 | $out = array_diff( $out, [ 'math_inputtex' ] ); |
710 | $out[] = 'math_input'; |
711 | return $out; |
712 | } |
713 | |
714 | protected function dbOutArray() { |
715 | $out = MathRenderer::dbOutArray(); |
716 | $out['math_input'] = $out['math_inputtex']; |
717 | unset( $out['math_inputtex'] ); |
718 | $out += $this->dbDebugOutArray(); |
719 | return $out; |
720 | } |
721 | |
722 | /** |
723 | * Reads the values from the database but does not overwrite set values with empty values |
724 | * @param stdClass $rpage (a database row) |
725 | */ |
726 | public function initializeFromCache( $rpage ) { |
727 | $this->inputHash = $rpage->math_inputhash; // MUST NOT BE NULL |
728 | if ( !empty( $rpage->math_mathml ) ) { |
729 | $this->mathml = $rpage->math_mathml; |
730 | } |
731 | if ( !empty( $rpage->math_input ) ) { |
732 | // in the current database the field is probably not set. |
733 | $this->userInputTex = $rpage->math_input; |
734 | } |
735 | if ( !empty( $rpage->math_tex ) ) { |
736 | $this->tex = $rpage->math_tex; |
737 | } |
738 | if ( !empty( $rpage->math_svg ) ) { |
739 | $this->svg = $rpage->math_svg; |
740 | } |
741 | $this->changed = false; |
742 | } |
743 | } |