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