Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
15.99% covered (danger)
15.99%
63 / 394
17.65% covered (danger)
17.65%
9 / 51
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathObject
15.99% covered (danger)
15.99%
63 / 394
17.65% covered (danger)
17.65%
9 / 51
6517.01
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 / 5
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\Extension\Math\MathMathML;
4use MediaWiki\Extension\Math\MathRenderer;
5use MediaWiki\Logger\LoggerFactory;
6use MediaWiki\MediaWikiServices;
7use MediaWiki\Revision\RevisionRecord;
8
9class 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}