Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
47.62% covered (danger)
47.62%
30 / 63
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathoidChecker
47.62% covered (danger)
47.62%
30 / 63
57.14% covered (warning)
57.14%
4 / 7
47.34
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getCheckResponse
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 getCacheKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 runCheck
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
6
 isValid
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getError
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
4.25
 getValidTex
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Extension\Math\InputCheck;
4
5use MediaWiki\Http\HttpRequestFactory;
6use MediaWiki\Message\Message;
7use Psr\Log\LoggerInterface;
8use RuntimeException;
9use Wikimedia\ObjectCache\WANObjectCache;
10
11class MathoidChecker extends BaseChecker {
12
13    private const EXPECTED_RETURN_CODES = [ 200, 400 ];
14    public const VERSION = 1;
15    /** @var string */
16    private $url;
17    /** @var int */
18    private $timeout;
19    /** @var WANObjectCache */
20    private $cache;
21    /** @var HttpRequestFactory */
22    private $httpFactory;
23    /** @var string */
24    private $type;
25    /** @var LoggerInterface */
26    private $logger;
27    /** @var int|null */
28    private $statusCode;
29    /** @var string */
30    private $response;
31
32    public function __construct(
33        WANObjectCache $cache,
34        HttpRequestFactory $httpFactory,
35        LoggerInterface $logger,
36        string $url,
37        int $timeout,
38        string $input,
39        string $type,
40        bool $purge
41    ) {
42        parent::__construct( $input, $purge );
43        $this->url = $url;
44        $this->timeout = $timeout;
45        $this->cache = $cache;
46        $this->httpFactory = $httpFactory;
47        $this->type = $type;
48        $this->logger = $logger;
49    }
50
51    /**
52     * @return array
53     */
54    public function getCheckResponse(): array {
55        if ( $this->statusCode === null ) {
56            $cacheInputKey = $this->getCacheKey();
57            if ( $this->purge ) {
58                $this->cache->delete( $cacheInputKey, WANObjectCache::TTL_INDEFINITE );
59            }
60            [ $this->statusCode, $this->response ] = $this->cache->getWithSetCallback(
61                $cacheInputKey,
62                WANObjectCache::TTL_INDEFINITE,
63                [ $this, 'runCheck' ],
64                [ 'version' => self::VERSION ]
65            );
66        }
67        return [ $this->statusCode, $this->response ];
68    }
69
70    /**
71     * @return string
72     */
73    public function getCacheKey(): string {
74        return $this->cache->makeGlobalKey(
75            self::class,
76            md5( $this->type . '-' . $this->inputTeX )
77        );
78    }
79
80    /**
81     * @return array
82     */
83    public function runCheck(): array {
84        $url = "{$this->url}/texvcinfo";
85        $q = rawurlencode( $this->inputTeX );
86        $postData = "type=$this->type&q=$q";
87        $options = [
88            'method' => 'POST',
89            'postData' => $postData,
90            'timeout' => $this->timeout,
91        ];
92        $req = $this->httpFactory->create( $url, $options, __METHOD__ );
93        $req->execute();
94        $statusCode = $req->getStatus();
95        if ( in_array( $statusCode, self::EXPECTED_RETURN_CODES, true ) ) {
96            return [ $statusCode, $req->getContent() ];
97        }
98        $e = new RuntimeException( 'Mathoid check returned unexpected error code.' );
99        $this->logger->error( 'Mathoid check endpoint "{url}" returned ' .
100            'HTTP status code "{statusCode}" for post data "{postData}": {exception}.',
101            [
102                'url' => $url,
103                'statusCode' => $statusCode,
104                'postData' => $postData,
105                'exception' => $e,
106            ]
107        );
108        throw $e;
109    }
110
111    public function isValid() {
112        [ $statusCode ] = $this->getCheckResponse();
113        if ( $statusCode === 200 ) {
114            return true;
115        }
116        return false;
117    }
118
119    public function getError(): ?Message {
120        [ $statusCode, $content ] = $this->getCheckResponse();
121        if ( $statusCode !== 200 ) {
122            // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
123            $json = @json_decode( $content );
124            if ( $json && isset( $json->detail ) ) {
125                return $this->errorObjectToMessage( $json->detail, $this->url );
126            }
127            return $this->errorObjectToMessage( (object)[ 'error' => (object)[
128                'message' => 'Math extension cannot connect to mathoid.' ] ], $this->url );
129        }
130        return null;
131    }
132
133    public function getValidTex(): ?string {
134        [ $statusCode, $content ] = $this->getCheckResponse();
135        if ( $statusCode === 200 ) {
136            $json = json_decode( $content );
137            return $json->checked;
138        }
139        return parent::getValidTex();
140    }
141
142}