Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathMathMLCli
0.00% covered (danger)
0.00%
0 / 86
0.00% covered (danger)
0.00%
0 / 10
930
0.00% covered (danger)
0.00%
0 / 1
 batchEvaluate
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 isMathoidCliConfigured
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 getMathoidCli
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 initializeFromCliResponse
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 renderError
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
42
 getMathoidCliQuery
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 evaluateWithCli
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 render
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 doCheck
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 appendLocationInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Math;
4
5use Exception;
6use MediaWiki\Logger\LoggerFactory;
7use MediaWiki\MediaWikiServices;
8use RuntimeException;
9use stdClass;
10
11/**
12 * @author Moritz Schubotz
13 */
14class MathMathMLCli extends MathMathML {
15
16    /**
17     * @param MathRenderer[] $renderers
18     * @return bool
19     */
20    public static function batchEvaluate( array $renderers ) {
21        $req = [];
22        foreach ( $renderers as $renderer ) {
23            '@phan-var MathMathMLCli $renderer';
24            // checking if the rendering is in the database is no security issue since only the md5
25            // hash of the user input string will be sent to the database
26            if ( !$renderer->isInDatabase() ) {
27                $req[] = $renderer->getMathoidCliQuery();
28            }
29        }
30        if ( count( $req ) === 0 ) {
31            return true;
32        }
33        $exitCode = 1;
34        $res = self::evaluateWithCli( $req, $exitCode );
35        foreach ( $renderers as $renderer ) {
36            '@phan-var MathMathMLCli $renderer';
37            if ( !$renderer->isInDatabase() ) {
38                $renderer->initializeFromCliResponse( $res );
39            }
40        }
41
42        return true;
43    }
44
45    /**
46     * @return bool
47     */
48    public static function isMathoidCliConfigured(): bool {
49        $mathoidCli = self::getMathoidCli();
50        return is_array( $mathoidCli )
51            && isset( $mathoidCli[0] )
52            && is_string( $mathoidCli[0] )
53            && is_executable( $mathoidCli[0] );
54    }
55
56    /**
57     * @return string[]|false
58     */
59    private static function getMathoidCli() {
60        global $wgMathoidCli;
61        if ( is_string( $wgMathoidCli ) ) {
62            return [ $wgMathoidCli ];
63        }
64        if ( is_array( $wgMathoidCli ) ) {
65            return $wgMathoidCli;
66        }
67        return false;
68    }
69
70    /**
71     * @param stdClass $res
72     * @return bool
73     */
74    private function initializeFromCliResponse( $res ) {
75        $mathoidCli = self::getMathoidCli();
76        if ( !property_exists( $res, $this->getInputHash() ) ) {
77            $this->lastError =
78                $this->getError( 'math_mathoid_error', 'cli',
79                    var_export( get_object_vars( $res ), true ) );
80            return false;
81        }
82        if ( $this->isEmpty() ) {
83            $this->lastError = $this->getError( 'math_empty_tex' );
84            return false;
85        }
86        $response = $res->{$this->getInputHash()};
87        if ( !$response->success ) {
88            $this->lastError = $this->renderError( $response );
89            return false;
90        }
91        $this->texSecure = true;
92        $this->tex = $response->sanetex;
93        // The host name is only relevant for the debugging. So using file:// to indicate that the
94        // cli interface seems to be OK.
95        $this->processJsonResult( $response, 'file://' . $mathoidCli[0] );
96        $this->mathStyle = $response->mathoidStyle;
97        $this->changed = true;
98        return true;
99    }
100
101    public function renderError( stdClass $response ): string {
102        $msg = $response->error;
103        try {
104            switch ( $response->detail->status ) {
105                case "F":
106                    $msg .= "\n Found {$response->detail->details}" .
107                            $this->appendLocationInfo( $response );
108                    break;
109                case 'S':
110                case "C":
111                    $msg .= $this->appendLocationInfo( $response );
112                    break;
113                case '-':
114                    // we do not know any cases that triggers this error
115            }
116        } catch ( Exception ) {
117            // use default error message
118        }
119
120        return $this->getError( 'math_mathoid_error', 'cli', $msg );
121    }
122
123    /**
124     * @return array
125     */
126    public function getMathoidCliQuery() {
127        return [
128            'query' => [
129                'q' => $this->getTex(),
130                'type' => $this->getInputType(),
131                'hash' => $this->getInputHash(),
132            ],
133        ];
134    }
135
136    /**
137     * @param mixed $req request
138     * @param int|null &$exitCode
139     * @return mixed
140     */
141    private static function evaluateWithCli( $req, &$exitCode = null ) {
142        $mathoidCli = self::getMathoidCli();
143        $json_req = json_encode( $req );
144        $cmd = MediaWikiServices::getInstance()->getShellCommandFactory()->create();
145        $cmd->params( $mathoidCli );
146        $cmd->input( $json_req );
147        $result = $cmd->execute();
148        if ( $result->getExitCode() != 0 ) {
149            $errorMsg = $result->getStderr();
150            LoggerFactory::getInstance( 'Math' )->error( 'Can not process {req} with config
151             {conf} returns {res}', [
152                'req' => $req,
153                'conf' => var_export( $mathoidCli, true ),
154                'res' => var_export( $result, true ),
155            ] );
156            throw new RuntimeException( "Failed to execute Mathoid cli '$mathoidCli[0]', reason: $errorMsg" );
157        }
158        $res = json_decode( $result->getStdout() );
159        if ( !$res ) {
160            throw new RuntimeException( "Mathoid cli response '$res' is no valid JSON file." );
161        }
162
163        return $res;
164    }
165
166    /** @inheritDoc */
167    public function render() {
168        return !$this->getLastError();
169    }
170
171    protected function doCheck(): bool {
172        // avoid that restbase is called if check is set to always
173        return $this->texSecure;
174    }
175
176    /**
177     * @param stdClass $response object from cli
178     * @return string containing the location information
179     */
180    private function appendLocationInfo( $response ) {
181        return "in {$response->detail->line}:{$response->detail->column}";
182    }
183}