Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.69% covered (warning)
87.69%
57 / 65
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
LocalChecker
87.69% covered (warning)
87.69%
57 / 65
60.00% covered (warning)
60.00%
6 / 10
20.75
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 isValid
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getValidTex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 run
95.65% covered (success)
95.65%
22 / 23
0.00% covered (danger)
0.00%
0 / 1
6
 getError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPresentationMathMLFragment
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getInputCacheKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 runCheck
80.00% covered (warning)
80.00%
20 / 25
0.00% covered (danger)
0.00%
0 / 1
6.29
 setContext
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setHookContainer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Math\InputCheck;
4
5use Exception;
6use MediaWiki\Extension\Math\Hooks\HookRunner;
7use MediaWiki\Extension\Math\MathRenderer;
8use MediaWiki\Extension\Math\WikiTexVC\TexVC;
9use MediaWiki\HookContainer\HookContainer;
10use MediaWiki\Message\Message;
11use Wikimedia\ObjectCache\WANObjectCache;
12
13class LocalChecker extends BaseChecker {
14
15    public const VERSION = 1;
16    private const VALID_TYPES = [ 'tex', 'inline-tex', 'chem' ];
17    private ?Message $error = null;
18    private ?string $mathMl = null;
19
20    private string $type;
21    private WANObjectCache $cache;
22
23    private bool $isChecked = false;
24    private ?MathRenderer $context = null;
25    private ?HookContainer $hookContainer = null;
26
27    public function __construct( WANObjectCache $cache, $tex = '', string $type = 'tex', bool $purge = false ) {
28        $this->cache = $cache;
29        parent::__construct( $tex, $purge );
30        $this->type = $type;
31    }
32
33    public function isValid(): bool {
34        $this->run();
35        return parent::isValid();
36    }
37
38    public function getValidTex(): ?string {
39        $this->run();
40        return parent::getValidTex();
41    }
42
43    public function run() {
44        if ( $this->isChecked ) {
45            return;
46        }
47        if ( !in_array( $this->type, self::VALID_TYPES, true ) ) {
48            $this->error = $this->errorObjectToMessage(
49                (object)[ "error" => "Unsupported type passed to LocalChecker: " . $this->type ], "LocalCheck" );
50            return;
51        }
52        try {
53            $cacheInputKey = $this->getInputCacheKey();
54            if ( $this->purge ) {
55                $this->cache->delete( $cacheInputKey, WANObjectCache::TTL_INDEFINITE );
56            }
57            $result = $this->cache->getWithSetCallback(
58                $cacheInputKey,
59                WANObjectCache::TTL_INDEFINITE,
60                [ $this, 'runCheck' ],
61                [ 'version' => self::VERSION ],
62            );
63        } catch ( Exception $e ) { // @codeCoverageIgnoreStart
64            // This is impossible since errors are thrown only if the option debug would be set.
65            $this->error = Message::newFromKey( 'math_failure' );
66            return;
67            // @codeCoverageIgnoreEnd
68        }
69        if ( $result['status'] === '+' ) {
70            $this->isValid = true;
71            $this->validTeX = $result['output'];
72            $this->mathMl = $result['mathml'];
73        } else {
74            $this->error = $this->errorObjectToMessage(
75                (object)[ "error" => (object)$result["error"] ],
76                "LocalCheck" );
77        }
78        $this->isChecked = true;
79    }
80
81    /**
82     * Returns the string of the last error.
83     * @return ?Message
84     */
85    public function getError(): ?Message {
86        $this->run();
87        return $this->error;
88    }
89
90    public function getPresentationMathMLFragment(): ?string {
91        $this->run();
92        return $this->mathMl;
93    }
94
95    public function getInputCacheKey(): string {
96        return $this->cache->makeGlobalKey(
97            self::class,
98            md5( $this->type . '-' . $this->inputTeX )
99        );
100    }
101
102    public function runCheck(): array {
103        if ( $this->type == 'chem' ) {
104            $options = [ 'usemhchem' => true, 'usemhchemtexified' => true ];
105            $texifyMhchem = true;
106        } else {
107            $options = [];
108            $texifyMhchem = false;
109        }
110
111        try {
112            $warnings = [];
113            $result = ( new TexVC() )->check( $this->inputTeX, $options, $warnings, $texifyMhchem );
114        } catch ( Exception $e ) { // @codeCoverageIgnoreStart
115            // This is impossible since errors are thrown only if the option debug would be set.
116            $this->error = Message::newFromKey( 'math_failure' );
117
118            return [];
119            // @codeCoverageIgnoreEnd
120        }
121        if ( $result['status'] === '+' ) {
122            $result['mathml'] = $result['input']->renderMML();
123            $out = [
124                'status' => '+',
125                'output' => $result['output'],
126                'mathml' => $result['mathml']
127            ];
128        } else {
129            $out = [
130                'status' => $result['status'],
131                'error' => $result['error'],
132            ];
133        }
134        if ( $this->context !== null && $this->hookContainer !== null ) {
135            $resultObject = (object)$result;
136            ( new HookRunner( $this->hookContainer ) )->onMathRenderingResultRetrieved(
137                $this->context,
138                $resultObject
139            );
140        }
141        return $out;
142    }
143
144    public function setContext( ?MathRenderer $renderer ): void {
145        $this->context = $renderer;
146    }
147
148    public function setHookContainer( ?HookContainer $hookContainer ): void {
149        $this->hookContainer = $hookContainer;
150    }
151}