diff --git a/ServiceWiring.php b/ServiceWiring.php index 16b90ac6c..ce432f3a1 100644 --- a/ServiceWiring.php +++ b/ServiceWiring.php @@ -38,7 +38,8 @@ return [ ), Math::getMathConfig( $services ), $services->getUserOptionsLookup(), - LoggerFactory::getInstance( 'Math' ) + LoggerFactory::getInstance( 'Math' ), + $services->getMainWANObjectCache() ); }, 'Math.WikibaseConnector' => static function ( MediaWikiServices $services ): MathWikibaseConnector { diff --git a/sql/mathlatexml.json b/sql/mathlatexml.json deleted file mode 100644 index 6bba47f15..000000000 --- a/sql/mathlatexml.json +++ /dev/null @@ -1,59 +0,0 @@ -[ - { - "name": "mathlatexml", - "columns": [ - { - "name": "math_inputhash", - "type": "binary", - "options": { - "notnull": true, - "length": 16, - "fixed": false - } - }, - { - "name": "math_inputtex", - "type": "text", - "options": { - "notnull": true, - "length": 65535 - } - }, - { - "name": "math_tex", - "type": "text", - "options": { - "notnull": false, - "length": 65535 - } - }, - { - "name": "math_mathml", - "type": "text", - "options": { - "notnull": false, - "length": 16777215 - } - }, - { - "name": "math_svg", - "type": "text", - "options": { - "notnull": false, - "length": 65535 - } - }, - { - "name": "math_style", - "type": "mwtinyint", - "options": { - "notnull": false - } - } - ], - "indexes": [], - "pk": [ - "math_inputhash" - ] - } -] diff --git a/sql/mathoid.json b/sql/mathoid.json deleted file mode 100644 index 01267a6c9..000000000 --- a/sql/mathoid.json +++ /dev/null @@ -1,74 +0,0 @@ -[ - { - "name": "mathoid", - "columns": [ - { - "name": "math_inputhash", - "type": "binary", - "options": { - "notnull": true, - "length": 16, - "fixed": false - } - }, - { - "name": "math_input", - "type": "text", - "options": { - "notnull": true, - "length": 65535 - } - }, - { - "name": "math_tex", - "type": "text", - "options": { - "notnull": false, - "length": 65535 - } - }, - { - "name": "math_mathml", - "type": "text", - "options": { - "notnull": false, - "length": 65535 - } - }, - { - "name": "math_svg", - "type": "text", - "options": { - "notnull": false, - "length": 65535 - } - }, - { - "name": "math_style", - "type": "mwtinyint", - "options": { - "notnull": false - } - }, - { - "name": "math_input_type", - "type": "mwtinyint", - "options": { - "notnull": false - } - }, - { - "name": "math_png", - "type": "blob", - "options": { - "notnull": false, - "length": 16777215 - } - } - ], - "indexes": [], - "pk": [ - "math_inputhash" - ] - } -] diff --git a/sql/mysql/mathlatexml.sql b/sql/mysql/mathlatexml.sql deleted file mode 100644 index 61bb35837..000000000 --- a/sql/mysql/mathlatexml.sql +++ /dev/null @@ -1,13 +0,0 @@ --- This file is automatically generated using maintenance/generateSchemaSql.php. --- Source: Math/sql/mathlatexml.json --- Do not modify this file directly. --- See https://www.mediawiki.org/wiki/Manual:Schema_changes -CREATE TABLE /*_*/mathlatexml ( - math_inputhash VARBINARY(16) NOT NULL, - math_inputtex TEXT NOT NULL, - math_tex TEXT DEFAULT NULL, - math_mathml MEDIUMTEXT DEFAULT NULL, - math_svg TEXT DEFAULT NULL, - math_style TINYINT DEFAULT NULL, - PRIMARY KEY(math_inputhash) -) /*$wgDBTableOptions*/; diff --git a/sql/mysql/mathoid.sql b/sql/mysql/mathoid.sql deleted file mode 100644 index d6f184136..000000000 --- a/sql/mysql/mathoid.sql +++ /dev/null @@ -1,15 +0,0 @@ --- This file is automatically generated using maintenance/generateSchemaSql.php. --- Source: Math/sql/mathoid.json --- Do not modify this file directly. --- See https://www.mediawiki.org/wiki/Manual:Schema_changes -CREATE TABLE /*_*/mathoid ( - math_inputhash VARBINARY(16) NOT NULL, - math_input TEXT NOT NULL, - math_tex TEXT DEFAULT NULL, - math_mathml TEXT DEFAULT NULL, - math_svg TEXT DEFAULT NULL, - math_style TINYINT DEFAULT NULL, - math_input_type TINYINT DEFAULT NULL, - math_png MEDIUMBLOB DEFAULT NULL, - PRIMARY KEY(math_inputhash) -) /*$wgDBTableOptions*/; diff --git a/sql/mysql/patch-mathoid.add_png.sql b/sql/mysql/patch-mathoid.add_png.sql deleted file mode 100644 index 86b74c2b9..000000000 --- a/sql/mysql/patch-mathoid.add_png.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE /*_*/mathoid ADD math_png mediumblob; \ No newline at end of file diff --git a/sql/postgres/mathlatexml.sql b/sql/postgres/mathlatexml.sql deleted file mode 100644 index cb53b40ec..000000000 --- a/sql/postgres/mathlatexml.sql +++ /dev/null @@ -1,13 +0,0 @@ --- This file is automatically generated using maintenance/generateSchemaSql.php. --- Source: Math/sql/mathlatexml.json --- Do not modify this file directly. --- See https://www.mediawiki.org/wiki/Manual:Schema_changes -CREATE TABLE mathlatexml ( - math_inputhash TEXT NOT NULL, - math_inputtex TEXT NOT NULL, - math_tex TEXT DEFAULT NULL, - math_mathml TEXT DEFAULT NULL, - math_svg TEXT DEFAULT NULL, - math_style SMALLINT DEFAULT NULL, - PRIMARY KEY(math_inputhash) -); diff --git a/sql/postgres/mathoid.sql b/sql/postgres/mathoid.sql deleted file mode 100644 index 5b3cf1cd5..000000000 --- a/sql/postgres/mathoid.sql +++ /dev/null @@ -1,15 +0,0 @@ --- This file is automatically generated using maintenance/generateSchemaSql.php. --- Source: Math/sql/mathoid.json --- Do not modify this file directly. --- See https://www.mediawiki.org/wiki/Manual:Schema_changes -CREATE TABLE mathoid ( - math_inputhash TEXT NOT NULL, - math_input TEXT NOT NULL, - math_tex TEXT DEFAULT NULL, - math_mathml TEXT DEFAULT NULL, - math_svg TEXT DEFAULT NULL, - math_style SMALLINT DEFAULT NULL, - math_input_type SMALLINT DEFAULT NULL, - math_png TEXT DEFAULT NULL, - PRIMARY KEY(math_inputhash) -); diff --git a/sql/sqlite/mathlatexml.sql b/sql/sqlite/mathlatexml.sql deleted file mode 100644 index 3462e96c2..000000000 --- a/sql/sqlite/mathlatexml.sql +++ /dev/null @@ -1,13 +0,0 @@ --- This file is automatically generated using maintenance/generateSchemaSql.php. --- Source: Math/sql/mathlatexml.json --- Do not modify this file directly. --- See https://www.mediawiki.org/wiki/Manual:Schema_changes -CREATE TABLE /*_*/mathlatexml ( - math_inputhash BLOB NOT NULL, - math_inputtex CLOB NOT NULL, - math_tex CLOB DEFAULT NULL, - math_mathml CLOB DEFAULT NULL, - math_svg CLOB DEFAULT NULL, - math_style SMALLINT DEFAULT NULL, - PRIMARY KEY(math_inputhash) -); diff --git a/sql/sqlite/mathoid.sql b/sql/sqlite/mathoid.sql deleted file mode 100644 index a0a463261..000000000 --- a/sql/sqlite/mathoid.sql +++ /dev/null @@ -1,15 +0,0 @@ --- This file is automatically generated using maintenance/generateSchemaSql.php. --- Source: Math/sql/mathoid.json --- Do not modify this file directly. --- See https://www.mediawiki.org/wiki/Manual:Schema_changes -CREATE TABLE /*_*/mathoid ( - math_inputhash BLOB NOT NULL, - math_input CLOB NOT NULL, - math_tex CLOB DEFAULT NULL, - math_mathml CLOB DEFAULT NULL, - math_svg CLOB DEFAULT NULL, - math_style SMALLINT DEFAULT NULL, - math_input_type SMALLINT DEFAULT NULL, - math_png BLOB DEFAULT NULL, - PRIMARY KEY(math_inputhash) -); diff --git a/src/HookHandlers/SchemaHooksHandler.php b/src/HookHandlers/SchemaHooksHandler.php index ad2684262..11b96d6f0 100644 --- a/src/HookHandlers/SchemaHooksHandler.php +++ b/src/HookHandlers/SchemaHooksHandler.php @@ -3,7 +3,6 @@ namespace MediaWiki\Extension\Math\HookHandlers; use DatabaseUpdater; -use LogicException; use MediaWiki\Installer\Hook\LoadExtensionSchemaUpdatesHook; /** @@ -19,22 +18,11 @@ class SchemaHooksHandler implements LoadExtensionSchemaUpdatesHook { public function onLoadExtensionSchemaUpdates( $updater ) { $type = $updater->getDB()->getType(); if ( !in_array( $type, [ 'mysql', 'sqlite', 'postgres' ], true ) ) { - throw new LogicException( "Math extension does not currently support $type database." ); + return; } foreach ( [ 'mathoid', 'mathlatexml' ] as $mode ) { - $updater->addExtensionTable( - $mode, - __DIR__ . "/../../sql/$type/$mode.sql" - ); - } - - if ( $type === 'mysql' ) { - $updater->addExtensionField( - 'mathoid', - 'math_png', - __DIR__ . '/../../sql/' . $type . '/patch-mathoid.add_png.sql' - ); + $updater->dropExtensionTable( $mode ); } } } diff --git a/src/MathLaTeXML.php b/src/MathLaTeXML.php index 34a38945b..d864bcc22 100644 --- a/src/MathLaTeXML.php +++ b/src/MathLaTeXML.php @@ -21,9 +21,9 @@ class MathLaTeXML extends MathMathML { /** @var string settings for LaTeXML daemon */ private $LaTeXMLSettings = ''; - public function __construct( $tex = '', $params = [] ) { + public function __construct( $tex = '', $params = [], $cache = null ) { global $wgMathLaTeXMLUrl; - parent::__construct( $tex, $params ); + parent::__construct( $tex, $params, $cache ); $this->host = $wgMathLaTeXMLUrl; $this->setMode( MathConfig::MODE_LATEXML ); } diff --git a/src/MathMathML.php b/src/MathMathML.php index 3a0f3ac46..c80cf57cb 100644 --- a/src/MathMathML.php +++ b/src/MathMathML.php @@ -51,9 +51,9 @@ class MathMathML extends MathRenderer { /** @var string|null */ private $mathoidStyle; - public function __construct( string $tex = '', array $params = [] ) { + public function __construct( string $tex = '', array $params = [], $cache = null ) { global $wgMathMathMLUrl; - parent::__construct( $tex, $params ); + parent::__construct( $tex, $params, $cache ); $this->setMode( MathConfig::MODE_MATHML ); $this->host = $wgMathMathMLUrl; if ( isset( $params['type'] ) ) { @@ -354,7 +354,7 @@ class MathMathML extends MathRenderer { return $this->svgPath; } return SpecialPage::getTitleFor( 'MathShowImage' )->getLocalURL( [ - 'hash' => $this->getMd5(), + 'hash' => $this->getInputHash(), 'mode' => $this->getMode(), 'noRender' => $noRender ] @@ -493,7 +493,7 @@ class MathMathML extends MathRenderer { protected function dbOutArray() { $out = parent::dbOutArray(); - if ( $this->getMathTableName() == 'mathoid' ) { + if ( $this->getMathTableName() === 'mathoid' ) { $out['math_input'] = $out['math_inputtex']; unset( $out['math_inputtex'] ); } @@ -502,20 +502,20 @@ class MathMathML extends MathRenderer { protected function dbInArray() { $out = parent::dbInArray(); - if ( $this->getMathTableName() == 'mathoid' ) { + if ( $this->getMathTableName() === 'mathoid' ) { $out = array_diff( $out, [ 'math_inputtex' ] ); $out[] = 'math_input'; } return $out; } - protected function initializeFromDatabaseRow( $rpage ) { + public function initializeFromCache( $rpage ) { // mathoid allows different input formats // therefore the column name math_inputtex was changed to math_input - if ( $this->getMathTableName() == 'mathoid' && !empty( $rpage->math_input ) ) { - $this->userInputTex = $rpage->math_input; + if ( $this->getMathTableName() === 'mathoid' && isset( $rpage['math_input'] ) ) { + $this->userInputTex = $rpage['math_input']; } - parent::initializeFromDatabaseRow( $rpage ); + parent::initializeFromCache( $rpage ); } /** diff --git a/src/MathMathMLCli.php b/src/MathMathMLCli.php index 2397f5e67..fefaee1b2 100644 --- a/src/MathMathMLCli.php +++ b/src/MathMathMLCli.php @@ -48,7 +48,7 @@ class MathMathMLCli extends MathMathML { */ private function initializeFromCliResponse( $res ) { global $wgMathoidCli; - if ( !property_exists( $res, $this->getMd5() ) ) { + if ( !property_exists( $res, $this->getInputHash() ) ) { $this->lastError = $this->getError( 'math_mathoid_error', 'cli', var_export( get_object_vars( $res ), true ) ); @@ -58,7 +58,7 @@ class MathMathMLCli extends MathMathML { $this->lastError = $this->getError( 'math_empty_tex' ); return false; } - $response = $res->{$this->getMd5()}; + $response = $res->{$this->getInputHash()}; if ( !$response->success ) { $this->lastError = $this->renderError( $response ); return false; @@ -103,7 +103,7 @@ class MathMathMLCli extends MathMathML { 'query' => [ 'q' => $this->getTex(), 'type' => $this->getInputType(), - 'hash' => $this->getMd5(), + 'hash' => $this->getInputHash(), ], ]; } diff --git a/src/MathNativeMML.php b/src/MathNativeMML.php index 14befaeb7..f755e4b72 100644 --- a/src/MathNativeMML.php +++ b/src/MathNativeMML.php @@ -21,8 +21,8 @@ use StatusValue; class MathNativeMML extends MathMathML { private LocalChecker $checker; - public function __construct( $tex = '', $params = [] ) { - parent::__construct( $tex, $params ); + public function __construct( $tex = '', $params = [], $cache = null ) { + parent::__construct( $tex, $params, $cache ); $this->setMode( MathConfig::MODE_NATIVE_MML ); } @@ -65,7 +65,7 @@ class MathNativeMML extends MathMathML { return $this->getMathml(); } - public function readFromDatabase() { + public function readFromCache(): bool { return false; } diff --git a/src/MathRenderer.php b/src/MathRenderer.php index 8fc94e88d..cabd33951 100644 --- a/src/MathRenderer.php +++ b/src/MathRenderer.php @@ -11,7 +11,6 @@ namespace MediaWiki\Extension\Math; -use MediaWiki\Deferred\DeferredUpdates; use MediaWiki\Extension\Math\InputCheck\BaseChecker; use MediaWiki\Logger\LoggerFactory; use MediaWiki\MediaWikiServices; @@ -20,14 +19,14 @@ use Message; use Parser; use Psr\Log\LoggerInterface; use RequestContext; -use stdClass; use StringUtils; +use WANObjectCache; /** * Abstract base class with static methods for rendering the tags using * different technologies. These static methods create a new instance of the * extending classes and render the math tags based on the mode setting of the user. - * Furthermore this class handles the caching of the rendered output. + * Furthermore, this class handles the caching of the rendered output. * * @author Tomasz Wegrzanowski * @author Brion Vibber @@ -58,13 +57,11 @@ abstract class MathRenderer { /** @var bool has the mathematical content changed */ protected $changed = false; /** @var bool is there a database entry for the mathematical content */ - protected $storedInDatabase = null; + protected $storedInCache = null; /** @var bool is there a request to purge the existing mathematical content */ protected $purge = false; /** @var string with last occurred error */ protected $lastError = ''; - /** @var string md5 value from userInputTex */ - protected $md5 = ''; /** @var string binary packed inputhash */ protected $inputHash = ''; /** @var string rendering mode */ @@ -78,13 +75,17 @@ abstract class MathRenderer { /** @var LoggerInterface */ private $logger; + private WANObjectCache $cache; + /** * Constructs a base MathRenderer * * @param string $tex (optional) LaTeX markup * @param array $params (optional) HTML attributes + * @param WANObjectCache|null $cache (optional) */ - public function __construct( $tex = '', $params = [] ) { + public function __construct( string $tex = '', $params = [], $cache = null ) { + $this->cache = $cache ?? MediaWikiServices::getInstance()->getMainWANObjectCache(); $this->params = $params; if ( isset( $params['id'] ) ) { $this->id = $params['id']; @@ -119,18 +120,6 @@ abstract class MathRenderer { $this->logger = LoggerFactory::getInstance( 'Math' ); } - /** - * @param string $md5 - * @return self the MathRenderer generated from md5 - */ - public static function newFromMd5( $md5 ) { - // @phan-suppress-next-line PhanTypeInstantiateAbstractStatic - $instance = new static(); - $instance->setMd5( $md5 ); - $instance->readFromDatabase(); - return $instance; - } - /** * Static factory method for getting a renderer based on mode * @@ -181,64 +170,31 @@ abstract class MathRenderer { * * @return string hash */ - public function getMd5() { - if ( !$this->md5 ) { - $this->md5 = md5( $this->userInputTex ); - } - return $this->md5; - } - - /** - * Set the input hash (if user input tex is not available) - * @param string $md5 - */ - public function setMd5( $md5 ) { - $this->md5 = $md5; - } - - /** - * Return hash of input - * - * @return string hash - */ - public function getInputHash() { - // TODO: What happens if $tex is empty? + public function getInputHash(): string { if ( !$this->inputHash ) { - $dbr = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->getReplicaDatabase(); - return $dbr->encodeBlob( pack( "H32", $this->getMd5() ) ); # Binary packed, not hex + $this->inputHash = hash( 'md5', // xxh128 might be better when dropping php 7 support + $this->mode . + $this->userInputTex . + implode( $this->params ) + ); } return $this->inputHash; } - /** - * Decode binary packed hash from the database to md5 of input_tex - * @param string $hash (binary) - * @return string md5 - */ - private static function dbHash2md5( $hash ) { - $dbr = wfGetDB( DB_REPLICA ); - $xhash = unpack( 'H32md5', $dbr->decodeBlob( $hash ) . " " ); - return $xhash['md5']; - } - /** * Reads rendering data from database * * @return bool true if read successfully, false otherwise */ - public function readFromDatabase() { - $dbr = MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->getReplicaDatabase(); - $rpage = $dbr->selectRow( $this->getMathTableName(), - $this->dbInArray(), - [ 'math_inputhash' => $this->getInputHash() ], - __METHOD__ ); + public function readFromCache(): bool { + $rpage = $this->cache->get( $this->getCacheKey() ); if ( $rpage !== false ) { - $this->initializeFromDatabaseRow( $rpage ); - $this->storedInDatabase = true; + $this->initializeFromCache( $rpage ); + $this->storedInCache = true; return true; } else { # Missing from the database and/or the render cache - $this->storedInDatabase = false; + $this->storedInCache = false; return false; } } @@ -258,82 +214,38 @@ abstract class MathRenderer { /** * Reads the values from the database but does not overwrite set values with empty values - * @param stdClass $rpage (a database row) + * @param array $rpage (a database row) */ - protected function initializeFromDatabaseRow( $rpage ) { - $this->inputHash = $rpage->math_inputhash; // MUST NOT BE NULL - $this->md5 = self::dbHash2md5( $this->inputHash ); - if ( !empty( $rpage->math_mathml ) ) { - $this->mathml = $rpage->math_mathml; + public function initializeFromCache( $rpage ) { + $this->inputHash = $rpage['math_inputhash']; // MUST NOT BE NULL + if ( isset( $rpage['math_mathml'] ) ) { + $this->mathml = $rpage['math_mathml']; } - if ( !empty( $rpage->math_inputtex ) ) { - // in the current database the field is probably not set. - $this->userInputTex = $rpage->math_inputtex; + if ( isset( $rpage['math_inputtex'] ) ) { + $this->userInputTex = $rpage['math_inputtex']; } - if ( !empty( $rpage->math_tex ) ) { - $this->tex = $rpage->math_tex; + if ( isset( $rpage['math_tex'] ) ) { + $this->tex = $rpage['math_tex']; } - if ( !empty( $rpage->math_svg ) ) { - $this->svg = $rpage->math_svg; + if ( isset( $rpage['math_svg'] ) ) { + $this->svg = $rpage['math_svg']; } $this->changed = false; } /** - * Writes rendering entry to database. + * Writes rendering entry to cache. * * WARNING: Use writeCache() instead of this method to be sure that all * renderer specific (such as squid caching) are taken into account. * This function stores the values that are currently present in the class - * to the database even if they are empty. + * to the cache even if they are empty. * * This function can be seen as protected function. - * @param \Wikimedia\Rdbms\IDatabase|null $dbw */ - public function writeToDatabase( $dbw = null ) { - # Now save it back to the DB: - if ( MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) { - return; - } + public function writeToCache() { $outArray = $this->dbOutArray(); - $mathTableName = $this->getMathTableName(); - $fname = __METHOD__; - if ( $this->isInDatabase() ) { - $this->debug( 'Update database entry' ); - $inputHash = $this->getInputHash(); - DeferredUpdates::addCallableUpdate( function () use ( - $dbw, $outArray, $inputHash, $mathTableName, $fname - ) { - $dbw = $dbw ?: wfGetDB( DB_PRIMARY ); - - $dbw->update( $mathTableName, $outArray, - [ 'math_inputhash' => $inputHash ], $fname ); - $this->logger->debug( - 'Row updated after db transaction was idle: ' . - var_export( $outArray, true ) . " to database" ); - } ); - } else { - $this->storedInDatabase = true; - $this->debug( 'Store new entry in database' ); - DeferredUpdates::addCallableUpdate( function () use ( - $dbw, $outArray, $mathTableName, $fname - ) { - $dbw = $dbw ?: wfGetDB( DB_PRIMARY ); - - $dbw->insert( $mathTableName, $outArray, $fname, [ 'IGNORE' ] ); - LoggerFactory::getInstance( 'Math' )->debug( - 'Row inserted after db transaction was idle {out}.', - [ - 'out' => var_export( $outArray, true ), - ] - ); - if ( $dbw->affectedRows() == 0 ) { - // That's the price for the delayed update. - $this->logger->warning( - 'Entry could not be written. Might be changed in between.' ); - } - } ); - } + $this->cache->set( $this->getCacheKey(), $outArray ); } /** @@ -346,7 +258,8 @@ abstract class MathRenderer { 'math_mathml' => $this->mathml, 'math_inputtex' => $this->userInputTex, 'math_tex' => $this->tex, - 'math_svg' => $this->svg + 'math_svg' => $this->svg, + 'math_mode' => $this->mode ]; return $out; } @@ -409,7 +322,7 @@ abstract class MathRenderer { $this->debug( 'Writing of cache requested' ); if ( $this->isChanged() ) { $this->debug( 'Change detected. Perform writing' ); - $this->writeToDatabase(); + $this->writeToCache(); return true; } else { $this->debug( "Nothing was changed. Don't write to database" ); @@ -591,7 +504,7 @@ abstract class MathRenderer { return true; } else { if ( $texCheckDisabled === MathConfig::NEW && $this->mode != MathConfig::MODE_SOURCE ) { - if ( $this->readFromDatabase() ) { + if ( $this->readFromCache() ) { $this->debug( 'Skip TeX check' ); $this->texSecure = true; return true; @@ -603,10 +516,10 @@ abstract class MathRenderer { } public function isInDatabase() { - if ( $this->storedInDatabase === null ) { - $this->readFromDatabase(); + if ( $this->storedInCache === null ) { + $this->readFromCache(); } - return $this->storedInDatabase; + return $this->storedInCache; } /** @@ -697,4 +610,11 @@ abstract class MathRenderer { protected function debug( $msg ) { $this->logger->debug( "$msg for \"{tex}\".", [ 'tex' => $this->userInputTex ] ); } + + private function getCacheKey() { + return $this->cache->makeGlobalKey( + self::class, + $this->getInputHash() + ); + } } diff --git a/src/Render/RendererFactory.php b/src/Render/RendererFactory.php index 9f6f6d3e7..a3d2b88ef 100644 --- a/src/Render/RendererFactory.php +++ b/src/Render/RendererFactory.php @@ -2,6 +2,7 @@ namespace MediaWiki\Extension\Math\Render; +use InvalidArgumentException; use MediaWiki\Config\ServiceOptions; use MediaWiki\Extension\Math\MathConfig; use MediaWiki\Extension\Math\MathLaTeXML; @@ -12,6 +13,7 @@ use MediaWiki\Extension\Math\MathRenderer; use MediaWiki\Extension\Math\MathSource; use MediaWiki\User\Options\UserOptionsLookup; use Psr\Log\LoggerInterface; +use WANObjectCache; class RendererFactory { @@ -34,23 +36,28 @@ class RendererFactory { /** @var LoggerInterface */ private $logger; + private WANObjectCache $cache; + /** * @param ServiceOptions $serviceOptions * @param MathConfig $mathConfig * @param UserOptionsLookup $userOptionsLookup * @param LoggerInterface $logger + * @param WANObjectCache $cache */ public function __construct( ServiceOptions $serviceOptions, MathConfig $mathConfig, UserOptionsLookup $userOptionsLookup, - LoggerInterface $logger + LoggerInterface $logger, + WANObjectCache $cache ) { $serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); $this->options = $serviceOptions; $this->mathConfig = $mathConfig; $this->userOptionsLookup = $userOptionsLookup; $this->logger = $logger; + $this->cache = $cache; } /** @@ -91,17 +98,17 @@ class RendererFactory { $renderer = new MathSource( $tex, $params ); break; case MathConfig::MODE_NATIVE_MML: - $renderer = new MathNativeMML( $tex, $params ); + $renderer = new MathNativeMML( $tex, $params, $this->cache ); break; case MathConfig::MODE_LATEXML: - $renderer = new MathLaTeXML( $tex, $params ); + $renderer = new MathLaTeXML( $tex, $params, $this->cache ); break; case MathConfig::MODE_MATHML: default: if ( $this->options->get( 'MathoidCli' ) ) { - $renderer = new MathMathMLCli( $tex, $params ); + $renderer = new MathMathMLCli( $tex, $params, $this->cache ); } else { - $renderer = new MathMathML( $tex, $params ); + $renderer = new MathMathML( $tex, $params, $this->cache ); } } $this->logger->debug( @@ -113,4 +120,15 @@ class RendererFactory { ); return $renderer; } + + public function getFromHash( $hash ) { + $rpage = $this->cache->get( $hash ); + if ( $rpage === false ) { + throw new InvalidArgumentException( 'Cache key is invalid' ); + } + $mode = $rpage['math_mode']; + $renderer = $this->getRenderer( '', [], $mode ); + $renderer->initializeFromCache( $rpage ); + return $renderer; + } } diff --git a/src/SpecialMathShowImage.php b/src/SpecialMathShowImage.php index 31fa52e90..659b1b8ed 100644 --- a/src/SpecialMathShowImage.php +++ b/src/SpecialMathShowImage.php @@ -81,10 +81,9 @@ class SpecialMathShowImage extends SpecialPage { echo $this->printSvgError( 'No Inputhash specified' ); } else { if ( $tex === '' && $asciimath === '' ) { - $this->renderer = $this->rendererFactory->getRenderer( '', [], $this->mode ); - $this->renderer->setMd5( $hash ); + $this->renderer = $this->rendererFactory->getFromHash( $hash ); $this->noRender = $request->getBool( 'noRender', false ); - $isInDatabase = $this->renderer->readFromDatabase(); + $isInDatabase = $this->renderer->readFromCache(); if ( $isInDatabase || $this->noRender ) { $success = $isInDatabase; } else { diff --git a/tests/phpunit/MathCacheTest.php b/tests/phpunit/MathCacheTest.php new file mode 100644 index 000000000..26d7929a9 --- /dev/null +++ b/tests/phpunit/MathCacheTest.php @@ -0,0 +1,79 @@ +<\ci>"; + + protected function setUp(): void { + parent::setUp(); + $this->renderer = new MathMathML( self::SOME_TEX ); + } + + /** + * Checks the tex and hash functions + * @covers \MediaWiki\Extension\Math\MathRenderer::getInputHash + */ + public function testInputHash() { + $this->assertEquals( 'beb7506b16f7c36aa0f9c8c9ef41b40b', $this->renderer->getInputHash() ); + } + + /** + * Helper function to set the current state of the sample renderer instance to the test values + */ + public function setValues() { + // set some values + $this->renderer->setTex( self::SOME_TEX ); + $this->renderer->setMathml( self::SOME_MATHML ); + } + + /** + * Checks database access. Writes an entry and reads it back. + * @covers \MediaWiki\Extension\Math\MathRenderer::writeToCache + * @covers \MediaWiki\Extension\Math\MathRenderer::readFromCache + */ + public function testDBBasics() { + $this->setValues(); + $this->renderer->writeToCache(); + $renderer2 = new MathMathML( self::SOME_TEX, [ 'display' => '' ] ); + $this->assertTrue( $renderer2->readFromCache(), 'Reading from database failed' ); + // comparing the class object does now work due to null values etc. + $this->assertEquals( + $this->renderer->getTex(), $renderer2->getTex(), "test if tex is the same" + ); + $this->assertEquals( + $this->renderer->getMathml(), $renderer2->getMathml(), "Check MathML encoding" + ); + $this->assertEquals( + $this->renderer->getHtmlOutput(), $renderer2->getHtmlOutput(), 'test if HTML is the same' + ); + } + + /** + * This test checks if no additional write operation + * is performed, if the entry already existed in the database. + */ + public function testNoWrite() { + $this->setValues(); + $inputHash = $this->renderer->getInputHash(); + $this->assertTrue( $this->renderer->isChanged() ); + $this->assertTrue( $this->renderer->writeCache(), "Write new entry" ); + $this->assertTrue( $this->renderer->readFromCache(), "Read entry from database" ); + $this->assertFalse( $this->renderer->isChanged() ); + } +} diff --git a/tests/phpunit/MathDatabaseTest.php b/tests/phpunit/MathDatabaseTest.php deleted file mode 100644 index d6f5a5c0d..000000000 --- a/tests/phpunit/MathDatabaseTest.php +++ /dev/null @@ -1,126 +0,0 @@ -<\ci>"; - - /** - * creates a new database connection and a new math renderer - * TODO: Check if there is a way to get database access without creating - * the connection to the database explicitly - * function addDBData() { - * $this->tablesUsed[] = 'math'; - * } - * was not sufficient. - * @throws Exception - */ - protected function setUp(): void { - parent::setUp(); - // TODO: figure out why this is necessary - $this->db = $this->getServiceContainer() - ->getDBLoadBalancer() - ->getConnection( DB_PRIMARY ); - $this->renderer = new MathMathML( self::SOME_TEX ); - $this->tablesUsed[] = 'mathoid'; - } - - /** - * Checks the tex and hash functions - * @covers \MediaWiki\Extension\Math\MathRenderer::getInputHash - */ - public function testInputHash() { - $expectedhash = $this->db->encodeBlob( pack( "H32", md5( self::SOME_TEX ) ) ); - $this->assertEquals( $expectedhash, $this->renderer->getInputHash() ); - } - - /** - * Helper function to set the current state of the sample renderer instance to the test values - */ - public function setValues() { - // set some values - $this->renderer->setTex( self::SOME_TEX ); - $this->renderer->setMathml( self::SOME_MATHML ); - } - - /** - * Checks database access. Writes an entry and reads it back. - * @covers \MediaWiki\Extension\Math\MathRenderer::writeToDatabase - * @covers \MediaWiki\Extension\Math\MathRenderer::readFromDatabase - */ - public function testDBBasics() { - $this->setValues(); - $this->renderer->writeToDatabase( $this->db ); - $renderer2 = new MathMathML( self::SOME_TEX ); - $this->assertTrue( $renderer2->readFromDatabase(), 'Reading from database failed' ); - // comparing the class object does now work due to null values etc. - $this->assertEquals( - $this->renderer->getTex(), $renderer2->getTex(), "test if tex is the same" - ); - $this->assertEquals( - $this->renderer->getMathml(), $renderer2->getMathml(), "Check MathML encoding" - ); - $this->assertEquals( - $this->renderer->getHtmlOutput(), $renderer2->getHtmlOutput(), 'test if HTML is the same' - ); - } - - /** - * Checks the creation of the math table. - * @covers \MediaWiki\Extension\Math\HookHandlers\SchemaHooksHandler::onLoadExtensionSchemaUpdates - */ - public function testCreateTable() { - $this->markTestSkippedIfDbType( 'postgres' ); - $this->markTestSkippedIfDbType( 'sqlite' ); - - $this->setMwGlobals( 'wgMathValidModes', [ MathConfig::MODE_MATHML ] ); - $this->db->dropTable( "mathoid", __METHOD__ ); - $dbu = DatabaseUpdater::newForDB( $this->db ); - $dbu->doUpdates( [ "extensions" ] ); - $this->expectOutputRegex( '/(.*)Creating mathoid table(.*)/' ); - $this->setValues(); - $this->renderer->writeToDatabase(); - $res = $this->db->select( "mathoid", "*" ); - $row = $res->fetchRow(); - $this->assertCount( 16, $row ); - } - - /** - * This test checks if no additional write operation - * is performed, if the entry already existed in the database. - */ - public function testNoWrite() { - $this->setValues(); - $inputHash = $this->renderer->getInputHash(); - $this->assertTrue( $this->renderer->isChanged() ); - $this->assertTrue( $this->renderer->writeCache(), "Write new entry" ); - $res = $this->db->selectField( "mathoid", "math_inputhash", - [ "math_inputhash" => $inputHash ] ); - $this->assertTrue( $res !== false, "Check database entry" ); - $this->assertTrue( $this->renderer->readFromDatabase(), "Read entry from database" ); - $this->assertFalse( $this->renderer->isChanged() ); - // modify the database entry manually - $this->db->delete( "mathoid", [ "math_inputhash" => $inputHash ] ); - // the renderer should not be aware of the modification and should not recreate the entry - $this->assertFalse( $this->renderer->writeCache() ); - // as a result no entry can be found in the database. - $this->assertFalse( $this->renderer->readFromDatabase() ); - } -} diff --git a/tests/phpunit/MathInputCheckTest.php b/tests/phpunit/MathInputCheckTest.php deleted file mode 100644 index 4b804e89c..000000000 --- a/tests/phpunit/MathInputCheckTest.php +++ /dev/null @@ -1,22 +0,0 @@ -getMockForAbstractClass( BaseChecker::class ); - /** @var BaseChecker $InputCheck */ - $this->assertFalse( $InputCheck->IsValid() ); - $this->assertNull( $InputCheck->getError() ); - $this->assertNull( $InputCheck->getValidTex() ); - } - -} diff --git a/tests/phpunit/MathLaTeXMLDatabaseTest.php b/tests/phpunit/MathLaTeXMLCacheTest.php similarity index 50% rename from tests/phpunit/MathLaTeXMLDatabaseTest.php rename to tests/phpunit/MathLaTeXMLCacheTest.php index f30f4cbef..e727ba25b 100644 --- a/tests/phpunit/MathLaTeXMLDatabaseTest.php +++ b/tests/phpunit/MathLaTeXMLCacheTest.php @@ -1,24 +1,18 @@ b"; private const SOME_MATHML = "iℏ∂_tΨ=H^Ψ<\ci>"; - private const SOME_LOG = "Sample Log Text."; - private const SOME_TIMESTAMP = 1272509157; - private const SOME_SVG = ">%%LIKE;'\" DROP TABLE math;"; /** * Helper function to test protected/private Methods @@ -32,21 +26,9 @@ class MathLaTeXMLDatabaseTest extends MediaWikiIntegrationTestCase { return $method; } - /** - * creates a new database connection and a new math renderer - * TODO: Check if there is a way to get database access without creating - * the connection to the database explicitly - * function addDBData() { - * $this->tablesUsed[] = 'math'; - * } - * was not sufficient. - */ protected function setUp(): void { parent::setUp(); - // TODO: figure out why this is necessary - $this->db = wfGetDB( DB_PRIMARY ); $this->renderer = new MathLaTeXML( self::SOME_TEX ); - self::setupTestDB( $this->db, "mathtest" ); } /** @@ -54,8 +36,8 @@ class MathLaTeXMLDatabaseTest extends MediaWikiIntegrationTestCase { * @covers \MediaWiki\Extension\Math\MathRenderer::getInputHash */ public function testInputHash() { - $expectedhash = $this->db->encodeBlob( pack( "H32", md5( self::SOME_TEX ) ) ); - $this->assertEquals( $expectedhash, $this->renderer->getInputHash() ); + $this->assertIsString( $this->renderer->getInputHash() ); + $this->assertStringMatchesFormat( '%x', $this->renderer->getInputHash() ); } /** @@ -77,38 +59,17 @@ class MathLaTeXMLDatabaseTest extends MediaWikiIntegrationTestCase { $this->assertEquals( "mathlatexml", $tableName, "Wrong latexml table name" ); } - /** - * Checks the creation of the math table. - * @covers \MediaWiki\Extension\Math\HookHandlers\SchemaHooksHandler::onLoadExtensionSchemaUpdates - */ - public function testCreateTable() { - $this->markTestSkippedIfDbType( 'postgres' ); - $this->markTestSkippedIfDbType( 'sqlite' ); - - $this->setMwGlobals( 'wgMathValidModes', [ MathConfig::MODE_LATEXML ] ); - $this->db->dropTable( "mathlatexml", __METHOD__ ); - $dbu = DatabaseUpdater::newForDB( $this->db ); - $dbu->doUpdates( [ "extensions" ] ); - $this->expectOutputRegex( '/(.*)Creating mathlatexml table(.*)/' ); - $this->setValues(); - $this->renderer->writeToDatabase(); - $res = $this->db->select( "mathlatexml", "*" ); - $row = $res->fetchRow(); - $this->assertCount( 12, $row ); - } - /** * Checks database access. Writes an entry and reads it back. - * @depends testCreateTable - * @covers \MediaWiki\Extension\Math\MathRenderer::writeToDatabase - * @covers \MediaWiki\Extension\Math\MathRenderer::readFromDatabase + * @covers \MediaWiki\Extension\Math\MathRenderer::writeToCache + * @covers \MediaWiki\Extension\Math\MathRenderer::readFromCache */ public function testDBBasics() { $this->setValues(); - $this->renderer->writeToDatabase(); + $this->renderer->writeToCache(); $renderer2 = $this->renderer = new MathLaTeXML( self::SOME_TEX ); - $renderer2->readFromDatabase(); + $renderer2->readFromCache(); // comparing the class object does now work due to null values etc. $this->assertEquals( $this->renderer->getTex(), $renderer2->getTex(), "test if tex is the same" diff --git a/tests/phpunit/MathRendererTest.php b/tests/phpunit/MathRendererTest.php index 112edd374..01fb8c877 100644 --- a/tests/phpunit/MathRendererTest.php +++ b/tests/phpunit/MathRendererTest.php @@ -39,12 +39,12 @@ class MathRendererTest extends MediaWikiIntegrationTestCase { public function testWriteCacheSkip() { $renderer = $this->getMockBuilder( MathRenderer::class )->onlyMethods( [ - 'writeToDatabase', + 'writeToCache', 'render', 'getMathTableName', 'getHtmlOutput' ] )->getMock(); - $renderer->expects( $this->never() )->method( 'writeToDatabase' ); + $renderer->expects( $this->never() )->method( 'writeToCache' ); /** @var MathRenderer $renderer */ $renderer->writeCache(); } @@ -56,12 +56,12 @@ class MathRendererTest extends MediaWikiIntegrationTestCase { public function testWriteCache() { $renderer = $this->getMockBuilder( MathRenderer::class )->onlyMethods( [ - 'writeToDatabase', + 'writeToCache', 'render', 'getMathTableName', 'getHtmlOutput' ] )->getMock(); - $renderer->expects( $this->never() )->method( 'writeToDatabase' ); + $renderer->expects( $this->never() )->method( 'writeToCache' ); /** @var MathRenderer $renderer */ $renderer->writeCache(); } @@ -87,10 +87,10 @@ class MathRendererTest extends MediaWikiIntegrationTestCase { 'render', 'getMathTableName', 'getHtmlOutput', - 'readFromDatabase', + 'readFromCache', 'setTex' ] )->setConstructorArgs( [ self::TEXVCCHECK_INPUT ] )->getMock(); - $renderer->expects( $this->never() )->method( 'readFromDatabase' ); + $renderer->expects( $this->never() )->method( 'readFromCache' ); $renderer->expects( $this->once() )->method( 'setTex' )->with( self::TEXVCCHECK_OUTPUT ); /** @var MathRenderer $renderer */ @@ -106,10 +106,10 @@ class MathRendererTest extends MediaWikiIntegrationTestCase { 'render', 'getMathTableName', 'getHtmlOutput', - 'readFromDatabase', + 'readFromCache', 'setTex' ] )->setConstructorArgs( [ self::TEXVCCHECK_INPUT ] )->getMock(); - $renderer->expects( $this->never() )->method( 'readFromDatabase' ); + $renderer->expects( $this->never() )->method( 'readFromCache' ); $renderer->expects( $this->never() )->method( 'setTex' ); /** @var MathRenderer $renderer */ @@ -125,10 +125,10 @@ class MathRendererTest extends MediaWikiIntegrationTestCase { 'render', 'getMathTableName', 'getHtmlOutput', - 'readFromDatabase', + 'readFromCache', 'setTex' ] )->setConstructorArgs( [ self::TEXVCCHECK_INPUT ] )->getMock(); - $renderer->expects( $this->once() )->method( 'readFromDatabase' ) + $renderer->expects( $this->once() )->method( 'readFromCache' ) ->willReturn( false ); $renderer->expects( $this->once() )->method( 'setTex' )->with( self::TEXVCCHECK_OUTPUT ); @@ -147,10 +147,10 @@ class MathRendererTest extends MediaWikiIntegrationTestCase { 'render', 'getMathTableName', 'getHtmlOutput', - 'readFromDatabase', + 'readFromCache', 'setTex' ] )->setConstructorArgs( [ self::TEXVCCHECK_INPUT ] )->getMock(); - $renderer->expects( $this->once() )->method( 'readFromDatabase' ) + $renderer->expects( $this->once() )->method( 'readFromCache' ) ->willReturn( true ); $renderer->expects( $this->never() )->method( 'setTex' );