mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Math
synced 2024-11-24 15:44:33 +00:00
a3072e273b
Most info-level log entries are actually debug messages, which should not end up in the production log by default, so adjust them. Only the Mathoid-powered TeX check fail has been promoted to info so that we can quickly identify such requests from pages. Bug: T121445 Change-Id: I3736c59f6425d675befea9438182ee1cdebe5fc5
304 lines
7.6 KiB
PHP
304 lines
7.6 KiB
PHP
<?php
|
|
/**
|
|
* MediaWiki math extension
|
|
*
|
|
* (c) 2002-2015 various MediaWiki contributors
|
|
* GPLv2 license; info in main package.
|
|
*/
|
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
|
|
|
class MathRestbaseInterface {
|
|
private $hash = false;
|
|
private $tex;
|
|
private $type;
|
|
private $checkedTex;
|
|
private $success;
|
|
private $identifiers;
|
|
private $error;
|
|
|
|
/**
|
|
* MathRestbaseInterface constructor.
|
|
* @param string $tex
|
|
* @param bool $displayStyle
|
|
*/
|
|
public function __construct( $tex = '', $displayStyle = true ) {
|
|
$this->tex = $tex;
|
|
if ( $displayStyle ) {
|
|
$this->type = 'tex';
|
|
} else {
|
|
$this->type = 'inline-tex';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return string MathML code
|
|
* @throws MWException
|
|
*/
|
|
public function getMathML() {
|
|
return $this->getContent( 'mml' );
|
|
}
|
|
|
|
private function getContent( $type ) {
|
|
$this->calculateHash();
|
|
$request = array(
|
|
'method' => 'GET',
|
|
'url' => $this->getUrl( "media/math/render/$type/{$this->hash}" )
|
|
);
|
|
$serviceClient = $this->getServiceClient();
|
|
$response = $serviceClient->run( $request );
|
|
if ( $response['code'] === 200 ) {
|
|
return $response['body'];
|
|
}
|
|
$this->log()->error( 'Restbase math server problem:', array(
|
|
'request' => $request,
|
|
'response' => $response,
|
|
'type' => $type,
|
|
'tex' => $this->tex
|
|
) );
|
|
throw new MWException( "Cannot get $type. Server problem." );
|
|
}
|
|
|
|
private function calculateHash() {
|
|
if ( !$this->hash ) {
|
|
if ( !$this->checkTeX() ) {
|
|
throw new MWException( "TeX input is invalid." );
|
|
}
|
|
}
|
|
}
|
|
|
|
public function checkTeX() {
|
|
$postData = array(
|
|
'type' => $this->type,
|
|
'q' => $this->tex
|
|
);
|
|
$requestResult = $this->makeRestbaseCheckRequest( $postData, $res );
|
|
$json = json_decode( $res );
|
|
if ( $requestResult ) {
|
|
$this->success = $json->success;
|
|
$this->checkedTex = $json->checked;
|
|
$this->identifiers = $json->identifiers;
|
|
return true;
|
|
} else {
|
|
if ( isset( $json->detail ) && isset( $json->detail->success ) ) {
|
|
$this->success = $json->detail->success;
|
|
$this->error = $json->detail;
|
|
} else {
|
|
$this->success = false;
|
|
$this->setErrorMessage( 'Math extension cannot connect to Restbase.' );
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs a service request
|
|
* Generates error messages on failure
|
|
* @see Http::post()
|
|
*
|
|
* @param string $post the encoded post request
|
|
* @param mixed $res the result
|
|
* @return bool success
|
|
*/
|
|
private function makeRestbaseCheckRequest( $post, &$res ) {
|
|
$res = null;
|
|
$request = array(
|
|
'method' => 'POST',
|
|
'body' => $post
|
|
);
|
|
$serviceClient = $this->getServiceClient();
|
|
$request['url'] = $this->getUrl( "media/math/check/{$this->type}" );
|
|
$response = $serviceClient->run( $request );
|
|
if ( $response['code'] === 200 ) {
|
|
$res = $response['body'];
|
|
$headers = $response['headers'];
|
|
$this->hash = $headers['x-resource-location'];
|
|
return true;
|
|
} else {
|
|
$res = $response['body'];
|
|
$this->log()->info( 'Tex check failed:', array(
|
|
'post' => $post,
|
|
'error' => $response['error'],
|
|
'url' => $request['url']
|
|
) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function getServiceClient() {
|
|
global $wgVirtualRestConfig;
|
|
$serviceClient = new VirtualRESTServiceClient( new MultiHttpClient( array() ) );
|
|
if ( isset( $wgVirtualRestConfig['modules']['restbase'] ) ) {
|
|
$cfg = $wgVirtualRestConfig['modules']['restbase'];
|
|
$cfg['parsoidCompat'] = false;
|
|
$vrsObject = new RestbaseVirtualRESTService( $cfg );
|
|
$serviceClient->mount( '/mathoid/', $vrsObject );
|
|
}
|
|
return $serviceClient;
|
|
}
|
|
|
|
|
|
/**
|
|
* The URL is generated accoding to the following logic:
|
|
*
|
|
* Case A: <code>$internal = false</code>, which means one needs an URL that is accessible from
|
|
* outside:
|
|
*
|
|
* --> If <code>$wgMathFullRestbaseURL</code> is configured use it, otherwise fall back try to
|
|
* <code>$wgVisualEditorFullRestbaseURL</code>. (Note, that this is not be worse than failing
|
|
* immediately.)
|
|
*
|
|
* Case B: <code> $internal= true</code>, which means one needs to access content from Restbase
|
|
* which does not need to be accessible from outside:
|
|
*
|
|
* --> Use the mount point whenever possible. If the mount point is not available, use
|
|
* <code>$wgMathFullRestbaseURL</code> with fallback to <code>wgVisualEditorFullRestbaseURL</code>
|
|
*
|
|
* @param string $path
|
|
* @param bool|true $internal
|
|
* @return string
|
|
* @throws MWException
|
|
*/
|
|
public function getUrl( $path, $internal = true ) {
|
|
global $wgVirtualRestConfig, $wgMathFullRestbaseURL, $wgVisualEditorFullRestbaseURL;
|
|
if ( $internal && isset( $wgVirtualRestConfig['modules']['restbase'] ) ) {
|
|
return "/mathoid/local/v1/$path";
|
|
}
|
|
if ( $wgMathFullRestbaseURL ) {
|
|
return "{$wgMathFullRestbaseURL}v1/$path";
|
|
}
|
|
if ( $wgVisualEditorFullRestbaseURL ) {
|
|
return "{$wgVisualEditorFullRestbaseURL}v1/$path";
|
|
}
|
|
$msg = 'Math extension can not find Restbase URL. Please specify $wgMathFullRestbaseURL.';
|
|
$this->setErrorMessage( $msg );
|
|
throw new MWException( $msg );
|
|
}
|
|
|
|
/**
|
|
* @return \Psr\Log\LoggerInterface
|
|
*/
|
|
private function log() {
|
|
return LoggerFactory::getInstance( 'Math' );
|
|
}
|
|
|
|
public function getSvg() {
|
|
return $this->getContent( 'svg' );
|
|
}
|
|
|
|
/**
|
|
* @param bool|false $skipConfigCheck
|
|
* @return bool
|
|
*/
|
|
public function checkBackend( $skipConfigCheck = false ) {
|
|
try {
|
|
$request = array(
|
|
'method' => 'GET',
|
|
'url' => $this->getUrl( '?spec' )
|
|
);
|
|
} catch ( Exception $e ) {
|
|
return false;
|
|
}
|
|
$serviceClient = $this->getServiceClient();
|
|
$response = $serviceClient->run( $request );
|
|
if ( $response['code'] === 200 ) {
|
|
return $skipConfigCheck || $this->checkConfig();
|
|
}
|
|
$this->log()->error( "Restbase backend is not correctly set up.", array(
|
|
'request' => $request,
|
|
'response' => $response
|
|
) );
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Generates a unique TeX string, renders it and gets it via a public URL.
|
|
* The method fails, if the public URL does not point to the same server, who did render
|
|
* the unique TeX input in the first place.
|
|
* @return bool
|
|
*/
|
|
private function checkConfig() {
|
|
// Generates a TeX string that probably has not been generated before
|
|
$uniqueTeX = uniqid( 't=', true );
|
|
$testInterface = new MathRestbaseInterface( $uniqueTeX );
|
|
if ( ! $testInterface->checkTeX() ){
|
|
$this->log()->warning( 'Config check failed, since test expression was considered as invalid.',
|
|
array( 'uniqueTeX' => $uniqueTeX ) );
|
|
return false;
|
|
}
|
|
try {
|
|
$url = $testInterface->getFullSvgUrl();
|
|
$req = MWHttpRequest::factory( $url );
|
|
$status = $req->execute();
|
|
if ( $status->isOK() ){
|
|
return true;
|
|
}
|
|
$this->log()->warning( 'Config check failed, due to an invalid response code.',
|
|
array( 'responseCode' => $status ) );
|
|
} catch ( Exception $e ) {
|
|
$this->log()->warning( 'Config check failed, due to an exception.', array( $e ) );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a publicly accessible link to the generated SVG image.
|
|
* @return string
|
|
* @throws MWException
|
|
*/
|
|
public function getFullSvgUrl() {
|
|
$this->calculateHash();
|
|
return $this->getUrl( "media/math/render/svg/{$this->hash}", false );
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getCheckedTex() {
|
|
return $this->checkedTex;
|
|
}
|
|
|
|
/**
|
|
* @return boolean
|
|
*/
|
|
public function getSuccess() {
|
|
return $this->success;
|
|
}
|
|
|
|
/**
|
|
* @return array
|
|
*/
|
|
public function getIdentifiers() {
|
|
return $this->identifiers;
|
|
}
|
|
|
|
/**
|
|
* @return stdClass
|
|
*/
|
|
public function getError() {
|
|
return $this->error;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getTex() {
|
|
return $this->tex;
|
|
}
|
|
|
|
/**
|
|
* @return string
|
|
*/
|
|
public function getType() {
|
|
return $this->type;
|
|
}
|
|
|
|
private function setErrorMessage( $msg ) {
|
|
$this->error = (object)array(
|
|
'error' =>
|
|
(object)array( 'message' => $msg )
|
|
);
|
|
}
|
|
|
|
}
|