Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 381
0.00% covered (danger)
0.00%
0 / 40
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMlpEval
0.00% covered (danger)
0.00%
0 / 381
0.00% covered (danger)
0.00%
0 / 40
17292
0.00% covered (danger)
0.00%
0 / 1
 isTexInputChanged
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIdentifiers
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setStep
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 loadData
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
420
 execute
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 getStep
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRandomPageText
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 setPage
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 setRevision
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 log
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enableMathStyles
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 getSubStep
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNextStep
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 getPreviousStep
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
72
 getRenderingFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFId
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getRandomFId
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
12
 getFId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOldId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRevisionTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 printIntorduction
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 1
420
 writeLog
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
56
 resetPage
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 resetFormula
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 printSource
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getMathMLRenderingAsHtmlFragment
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 printFormula
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 printTitle
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 printIntro
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getWikiTextLink
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 printFormulaRef
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 printPrefix
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 printMathObjectInContext
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 getRelations
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getSpeechRuleText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMathMlRenderer
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 updateTex
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 removeSVGs
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Extension\Math\MathRenderer;
4use MediaWiki\Logger\LoggerFactory;
5use MediaWiki\MediaWikiServices;
6use MediaWiki\Revision\RevisionRecord;
7
8/**
9 * MediaWiki MathSearch extension
10 *
11 * (c) 2015 Moritz Schubotz
12 * GPLv2 license; info in main package.
13 *
14 * @file
15 * @ingroup extensions
16 */
17class SpecialMlpEval extends SpecialPage {
18
19    public const STEP_PAGE = 1;
20    public const STEP_FORMULA = 2;
21    public const STEP_TEX = 3;
22    public const STEP_RENDERING = 4;
23    public const STEP_IDENTIFIERS = 5;
24    public const STEP_DEFINITIONS = 6;
25    public const STEP_FINISHED = 7;
26    private const MAX_ATTEMPTS = 10;
27
28    private $subStepNames = [
29        ''   => '',
30        '4'  => '',
31        '4a' => 'Mathoid-',
32        '4b' => 'Native-',
33        '4c' => 'LaTeXML-'
34    ];
35    /** @var MathObject */
36    private $selectedMathTag;
37    /** @var int */
38    private $step;
39    /** @var MathIdGenerator */
40    private $mathIdGen;
41    /** @var int */
42    private $oldId;
43    /** @var bool|string */
44    private $lastError = false;
45    /** @var string */
46    private $fId;
47    /** @var RevisionRecord */
48    private $revisionRecord;
49    private $texInputChanged = false;
50    private $identifiers = [];
51    private $relations;
52    private $speechRuleText;
53    /** @var string */
54    private $subStep = '';
55    /** @var string[] */
56    private $renderingFields = [ 'absolute', 'best', 'size', 'spacing', 'integration', 'font' ];
57
58    /**
59     * @return bool
60     */
61    public function isTexInputChanged() {
62        return $this->texInputChanged;
63    }
64
65    /**
66     * @return array
67     */
68    public function getIdentifiers() {
69        return $this->identifiers;
70    }
71
72    function __construct() {
73        parent::__construct( 'MlpEval' );
74    }
75
76    private function setStep( $step ) {
77        $this->step = $step;
78        return $step;
79    }
80
81    private function loadData() {
82        $req = $this->getRequest();
83        $revId = $req->getInt( 'oldId' );
84        if ( $req->getText( 'wp1-page' ) ) {
85            $t = Title::newFromText( $req->getText( 'wp1-page' ) );
86            if ( $this->setPage( $t ) ) {
87                if ( $revId && $revId != $this->getOldId() ) {
88                    $this->writeLog( "$revId was not selected", -1, $revId );
89                }
90            }
91            $revId = $this->getOldId();
92        }
93        if ( $revId === 0 ) {
94            return $this->setStep( 1 );
95        }
96        if ( $this->setRevision( $revId ) === false ) {
97            return $this->setStep( 1 );
98        }
99        if ( $req->getText( 'pgRst' ) ) {
100            return $this->resetPage();
101        } elseif ( $req->getInt( 'oldStep' ) === 1 ) {
102            $this->writeLog( "pgSelect: User selects page" . $revId, 1 );
103        }
104        $fId = $req->getText( 'fId' );
105        $oldStep = $req->getInt( 'oldStep' );
106        $restButton = $req->getBool( 'fRst' );
107        if ( $oldStep >= self::STEP_FINISHED || $restButton ) {
108            $this->resetFormula();
109            $fId = '';
110        }
111        if ( $fId === '' ) {
112            $this->setFId( $this->getRandomFId() );
113            return $this->setStep( 2 );
114        }
115        if ( $this->setFId( $fId ) === false ) {
116            return $this->setStep( 2 );
117        }
118        if ( $req->getInt( 'oldStep' ) == 3 || $req->getInt( 'oldStep' ) == 4 ) {
119            $this->setStep( 4 );
120            $this->subStep = $req->getText( 'oldSubStep' );
121            foreach ( $this->renderingFields as $key ) {
122                $val = $req->getVal( "wp4-$key" );
123                $substep = $this->subStep;
124                if ( $val ) {
125                    $req->setVal( "4-$key-$substep", $val );
126                    $req->unsetVal( "wp4-$key" );
127                }
128            }
129            $nextStep = $this->getNextStep();
130            if ( $nextStep !== 5 ) {
131                $this->subStep = $nextStep;
132                $this->writeLog( "User updates step 4.", 4 );
133                return 4;
134            } else {
135                $this->writeLog( "User completes step 4.", 4 );
136                return $this->setStep( 5 );
137            }
138        }
139        if ( $req->getArray( 'wp5-identifiers' ) ) {
140            $this->identifiers = $req->getArray( 'wp5-identifiers' );
141            $missing = $req->getText( 'wp5-missing' );
142            if ( $missing ) {
143                // TODO: Check for invalid TeX
144                $this->identifiers =
145                    array_merge( $this->identifiers, preg_split( '/[\n\r]/', $missing ) );
146            }
147        }
148        $this->writeLog( "User completes step " . $req->getInt( 'oldStep' ), $req->getInt( 'oldStep' ) );
149        return $this->setStep( $req->getInt( 'oldStep' ) + 1 );
150    }
151
152    /**
153     * The main function
154     * @param string|null $par
155     */
156    public function execute( $par ) {
157        $this->loadData();
158        $this->setHeaders();
159        if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
160            $this->printIntorduction();
161        }
162        $form = new MlpEvalForm( $this );
163        $form->show();
164        if ( $this->step == 5 || $this->step == 6 ) {
165            $this->getOutput()->addWikiMsg( 'math-lp-5-footer' );
166        }
167    }
168
169    public function getStep() {
170        return $this->step;
171    }
172
173    public function getRandomPageText() {
174        try {
175            $uid = $this->getUser()->getId();
176            $dbr = MediaWikiServices::getInstance()
177            ->getConnectionProvider()
178            ->getReplicaDatabase();
179            $results = $dbr->selectFieldValues( 'math_review_list', 'revision_id',
180                    "revision_id not in (SELECT revision_id from math_mlp where user_id = $uid )",
181                    __METHOD__, [
182                        'LIMIT'    => 1,
183                        'ORDER BY' => 'priority desc'
184                ] );
185            if ( $results ) {
186                $this->setRevision( $results[0] );
187                return $this->revisionRecord->getPageAsLinkTarget()->getText();
188            }
189        } catch ( Exception $e ) {
190            // empty
191        }
192        $rp = MediaWikiServices::getInstance()->getSpecialPageFactory()->getPage( 'Randompage' );
193        $rp->setContext( $this->getContext() );
194        for ( $i = 0; $i < self::MAX_ATTEMPTS; $i++ ) {
195            $title = $rp->getRandomTitle();
196            if ( $title !== null && $this->setPage( $title ) ) {
197                $this->lastError = "";
198                return $title->getText();
199            }
200        }
201        $this->log()->warning( "Could not find suitable page with math:" . $this->lastError );
202        $this->lastError = "";
203        return "";
204    }
205
206    private function setPage( Title $title ) {
207        if ( $title === null ) {
208            $this->lastError = "Title was null.";
209            return false;
210        }
211        if ( $title->isRedirect() ) {
212            $this->lastError = "Redirects are not supported";
213            return false;
214        }
215        $revision = $title->getLatestRevID();
216        if ( $revision ) {
217            return $this->setRevision( $revision );
218        } else {
219            $this->lastError = "invalid revision";
220            return false;
221        }
222    }
223
224    /**
225     * @param int $revId
226     * @return bool
227     */
228    private function setRevision( $revId = 0 ) {
229        if ( $revId == 0 && $this->oldId > 0 ) {
230            $revId = $this->oldId;
231        } else {
232            $this->oldId = $revId;
233        }
234        if ( $revId == 0 ) {
235            $this->lastError = "no revision id given";
236            return false;
237        }
238        $this->revisionRecord = MediaWikiServices::getInstance()
239            ->getRevisionLookup()
240            ->getRevisionById( $revId );
241        $this->mathIdGen = MathIdGenerator::newFromRevisionRecord(
242            $this->revisionRecord
243        );
244        $tagCount = count( $this->mathIdGen->getMathTags() );
245        if ( $tagCount == 0 ) {
246            $this->lastError = "has no math tags";
247            return false;
248        }
249        return true;
250    }
251
252    /**
253     * @return \Psr\Log\LoggerInterface
254     */
255    private function log() {
256        return LoggerFactory::getInstance( 'MathSearch' );
257    }
258
259    private function enableMathStyles() {
260        $out = $this->getOutput();
261        $styles = [ 'ext.math.desktop.styles', 'ext.math.scripts' ];
262        if ( $this->subStep == '4b' ) {
263            $styles[] = 'ext.math-svg.styles';
264        } elseif ( $this->subStep ) {
265            $styles[] = 'ext.math-mathml.styles';
266        } else {
267            $styles[] = 'ext.math.styles';
268        }
269        $out->addModuleStyles( $styles );
270    }
271
272    public function getSubStep() {
273        return $this->subStep;
274    }
275
276    public function getNextStep() {
277        if ( $this->step == 4 ) {
278            switch ( $this->subStep ) {
279                case '':
280                    return '4';
281                case '4':
282                    return '4a';
283                case '4a':
284                    return '4b';
285                case '4b':
286                    return '4c';
287                default:
288                    return 5;
289            }
290        } else {
291            return $this->step + 1;
292        }
293    }
294
295    public function getPreviousStep( $step = false, $substep = '' ) {
296        if ( $step === false ) {
297            $step = $this->step;
298            $substep = $this->subStep;
299        }
300        if ( $step == 5 ) {
301            return '4c';
302        }
303        if ( $step == 4 ) {
304            switch ( $substep ) {
305                case '4a':
306                    return '4';
307                case '4b':
308                    return '4a';
309                case '4c':
310                    return '4b';
311                // case '4':
312                default:
313                    return '3';
314            }
315        } else {
316            return $step - 1;
317        }
318    }
319
320    /**
321     * @return string[]
322     */
323    public function getRenderingFields() {
324        return $this->renderingFields;
325    }
326
327    protected function getGroupName() {
328        return 'mathsearch';
329    }
330
331    /**
332     * @param string $fId
333     *
334     * @return true
335     */
336    private function setFId( $fId = '' ) {
337        if ( $fId == '' && $this->fId != '' ) {
338            $fId = $this->fId;
339        } else {
340            $this->fId = $fId;
341        }
342        $this->selectedMathTag = MathObject::newFromRevisionText( $this->oldId, $fId );
343        return true;
344    }
345
346    /**
347     * @return string
348     */
349    private function getRandomFId() {
350        try {
351            $uid = $this->getUser()->getId();
352            $rid = $this->revisionRecord->getId();
353            $dbr = MediaWikiServices::getInstance()
354            ->getConnectionProvider()
355            ->getReplicaDatabase();
356            // Note that the math anchor is globally unique
357            $results = $dbr->selectFieldValues( 'math_review_list', 'anchor',
358                "anchor not in (SELECT anchor from math_mlp where user_id = $uid " .
359                "and anchor is not NULL) and revision_id = $rid",
360                __METHOD__, [
361                    'LIMIT'    => 1,
362                    'ORDER BY' => 'priority desc'
363                ] );
364            if ( $results ) {
365                return $results[0];
366            }
367        } catch ( Exception $e ) {
368            // empty
369        }
370        $unique = array_rand( $this->mathIdGen->getMathTags() );
371        return $this->mathIdGen->parserKey2fId( $unique );
372    }
373
374    /**
375     * @return string
376     */
377    public function getFId() {
378        return $this->fId;
379    }
380
381    /**
382     * @return int
383     */
384    public function getOldId() {
385        return $this->oldId;
386    }
387
388    /**
389     * @return Title
390     */
391    public function getRevisionTitle() {
392        return Title::newFromLinkTarget( $this->revisionRecord->getPageAsLinkTarget() );
393    }
394
395    private function printIntorduction() {
396        $this->enableMathStyles();
397        $out = $this->getOutput();
398        // $out->addWikiTextAsInterface( "Welcome to the MLP evaluation. Your data will be recorded." );
399        // $out->addWikiTextAsInterface( "You are in step {$this->step} of possible evaluation 5 steps" );
400        $this->printPrefix();
401        switch ( $this->step ) {
402            case self::STEP_PAGE:
403                break;
404            case self::STEP_FORMULA:
405                $this->printMathObjectInContext();
406
407                break;
408            case self::STEP_TEX:
409                $mo = $this->selectedMathTag;
410                $this->printSource( $mo->getUserInputTex(),
411                    $this->msg( 'math-lp-3-pretty-option-1' )->text(), 'latex' );
412                $texInfo = $mo->getTexInfo();
413                if ( $texInfo ) {
414                    if ( $texInfo->getChecked() !== $mo->getUserInputTex() ) {
415                        $this->printSource( $texInfo->getChecked(),
416                            $this->msg( 'math-lp-3-pretty-option-2' )->text(), 'latex' );
417                        $this->texInputChanged = true;
418                    }
419                }
420                break;
421            case self::STEP_RENDERING:
422                $services = MediaWikiServices::getInstance();
423                switch ( $this->subStep ) {
424                    case '4a':
425                        $services->getUserOptionsManager()->setOption( $this->getUser(), 'math', 'mathml' );
426                        $this->printMathObjectInContext( false, false,
427                            $this->getMathMLRenderingAsHtmlFragment( 'mathml' ) );
428                        break;
429                    case '4b':
430                        $services->getUserOptionsManager()->setOption( $this->getUser(), 'math', 'native' );
431                        $this->printMathObjectInContext( false, false,
432                            $this->getMathMLRenderingAsHtmlFragment( 'native' ) );
433                        break;
434                    case '4c':
435                        $services->getUserOptionsManager()->setOption( $this->getUser(), 'math', 'latexml' );
436                        $this->printMathObjectInContext( false, false,
437                            $this->getMathMLRenderingAsHtmlFragment( 'latexml' ),
438                            [ __CLASS__, 'removeSVGs' ] );
439                }
440                break;
441            case self::STEP_IDENTIFIERS:
442                $mo = MathObject::newFromRevisionText( $this->oldId, $this->fId );
443                $md = $mo->getTexInfo();
444                if ( $md ) {
445                    $this->identifiers = array_unique( $md->getIdentifiers() );
446                    if ( $this->getRequest()->getArray( 'wp5-identifiers' ) == null ) {
447                        $this->getRequest()->setVal( 'wp5-identifiers', $this->identifiers );
448                    }
449                }
450                break;
451            case self::STEP_DEFINITIONS:
452                $this->printMathObjectInContext();
453                $this->enableMathStyles();
454                $mo = MathObject::newFromRevisionText( $this->oldId, $this->fId );
455                $this->relations = [];
456                $rels = $mo->getRelations();
457                $srt = $mo->getMathMlAltText();
458                if ( !$srt ) {
459                    // retry to get alltext might be a caching problem
460                    $r = new MathoidDriver( $mo->getUserInputTex() );
461                    $srt = $r->getSpeech();
462                }
463                $this->speechRuleText = $srt;
464                $wd = new WikidataDriver();
465                foreach ( $this->identifiers as $i ) {
466                    $this->relations[$i] = [];
467                    if ( isset( $rels[$i] ) ) {
468                        foreach ( $rels[$i] as $rel ) {
469                            if ( preg_match( '/\[\[(.*)\]\]/', $rel->definition, $m ) ) {
470                                if ( $wd->search( $m[1] ) ) {
471                                    $res = $wd->getResults();
472                                    $this->relations[$i][] = $res[1];
473                                }
474                            } else {
475                                $this->relations[$i][] = $rel->definition;
476                            }
477                        }
478                    }
479                }
480                break;
481        }
482    }
483
484    private function writeLog( $message, $step = false, $revId = false ) {
485        $userId = $this->getUser()->getId();
486        $logData = [
487            'data'   => $this->getRequest()->getValues(),
488            'header' => $this->getRequest()->getAllHeaders()
489        ];
490        $json = json_encode( $logData );
491        if ( $step == false ) {
492            $step = $this->step;
493        }
494        if ( !$revId ) {
495            $revId = $this->oldId;
496        }
497        $row = [
498            'user_id'     => $userId,
499            'step'        => $step,
500            'json_data'   => $json,
501            'revision_id' => $revId,
502            'anchor'      => $this->fId ?? 'undefined', // NULL is no longer allowed
503            'comment'     => $message
504        ];
505        if ( $userId == 0 ) {
506            $this->printSource( var_export( $message, true ), 'Error: No user found to store results.' );
507            return;
508        }
509        $dbw = MediaWikiServices::getInstance()
510            ->getConnectionProvider()
511            ->getPrimaryDatabase();
512
513        $dbw->upsert( 'math_mlp', $row, [ [ 'user_id', 'revision_id', 'anchor', 'step' ] ], $row );
514        if ( $this->fId ) {
515            $dbw->startAtomic( __METHOD__ );
516            $cnt = $dbw->selectField( 'math_mlp', 'count( distinct user_id)', [
517                'revision_id' => $revId,
518                'anchor' => $this->fId
519            ] );
520            if ( $cnt == 1 ) {
521                $row = [
522                        'revision_id' => $revId,
523                        'anchor'      => $this->fId,
524                        'priority'    => 2
525                ];
526                $dbw->upsert( 'math_review_list', $row, [ [ 'revision_id', 'anchor' ] ], $row );
527            } elseif ( $cnt > 1 ) {
528                $dbw->delete( 'math_review_list', [ 'revision_id', 'anchor' ] );
529            }
530            $dbw->endAtomic( __METHOD__ );
531        }
532    }
533
534    private function resetPage() {
535        $req = $this->getRequest();
536        $rndRev = $req->getInt( 'oldId' );
537        $oldStep = $req->getInt( 'oldStep' );
538        if ( $rndRev && $oldStep == 1 ) {
539            $this->writeLog( "$rndRev was not selected, by button", -1, $rndRev );
540        }
541        $req->unsetVal( 'wp1-page' );
542        $req->unsetVal( 'oldId' );
543        $req->unsetVal( 'fId' );
544        $req->unsetVal( 'oldStep' );
545        $this->fId = '';
546        return $this->setStep( 1 );
547    }
548
549    private function resetFormula() {
550        $req = $this->getRequest();
551        $this->writeLog( "pgRst: User selects another formula", $req->getInt( 'oldStep' ) );
552        $valueNames = $req->getValueNames( [ 'oldId', 'wpEditToken' ] );
553        foreach ( $valueNames as $name ) {
554            $req->unsetVal( $name );
555        }
556        $this->fId = '';
557    }
558
559    private function printSource( $source, $description = "", $language = "text", $linestart = true ) {
560        if ( $description ) {
561            $description .= ": ";
562        }
563        $this->getOutput()->addWikiTextAsInterface( "$description<syntaxhighlight lang=\"$language\">" .
564            $source . '</syntaxhighlight>', $linestart );
565    }
566
567    /**
568     * @param string $mode
569     * @param float $factor
570     * @param string|false $tex
571     * @param array $options
572     * @return string
573     */
574    public function getMathMLRenderingAsHtmlFragment(
575        $mode, $factor = 2.5, $tex = false, $options = []
576    ) {
577        $renderer = $this->getMathMlRenderer( $mode, $tex, $options );
578        $largeMathML = $renderer->getMathml();
579        $factor = round( $factor * 100 );
580        return preg_replace(
581            "/<math/",
582            "<math style=\"font-size: {$factor}% !important;\"",
583            $largeMathML
584        );
585    }
586
587    private function printFormula() {
588        $mo = MathObject::newFromRevisionText( $this->oldId, $this->fId );
589        /** @var MathRenderer $renderer */
590        $renderer = MediaWikiServices::getInstance()
591            ->get( 'Math.RendererFactory' )
592            ->getRenderer( $mo->getUserInputTex(), [] );
593        if ( $renderer->render() ) {
594            $output = $renderer->getHtmlOutput();
595        } else {
596            $output = $renderer->getLastError();
597        }
598        $this->getOutput()->addHTML( $output );
599    }
600
601    private function printTitle() {
602        $sectionTitle = $this->msg( "math-lp-{$this->step}-head" )
603            ->params( $this->subStepNames[$this->subStep], $this->subStep );
604        $this->getOutput()->addHTML( "<h2>$sectionTitle</h2>" );
605    }
606
607    private function printIntro() {
608        $msg =
609            new Message( "math-lp-{$this->step}-intro", [ $this->subStepNames[$this->subStep] ] );
610        $this->getOutput()->addWikiTextAsInterface( $msg->text() );
611    }
612
613    private function getWikiTextLink() {
614        $description = "{$this->getRevisionTitle()}#{$this->fId}";
615        return "[[Special:Permalink/{$this->oldId}#{$this->fId}|$description]]";
616    }
617
618    private function printFormulaRef() {
619        $this->getOutput()->addWikiMsg( 'math-lp-formula-ref', $this->getWikiTextLink(),
620            $this->selectedMathTag->getWikiText(), $this->revisionRecord->getTimestamp() );
621    }
622
623    private function printPrefix() {
624        if ( $this->step > 1 ) {
625            $this->printFormulaRef();
626        }
627        if ( $this->step === 1 ) {
628            $this->printIntro();
629            $this->printTitle();
630        } else {
631            $this->printTitle();
632            $this->printIntro();
633        }
634    }
635
636    /**
637     * @param bool $highlight
638     * @param bool $collapsed
639     * @param string|false $formula
640     * @param callback|false $filter
641     */
642    public function printMathObjectInContext(
643        $highlight = true, $collapsed = true, $formula = false, $filter = false
644    ) {
645        if ( $collapsed ) {
646            $collapsed = "mw-collapsed";
647        }
648        $out = $this->getOutput();
649        $hl = new MathHighlighter( $this->fId, $this->oldId, $highlight );
650        $out->addHTML(
651            "<div class=\"toccolours mw-collapsible $collapsed\"  style=\"text-align: left\">"
652        );
653        if ( !$formula ) {
654            $this->printFormula();
655        } else {
656            $out->addHTML( $formula );
657        }
658        $out->addHTML( '<div class="mw-collapsible-content">' );
659
660        $popts = $out->parserOptions();
661        $popts->setInterfaceMessage( false );
662
663        if ( method_exists( ParserFactory::class, 'getInstance' ) ) {
664            // MW 1.39+
665            $parser = MediaWikiServices::getInstance()->getParserFactory()->getInstance();
666        } else {
667            $parser = MediaWikiServices::getInstance()->getParser()->getFreshParser();
668        }
669        $parserOutput = $parser->parse(
670            $hl->getWikiText(), $this->getRevisionTitle(), $popts )->getText();
671        if ( $filter ) {
672            call_user_func_array( $filter, [ &$parserOutput ] );
673        }
674        $out->addHTML( $parserOutput );
675        $out->addHTML( '</div></div>' );
676    }
677
678    /**
679     * @param mixed|null $key
680     * @return mixed
681     */
682    public function getRelations( $key = null ) {
683        if ( $key ) {
684            return $this->relations[$key];
685        }
686        return $this->relations;
687    }
688
689    /**
690     * @return string
691     */
692    public function getSpeechRuleText() {
693        return $this->speechRuleText;
694    }
695
696    /**
697     * @param string $mode
698     * @param string|false $tex
699     * @param array $options
700     * @return MathRenderer
701     */
702    private function getMathMlRenderer( $mode, $tex, $options ) {
703        $this->updateTex( $tex, $options );
704        $renderer = MathRenderer::getRenderer( $tex, $options, $mode );
705        $renderer->checkTeX();
706        $renderer->render();
707        $renderer->writeCache();
708        return $renderer;
709    }
710
711    /**
712     * @param string|false &$tex
713     * @param array &$options
714     */
715    private function updateTex( &$tex, &$options ) {
716        if ( !$tex ) {
717            $tex = $this->selectedMathTag->getUserInputTex();
718            $options = $this->selectedMathTag->getParams();
719        }
720    }
721
722    public static function removeSVGs( &$html ) {
723        // $html = str_replace( 'style="display: none;"><math', '><math', $html );
724        // $html = preg_replace( '/<meta(.*?)mwe-math-fallback-image(.*?)>/', '', $html );
725    }
726
727}