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