Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.36% covered (success)
96.36%
53 / 55
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
MathoidChecker
96.36% covered (success)
96.36%
53 / 55
85.71% covered (warning)
85.71%
6 / 7
14
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
2
 isValid
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 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 int|null */
16    private $statusCode;
17    /** @var string */
18    private $response;
19
20    public function __construct(
21        private readonly WANObjectCache $cache,
22        private readonly HttpRequestFactory $httpFactory,
23        private readonly LoggerInterface $logger,
24        private readonly string $url,
25        private readonly int $timeout,
26        string $input,
27        private readonly string $type,
28        bool $purge,
29    ) {
30        parent::__construct( $input, $purge );
31    }
32
33    public function getCheckResponse(): array {
34        if ( $this->statusCode === null ) {
35            $cacheInputKey = $this->getCacheKey();
36            if ( $this->purge ) {
37                $this->cache->delete( $cacheInputKey, WANObjectCache::TTL_INDEFINITE );
38            }
39            [ $this->statusCode, $this->response ] = $this->cache->getWithSetCallback(
40                $cacheInputKey,
41                WANObjectCache::TTL_INDEFINITE,
42                [ $this, 'runCheck' ],
43                [ 'version' => self::VERSION ]
44            );
45        }
46        return [ $this->statusCode, $this->response ];
47    }
48
49    public function getCacheKey(): string {
50        return $this->cache->makeGlobalKey(
51            self::class,
52            md5( $this->type . '-' . $this->inputTeX )
53        );
54    }
55
56    public function runCheck(): array {
57        $url = "{$this->url}/texvcinfo";
58        $q = rawurlencode( $this->inputTeX );
59        $postData = "type=$this->type&q=$q";
60        $options = [
61            'method' => 'POST',
62            'postData' => $postData,
63            'timeout' => $this->timeout,
64        ];
65        $req = $this->httpFactory->create( $url, $options, __METHOD__ );
66        $req->execute();
67        $statusCode = $req->getStatus();
68        if ( in_array( $statusCode, self::EXPECTED_RETURN_CODES, true ) ) {
69            return [ $statusCode, $req->getContent() ];
70        }
71        $e = new RuntimeException( 'Mathoid check returned unexpected error code.' );
72        $this->logger->error( 'Mathoid check endpoint "{url}" returned ' .
73            'HTTP status code "{statusCode}" for post data "{postData}": {exception}.',
74            [
75                'url' => $url,
76                'statusCode' => $statusCode,
77                'postData' => $postData,
78                'exception' => $e,
79            ]
80        );
81        throw $e;
82    }
83
84    public function isValid(): bool {
85        [ $statusCode ] = $this->getCheckResponse();
86        return $statusCode === 200;
87    }
88
89    public function getError(): ?Message {
90        [ $statusCode, $content ] = $this->getCheckResponse();
91        if ( $statusCode !== 200 ) {
92            // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
93            $json = @json_decode( $content );
94            if ( $json && isset( $json->detail ) ) {
95                return $this->errorObjectToMessage( $json->detail, $this->url );
96            }
97            return $this->errorObjectToMessage( (object)[ 'error' => (object)[
98                'message' => 'Math extension cannot connect to mathoid.' ] ], $this->url );
99        }
100        return null;
101    }
102
103    public function getValidTex(): ?string {
104        [ $statusCode, $content ] = $this->getCheckResponse();
105        if ( $statusCode === 200 ) {
106            $json = json_decode( $content );
107            return $json->checked;
108        }
109        return parent::getValidTex();
110    }
111
112}