Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.03% covered (danger)
16.03%
63 / 393
17.65% covered (danger)
17.65%
9 / 51
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathObject
16.03% covered (danger)
16.03%
63 / 393
17.65% covered (danger)
17.65%
9 / 51
6507.70
0.00% covered (danger)
0.00%
0 / 1
 hash2md5
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 findSimilarPages
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
 cloneFromRenderer
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 constructformpage
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 newFromRevisionText
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 dbIndexFieldsArray
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 constructformpagerow
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 extractMathTagsFromWikiText
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 updateStatistics
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 getStatusCode
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setStatusCode
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTimestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setTimestamp
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getLog
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLog
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getIndexTimestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getObservations
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
90
 getInputHash
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getRevisionID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRevisionID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateObservations
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
12
 getNouns
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 getPageTitle
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 getAllOccurrences
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 isCurrent
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 printLink2Page
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getAnchorID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setAnchorID
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSvg
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 addIdentifierTitle
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 getRevision
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getRelations
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
30
 getMathTableName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTexInfo
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getWikiText
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getMathMlAltText
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getSvgWidth
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getSvgHeight
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getReSizedSvgLink
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 getRbi
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPostData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPostData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRenderingTime
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRenderingTime
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 dbDebugOutArray
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 writeToCache
57.14% covered (warning)
57.14%
20 / 35
0.00% covered (danger)
0.00%
0 / 1
5.26
 readFromCache
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 dbInArray
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 dbOutArray
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 initializeFromCache
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
5.20
1<?php
2
3use MediaWiki\Deferred\DeferredUpdates;
4use MediaWiki\Extension\Math\MathMathML;
5use MediaWiki\Extension\Math\MathRenderer;
6use MediaWiki\Logger\LoggerFactory;
7use MediaWiki\MediaWikiServices;
8use MediaWiki\Revision\RevisionRecord;
9use MediaWiki\Title\Title;
10use Wikimedia\Rdbms\IResultWrapper;
11
12class 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}