Add special page and API endpoint that show information from math Wikibase items

Add a special page and an API endpoint to fetch data from Wikibase
items with a given qID. The special page summarizes information from
Wikibase. The API endpoint allows to request the information
directly. Both, the API endpoint and the special page, fetch the data
from a new helper class for consistency.

Bug: T208758
Bug: T229939
Change-Id: Idd22057a88312bf1a1cb5546d0a6edca5678d80d
This commit is contained in:
Moritz Schubotz (physikerwelt) 2019-07-19 22:46:20 +02:00 committed by AndreG-P
parent 0de63bdf6c
commit 4a7bc3ee31
12 changed files with 1081 additions and 11 deletions

View file

@ -8,8 +8,7 @@ $cfg['directory_list'] = array_merge(
$cfg['directory_list'],
[
'../../extensions/VisualEditor',
'../../extensions/Wikibase/repo',
'../../extensions/Wikibase/lib',
'../../extensions/Wikibase'
]
);
@ -17,8 +16,7 @@ $cfg['exclude_analysis_directory_list'] = array_merge(
$cfg['exclude_analysis_directory_list'],
[
'../../extensions/VisualEditor',
'../../extensions/Wikibase/repo',
'../../extensions/Wikibase/lib',
'../../extensions/Wikibase'
]
);

View file

@ -14,5 +14,6 @@ $specialPageAliases = [];
/** English (English) */
$specialPageAliases['en'] = [
'MathShowImage' => [ 'MathShowImage' ]
'MathShowImage' => [ 'MathShowImage' ],
'MathWikibase' => [ 'MathWikibase' ]
];

View file

@ -5,7 +5,8 @@
"Tomasz Wegrzanowski",
"Brion Vibber",
"Moritz Schubotz",
"Derk-Jan Hartman"
"Derk-Jan Hartman",
"André Greiner-Petter"
],
"url": "https://www.mediawiki.org/wiki/Extension:Math",
"descriptionmsg": "math-desc",
@ -29,16 +30,26 @@
"MathInputCheckRestbase": "src/MathInputCheckRestbase.php",
"SpecialMathShowImage": "src/SpecialMathShowImage.php",
"SpecialMathStatus": "src/SpecialMathStatus.php",
"SpecialMathWikibase": "src/SpecialMathWikibase.php",
"MathValidator": "src/MathValidator.php",
"MathFormatter": "src/MathFormatter.php",
"MathWikibaseHook": "src/MathWikibaseHook.php",
"MathMLRdfBuilder": "src/MathMLRdfBuilder.php",
"MathPng": "src/MathPng.php",
"MathDataUpdater": "src/MathDataUpdater.php"
"MathDataUpdater": "src/MathDataUpdater.php",
"MathWikibaseConfig": "src/MathWikibaseConfig.php",
"MathWikibaseConnector": "src/MathWikibaseConnector.php",
"MathWikibaseInfo": "src/MathWikibaseInfo.php",
"ApiMathWikibaseExtracts": "src/ApiMathWikibaseExtracts.php"
},
"DefaultUserOptions": {
"math": "mathml"
},
"APIPropModules": {
"mathwbextracts": {
"class": "ApiMathWikibaseExtracts"
}
},
"ExtensionMessagesFiles": {
"MathAlias": "Math.alias.php",
"MathAliasNoTranslate": "Math.alias.noTranslate.php"
@ -108,7 +119,11 @@
"source",
"mathml"
],
"MathEnableWikibaseDataType": true
"MathEnableWikibaseDataType": true,
"MathEnableFormulaLinks": false,
"MathWikibasePropertyIdHasPart": "P527",
"MathWikibasePropertyIdDefiningFormula": "P2534",
"MathWikibasePropertyIdQuantitySymbol": "P416"
},
"VisualEditorPluginModules": [
"ext.math.visualEditor"
@ -129,6 +144,12 @@
"ext.math.scripts": {
"scripts": "ext.math.js"
},
"ext.math.wikibase.scripts": {
"scripts": "ext.math.wikibase.js",
"dependencies": [
"jquery.wikibase.entityselector"
]
},
"ext.math.visualEditor": {
"scripts": [
"ve-math/ve.dm.MWLatexNode.js",
@ -246,7 +267,8 @@
},
"SpecialPages": {
"MathShowImage": "SpecialMathShowImage",
"MathStatus": "SpecialMathStatus"
"MathStatus": "SpecialMathStatus",
"MathWikibase": "SpecialMathWikibase"
},
"TrackingCategories": [
"math-tracking-category-error",

View file

@ -95,7 +95,28 @@
"mw_math_source": "LaTeX source (for text browsers)",
"prefs-math": "Math",
"mathstatus": "Math status",
"mathwikibase": "Math Formula Information",
"datatypes-type-math": "Mathematical expression",
"wikibase-listdatatypes-math-head": "Mathematical expression",
"wikibase-listdatatypes-math-body": "Literal data field for mathematical expressions, formula, equations and such, expressed in a variant of LaTeX."
"wikibase-listdatatypes-math-body": "Literal data field for mathematical expressions, formula, equations and such, expressed in a variant of LaTeX.",
"math-wikibase-header": "Information of Mathematical Items",
"math-wikibase-formula": "Formula",
"math-wikibase-formula-name": "Name",
"math-wikibase-formula-type": "Type",
"math-wikibase-formula-header-format": "'''$1:''' $2",
"math-wikibase-formula-description": "Description",
"math-wikibase-formula-information": "Math Formula Information",
"math-wikibase-formula-link-header": "Data Source",
"math-wikibase-formula-elements-header": "Elements of the Forumla",
"math-wikibase-special-form-header": "Specify the name of the formula:",
"math-wikibase-special-form-placeholder": "Title of the formula",
"math-wikibase-special-form-button": "Request Information",
"math-wikibase-special-error-header": "Error",
"math-wikibase-special-error-invalid-argument": "Your specified Wikibase item ID does not exist.",
"math-wikibase-special-error-unknown": "An unknown error occured due fetching data from Wikibase.",
"math-wikibase-special-error-no-wikibase": "The Wikibase extension is required in order to use this special page.",
"apihelp-query+mathwbextracts-example-1": "Requests a summary of mathematical Wikibase items.",
"apihelp-query+mathwbextracts-summary": "Returns a summary of a mathematical Wikibase item in HTML.",
"apihelp-query+mathwbextracts-param-qid": "The QID of the Wikibase item that will be requested.",
"apihelp-query+mathwbextracts-param-uselang": "The language that should be requested for the Wikibase item. English is the default value."
}

View file

@ -95,7 +95,28 @@
"mw_math_source": "In user preferences (math). All mw_math_* messages MUST be different, things will break otherwise!\n\nUsed as label for source radio button.\n\nSee also:\n* {{msg-mw|Mw math png}}\n* {{msg-mw|Mw math latexml}}",
"prefs-math": "Used in user preferences as a section heading.\n{{Identical|Math}}",
"mathstatus": "Title of a special page that displays information about the enabled math rendering modes.",
"mathwikibase": "Title of a special page that displays information about a mathematical Wikibase item.",
"datatypes-type-math": "The name of a data type.\n{{related|Datatypes-type}}\n{{Identical|Mathematical expression}}",
"wikibase-listdatatypes-math-head": "{{Wikibase-datatype-head|Mathematical expression|math}}\n{{Identical|Mathematical expression}}",
"wikibase-listdatatypes-math-body": "{{Wikibase-datatype-body|Mathematical expression}}"
"wikibase-listdatatypes-math-body": "{{Wikibase-datatype-body|Mathematical expression}}",
"apihelp-query+mathwbextracts-example-1": "{{doc-apihelp-example|query+mathwbextracts}}",
"apihelp-query+mathwbextracts-summary": "{{doc-apihelp-summary|query+mathwbextracts}}",
"apihelp-query+mathwbextracts-param-qid": "{{doc-apihelp-param|query+mathwbextracts|qid}}",
"apihelp-query+mathwbextracts-param-uselang": "{{doc-apihelp-param|query+mathwbextracts|uselang}}",
"math-wikibase-formula": "Label for the inline header for formulae",
"math-wikibase-formula-name": "Label for the name of the formula",
"math-wikibase-formula-type": "Label for the type of the formula",
"math-wikibase-formula-description": "Label for the description of the formula",
"math-wikibase-formula-information": "Header for information section of the formula",
"math-wikibase-formula-link-header": "Header for the hyperlink section to wikibase of the formula",
"math-wikibase-formula-elements-header": "Header for the elements section of the forumla",
"math-wikibase-formula-header-format": "The pattern to organize label $1 with information $2",
"math-wikibase-header": "wikibase Information of Mathematical Items",
"math-wikibase-special-form-header": "The header of the form to request a wikibase id from user.",
"math-wikibase-special-form-placeholder": "The placeholder of input field where a user can request a wikibase id.",
"math-wikibase-special-form-button": "The button label to submits the form to request a wikibase id.",
"math-wikibase-special-error-header": "The header of the error page. Will be shown when an error occured on the special page.",
"math-wikibase-special-error-invalid-argument": "The error text if the requested wikibase does not exist.",
"math-wikibase-special-error-unknown": "The error text when an unkown error occurred during fetching the information.\nFurther information about the error can be found in the logs.",
"math-wikibase-special-error-no-wikibase": "The error text that show up when the required Wikibase extension is missing."
}

View file

@ -0,0 +1,17 @@
( function () {
'use strict';
var serverUrl = mw.config.get( 'wgServer' ),
scriptPath = mw.config.get( 'wgScriptPath' ),
repoApiUrl = serverUrl + scriptPath + '/api.php',
contLang = mw.config.get( 'wgContentLanguage' );
// eslint-disable-next-line no-jquery/no-global-selector
$( '.mwe-math-wikibase-entityselector-input input' ).entityselector( {
url: repoApiUrl,
language: contLang,
type: 'item'
} ).on( 'entityselectorselected', function ( event, entityId ) {
this.value = entityId;
} );
}() );

View file

@ -0,0 +1,154 @@
<?php
/**
* API extension to fetch data from mathematical Wikibase items
*/
class ApiMathWikibaseExtracts extends ApiQueryBase {
/**
* @var string the prefix of the parameter names of this API
*/
const PREFIX = "math";
/**
* @var MathWikibaseConnector
*/
private $wikibase;
/**
* @param ApiQuery $query
* @param string $moduleName
*/
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, self::PREFIX );
$this->wikibase = new MathWikibaseConnector(
MathWikibaseConfig::getDefaultMathWikibaseConfig()
);
}
/**
* @throws ApiUsageException
*/
public function execute() {
$result = $this->getResult();
$params = $this->extractRequestParams();
$qid = $params['qid'];
$lang = $params['uselang'] ?: 'en';
try {
$info = $this->wikibase->fetchWikibaseFromId( $qid, $lang );
$result->addValue(
[ 'query', 'mathwbextracts' ],
$qid,
$this->buildResponse( $qid, $lang, $info )
);
} catch ( MWException $e ) {
// impossible to fetch the data. Keep the answer empty
$result->addValue(
[ 'query', 'mathwbextracts' ],
$qid,
[]
);
}
}
/**
* @param string $qid
* @param string $lang language code
* @param MathWikibaseInfo $info information from MathWikibaseConnector
* @return array response
*/
private function buildResponse( $qid, $lang, $info ) {
$specialPageTitle = Title::newFromText( "Special:MathWikibase" );
$url = $specialPageTitle->getLocalURL( [
'qid' => $qid
] );
try {
$langObj = Language::factory( $lang );
} catch ( MWException $e ) {
throw new InvalidArgumentException( "Invalid language code." );
}
return [
"title" => $info->getLabel(),
"extract" => self::buildHTMLRepresentation( $info ),
"canonicalurl" => $url,
"fullurl" => $url,
"contentmodel" => "html",
"pagelanguage" => $langObj->getCode(),
"pagelanguagedir" => $langObj->getDir(),
"pagelanguagehtmlcode" => $langObj->getHtmlCode()
];
}
/**
* Generates an HTML string from the given data.
* @param MathWikibaseInfo $info an info object generated by fetchWikibaseFromId
* @return string an HTML representation of the given info object
*/
private function buildHTMLRepresentation( $info ) {
$output = Html::openElement(
"div",
[ "style" => "display: flex; flex-direction: row; align-items: flex-start; flow-wrap: wrap;" ]
);
if ( $info->hasParts() ) {
$output .= Html::openElement( "div", [ "style" => "width: 70%;" ] );
} else {
$output .= Html::openElement( "div", [ "style" => "width: 100%;" ] );
}
$output .= Html::element( "span", [ "style" => "font-weight: bold;" ], $info->getLabel() );
$output .= Html::element( "span", [], " (" . $info->getDescription() . ")" );
$output .= Html::closeElement( "div" );
if ( $info->hasParts() ) {
$output .= Html::openElement(
"div",
[ "style" => "width: 30%; display: flex; justify-content: center;" ]
);
$output .= $info->generateSmallTableOfParts();
$output .= Html::closeElement( "div" );
}
$output .= Html::closeElement( "div" );
return $output;
}
/**
* Defines the parameters for this API endpoint
* @return array
*/
public function getAllowedParams() {
return [
'qid' => [
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
],
'uselang' => [
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => false
]
];
}
/**
* @see ApiBase::getExamplesMessages()
* @return array
*/
protected function getExamplesMessages() {
return [
'action=query&prop=mathwbextracts&mathqid=Q35875&mathuselang=en'
=> 'apihelp-query+mathwbextracts-example-1',
];
}
/**
* @see ApiBase::getHelpUrls()
* @return string
*/
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/Extension:Math#API';
}
}

View file

@ -7,6 +7,7 @@
*/
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
/**
* Converts LaTeX to MathML using the mathoid-server
@ -471,6 +472,8 @@ class MathMathML extends MathRenderer {
* @return string Html output that is embedded in the page
*/
public function getHtmlOutput() {
$config = MediaWikiServices::getInstance()->getMainConfig();
$enableLinks = $config->get( "MathEnableFormulaLinks" );
if ( $this->getMathStyle() == 'display' ) {
$element = 'div';
} else {
@ -480,10 +483,18 @@ class MathMathML extends MathRenderer {
if ( $this->getID() !== '' ) {
$attribs['id'] = $this->getID();
}
$hyperlink = null;
if ( isset( $this->params['qid'] ) && preg_match( '/Q\d+/', $this->params['qid'] ) ) {
$attribs['data-qid'] = $this->params['qid'];
// TODO: SpecialPage::getTitleFor uses the depcrated method Title::newFromTitleValue and
// declares a never thrown MWException. Once this SpecialPage is updated, update the next line.
$titleObj = Title::newFromLinkTarget( SpecialPage::getTitleValueFor( 'MathWikibase' ) );
$hyperlink = $titleObj->getLocalURL( [ 'qid' => $this->params['qid'] ] );
}
$output = Html::openElement( $element, $attribs );
if ( $hyperlink && $enableLinks ) {
$output .= Html::openElement( 'a', [ 'href' => $hyperlink, 'style' => 'color:inherit;' ] );
}
// MathML has to be wrapped into a div or span in order to be able to hide it.
// Remove displayStyle attributes set by the MathML converter
// (Beginning from Mathoid 0.2.5 block is the default layout.)
@ -497,6 +508,9 @@ class MathMathML extends MathRenderer {
'class' => $this->getClassName(), 'style' => 'display: none;'
], $mml );
$output .= $this->getFallbackImage();
if ( $hyperlink && $enableLinks ) {
$output .= Html::closeElement( 'a' );
}
$output .= Html::closeElement( $element );
return $output;
}

133
src/MathWikibaseConfig.php Normal file
View file

@ -0,0 +1,133 @@
<?php
use MediaWiki\MediaWikiServices;
use Wikibase\DataModel\Entity\EntityIdParser;
use Wikibase\DataModel\Entity\PropertyId;
use Wikibase\Lib\Store\EntityRevisionLookup;
use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookupFactory;
use Wikibase\Repo\WikibaseRepo;
/**
* A config class for the MathWikibaseConnector to connect with Wikibase
* @see MathWikibaseConnector
*/
class MathWikibaseConfig {
/**
* @var EntityIdParser
*/
private $idParser;
/**
* @var EntityRevisionLookup
*/
private $entityRevisionLookup;
/**
* @var LanguageFallbackLabelDescriptionLookupFactory
*/
private $labelLookupFactory;
/**
* @var PropertyId
*/
private $propertyIdHasPart;
/**
* @var PropertyId
*/
private $propertyIdQuantitySymbol;
/**
* @var PropertyId
*/
private $propertyIdDefiningFormula;
/**
* @var MathWikibaseConfig
*/
private static $defaultConfig;
/**
* MathWikibaseConfig constructor.
* @param EntityIdParser $entityIdParser
* @param EntityRevisionLookup $entityRevisionLookup
* @param LanguageFallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory
*/
public function __construct(
EntityIdParser $entityIdParser,
EntityRevisionLookup $entityRevisionLookup,
LanguageFallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory
) {
$this->idParser = $entityIdParser;
$this->entityRevisionLookup = $entityRevisionLookup;
$this->labelLookupFactory = $labelDescriptionLookupFactory;
$config = MediaWikiServices::getInstance()->getMainConfig();
$this->propertyIdHasPart = $this->idParser->parse(
$config->get( "MathWikibasePropertyIdHasPart" )
);
$this->propertyIdDefiningFormula = $this->idParser->parse(
$config->get( "MathWikibasePropertyIdDefiningFormula" )
);
$this->propertyIdQuantitySymbol = $this->idParser->parse(
$config->get( "MathWikibasePropertyIdQuantitySymbol" )
);
}
/**
* @return EntityIdParser
*/
public function getIdParser() : EntityIdParser {
return $this->idParser;
}
/**
* @return EntityRevisionLookup
*/
public function getEntityRevisionLookup() : EntityRevisionLookup {
return $this->entityRevisionLookup;
}
/**
* @return LanguageFallbackLabelDescriptionLookupFactory
*/
public function getLabelLookupFactory() : LanguageFallbackLabelDescriptionLookupFactory {
return $this->labelLookupFactory;
}
/**
* @return PropertyId
*/
public function getPropertyIdHasPart() : PropertyId {
return $this->propertyIdHasPart;
}
/**
* @return PropertyId
*/
public function getPropertyIdQuantitySymbol() : PropertyId {
return $this->propertyIdQuantitySymbol;
}
/**
* @return PropertyId
*/
public function getPropertyIdDefiningFormula() : PropertyId {
return $this->propertyIdDefiningFormula;
}
/**
* @return MathWikibaseConfig default config
*/
public static function getDefaultMathWikibaseConfig() : MathWikibaseConfig {
if ( !self::$defaultConfig ) {
$wikibaseRepo = WikibaseRepo::getDefaultInstance();
self::$defaultConfig = new MathWikibaseConfig(
$wikibaseRepo->getEntityIdParser(),
$wikibaseRepo->getEntityRevisionLookup(),
$wikibaseRepo->getLanguageFallbackLabelDescriptionLookupFactory()
);
}
return self::$defaultConfig;
}
}

View file

@ -0,0 +1,232 @@
<?php
use DataValues\StringValue;
use Wikibase\Client\WikibaseClient;
use Wikibase\DataModel\Entity\EntityIdParsingException;
use Wikibase\DataModel\Entity\EntityIdValue;
use Wikibase\DataModel\Entity\Item;
use Wikibase\DataModel\Services\Lookup\LabelDescriptionLookup;
use Wikibase\DataModel\Snak\PropertyValueSnak;
use Wikibase\DataModel\Snak\Snak;
use Wikibase\DataModel\Statement\StatementList;
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
use Wikibase\Lib\Store\StorageException;
/**
* A class that connects with the local instance of wikibase to fetch
* information from single items. There is always only one instance of this class.
*
* @see WikibaseRepo::getDefaultInstance() the instance thats been used to fetch the data
* @see MathWikibaseConnector::getInstance() to get an instance of the class
*/
class MathWikibaseConnector {
/**
* @var MathWikibaseConfig
*/
private $config;
/**
* MathWikibaseConnector constructor.
* @param MathWikibaseConfig $config
*/
public function __construct( MathWikibaseConfig $config ) {
$this->config = $config;
}
/**
* @param string $qid
* @param string $langCode the language to fetch data
* (may fallback if requested language does not exist)
*
* @return MathWikibaseInfo the object may be empty if no information can be fetched.
* @throws InvalidArgumentException if the language code does not exist or the given
* id does not exist
*/
public function fetchWikibaseFromId( $qid, $langCode ) {
try {
$lang = Language::factory( $langCode );
} catch ( MWException $e ) {
throw new InvalidArgumentException( "Invalid language code specified." );
}
$langLookupFactory = $this->config->getLabelLookupFactory();
$langLookup = $langLookupFactory->newLabelDescriptionLookup( $lang );
$idParser = $this->config->getIdParser();
$entityRevisionLookup = $this->config->getEntityRevisionLookup();
try {
$entityId = $idParser->parse( $qid ); // exception if the given ID is invalid
$entityRevision = $entityRevisionLookup->getEntityRevision( $entityId );
} catch ( EntityIdParsingException $e ) {
throw new InvalidArgumentException( "Invalid Wikibase ID." );
} catch ( RevisionedUnresolvedRedirectException $e ) {
throw new InvalidArgumentException( "Non-existing Wikibase ID." );
} catch ( StorageException $e ) {
throw new InvalidArgumentException( "Non-existing Wikibase ID." );
}
if ( !$entityId || !$entityRevision ) {
throw new InvalidArgumentException( "Non-existing Wikibase ID." );
}
$entity = $entityRevision->getEntity();
$output = new MathWikibaseInfo( $entityId );
if ( $entity instanceof Item ) {
$this->fetchLabelDescription( $output, $langLookup );
$this->fetchStatements( $output, $entity, $langLookup );
return $output;
} else { // we only allow Wikibase items
throw new InvalidArgumentException( "The specified Wikibase ID does not represented an item." );
}
}
/**
* Fetches only label and description from an entity.
* @param MathWikibaseInfo $output the entity id of the entity
* @param LabelDescriptionLookup $langLookup a lookup handler to fetch right languages
* @return MathWikibaseInfo filled up with label and description
*/
private function fetchLabelDescription(
MathWikibaseInfo $output,
LabelDescriptionLookup $langLookup ) {
$label = $langLookup->getLabel( $output->getId() );
$desc = $langLookup->getDescription( $output->getId() );
if ( $label ) {
$output->setLabel( $label->getText() );
}
if ( $desc ) {
$output->setDescription( $desc->getText() );
}
return $output;
}
/**
* Fetches 'has part' statements from a given item element with a defined lookup object for
* the languages.
* @param MathWikibaseInfo $output the output element
* @param Item $item item to fetch statements from
* @param LabelDescriptionLookup $langLookup
* @return MathWikibaseInfo the updated $output object
*/
private function fetchStatements(
MathWikibaseInfo $output,
Item $item,
LabelDescriptionLookup $langLookup ) {
$statements = $item->getStatements();
$hasPartStatements = $statements->getByPropertyId( $this->config->getPropertyIdHasPart() );
$this->fetchHasPartSnaks( $output, $hasPartStatements, $langLookup );
$symbolStatement = $statements->getByPropertyId( $this->config->getPropertyIdDefiningFormula() );
if ( $symbolStatement->count() < 1 ) { // if it's not a formula, it might be a symbol
$symbolStatement = $statements->getByPropertyId( $this->config->getPropertyIdQuantitySymbol() );
}
$this->fetchSymbol( $output, $symbolStatement );
return $output;
}
/**
* Fetches the symbol or defining formula from a statement list and adds the symbol to the
* given info object
* @param MathWikibaseInfo $output
* @param StatementList $statements
* @return MathWikibaseInfo updated object
*/
private function fetchSymbol( MathWikibaseInfo $output, StatementList $statements ) {
foreach ( $statements as $statement ) {
$snak = $statement->getMainSnak();
if ( $snak instanceof PropertyValueSnak && $this->isQualifierDefinien( $snak ) ) {
$dataVal = $snak->getDataValue();
$symbol = new StringValue( $dataVal->getValue() );
$output->setSymbol( $symbol );
return $output;
}
}
return $output;
}
/**
* Fetches single snaks from 'has part' statements
*
* @param MathWikibaseInfo $output
* @param StatementList $statements the 'has part' statements
* @param LabelDescriptionLookup $langLookup
* @return MathWikibaseInfo
*@todo refactor this method once Wikibase has a more convenient way to handle snaks
*/
private function fetchHasPartSnaks(
MathWikibaseInfo $output,
StatementList $statements,
LabelDescriptionLookup $langLookup ) {
foreach ( $statements as $statement ) {
$snaks = $statement->getAllSnaks();
$innerInfo = null;
$symbol = null;
foreach ( $snaks as $snak ) {
if ( $snak instanceof PropertyValueSnak ) {
if ( $this->isQualifierDefinien( $snak ) ) {
$dataVal = $snak->getDataValue();
$symbol = new StringValue( $dataVal->getValue() );
} elseif ( $snak->getPropertyId()->equals( $this->config->getPropertyIdHasPart() ) ) {
$dataVal = $snak->getDataValue();
$entityIdValue = $dataVal->getValue();
if ( $entityIdValue instanceof EntityIdValue ) {
$innerEntityId = $entityIdValue->getEntityId();
$innerInfo = new MathWikibaseInfo( $innerEntityId );
$this->fetchLabelDescription( $innerInfo, $langLookup );
}
}
}
}
if ( $innerInfo ) {
$innerInfo->setSymbol( $symbol );
$output->addHasPartElement( $innerInfo );
}
}
return $output;
}
/**
* @param Snak $snak
* @return bool true if the given snak is either a defining formula or a quantity symbol
*/
private function isQualifierDefinien( Snak $snak ) {
return $snak->getPropertyId()->equals( $this->config->getPropertyIdQuantitySymbol() ) ||
$snak->getPropertyId()->equals( $this->config->getPropertyIdDefiningFormula() );
}
/**
* @todo should be refactored once there is an easier way to get the URL
* @param string $qID
* @return string
*/
public static function buildURL( $qID ) {
$baseurl = WikibaseClient::getDefaultInstance()
->getSettings()->getSetting( 'repoUrl' );
$articlePath = WikibaseClient::getDefaultInstance()
->getSettings()->getSetting( 'repoArticlePath' );
$namespaces = WikibaseClient::getDefaultInstance()
->getSettings()->getSetting( 'repoNamespaces' );
$url = $baseurl . $articlePath;
if ( $namespaces && $namespaces["item"] ) {
$articleId = $namespaces["item"] . ":" . $qID;
} else {
$articleId = $qID;
}
// repoArticlePath contains the placeholder $1 for the page title
// see: https://www.mediawiki.org/wiki/Manual:$wgArticlePath
return str_replace( '$1', $articleId, $url );
}
}

199
src/MathWikibaseInfo.php Normal file
View file

@ -0,0 +1,199 @@
<?php
use DataValues\StringValue;
use MediaWiki\MediaWikiServices;
use Wikibase\DataModel\Entity\EntityId;
use Wikibase\Lib\Formatters\SnakFormatter;
/**
* This class stores information about mathematical Wikibase items.
*/
class MathWikibaseInfo {
/**
* @var EntityId
*/
private $id;
/**
* @var string the label of the item
*/
private $label;
/**
* @var string description of the item
*/
private $description;
/**
* @var StringValue a symbol representing the item
*/
private $symbol;
/**
* @var MathWikibaseInfo[]
*/
private $hasParts = [];
/**
* @var MathFormatter
*/
private $mathFormatter;
public function __construct( EntityId $entityId ) {
$this->id = $entityId;
$this->mathFormatter = new MathFormatter( SnakFormatter::FORMAT_HTML );
}
/**
* @param string $label
*/
public function setLabel( $label ) {
$this->label = $label;
}
/**
* @param string $description
*/
public function setDescription( $description ) {
$this->description = $description;
}
/**
* @param StringValue $symbol
*/
public function setSymbol( $symbol ) {
$this->symbol = $symbol;
}
/**
* @param MathWikibaseInfo $info
*/
public function addHasPartElement( MathWikibaseInfo $info ) {
array_push( $this->hasParts, $info );
}
/**
* @param MathWikibaseInfo[] $infos
*/
public function addHasPartElements( $infos ) {
array_push( $this->hasParts, ...$infos );
}
/**
* @return EntityId id
*/
public function getId() {
return $this->id;
}
/**
* @return string label
*/
public function getLabel() {
return $this->label;
}
/**
* @return string description
*/
public function getDescription() {
return $this->description;
}
/**
* @return StringValue symbol
*/
public function getSymbol() {
return $this->symbol;
}
/**
* @return string|null html formatted version of the symbol
*/
public function getFormattedSymbol() {
if ( $this->symbol ) {
return $this->mathFormatter->format( $this->getSymbol() );
} else {
return null;
}
}
/**
* @return MathWikibaseInfo[] hasparts
*/
public function getParts() {
return $this->hasParts;
}
/**
* Does this info object has elements?
* @return bool true if there are elements otherwise false
*/
public function hasParts() {
if ( !$this->hasParts ) {
return false;
}
return $this->hasParts !== [];
}
/**
* Generates an HTML table representation of the has-parts elements
* @return string
*/
public function generateTableOfParts() {
$lang = MediaWikiServices::getInstance()->getContentLanguage();
$labelAlign = $lang->isRTL() ? 'left' : 'right';
$labelAlignOpposite = !$lang->isRTL() ? 'left' : 'right';
$output = HTML::openElement( "table", [ "style" => "padding: 5px" ] );
$output .= HTML::openElement( "tbody" );
foreach ( $this->hasParts as $part ) {
$output .= HTML::openElement( "tr" );
$output .= HTML::element(
"td",
[ "style" => "font-weight: bold; text-align:$labelAlign;" ],
$part->getLabel()
);
$output .= HTML::rawElement(
"td",
[ "style" => "text-align:center; padding: 2px; padding-left: 10px; padding-right: 10px;" ],
$part->getFormattedSymbol()
);
$output .= HTML::element(
"td",
[ "style" => "font-style: italic; text-align:$labelAlignOpposite;" ],
$part->getDescription()
);
$output .= HTML::closeElement( "tr" );
}
$output .= HTML::closeElement( "tbody" );
$output .= HTML::closeElement( "table" );
return $output;
}
/**
* Generates a minimalized HTML representation of the has-parts elements.
* @return string
*/
public function generateSmallTableOfParts() {
$output = HTML::openElement( "table" );
$output .= HTML::openElement( "tbody" );
foreach ( $this->hasParts as $part ) {
$output .= HTML::openElement( "tr" );
$output .= HTML::rawElement(
"td",
[ "style" => "text-align:right;" ],
$part->getFormattedSymbol()
);
$output .= HTML::element( "td", [ "style" => "text-align:left;" ], $part->getLabel() );
$output .= HTML::closeElement( "tr" );
}
$output .= HTML::closeElement( "tbody" );
$output .= HTML::closeElement( "table" );
return $output;
}
}

258
src/SpecialMathWikibase.php Normal file
View file

@ -0,0 +1,258 @@
<?php
use MediaWiki\Logger\LoggerFactory;
class SpecialMathWikibase extends SpecialPage {
/**
* The parameter for this special page
*/
const PARAMETER = "qid";
/**
* @var MathWikibaseConnector Wikibase connection
*/
private $wikibase;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* SpecialMathWikibase constructor.
*/
public function __construct() {
parent::__construct(
'MathWikibase',
'', // no restriciton
true // show on Special:SpecialPages
);
$this->logger = LoggerFactory::getInstance( 'Math' );
}
/**
* @inheritDoc
*/
public function execute( $par ) {
global $wgContLanguageCode;
if ( !$this->isWikibaseAvailable() ) {
$out = $this->getOutput();
$out->setPageTitle(
$this->getPlainText( 'math-wikibase-special-error-header' )
);
$out->addHTML(
wfMessage( 'math-wikibase-special-error-no-wikibase' )->inContentLanguage()
);
return;
}
if ( !$this->wikibase ) {
$this->wikibase = new MathWikibaseConnector(
MathWikibaseConfig::getDefaultMathWikibaseConfig()
);
}
$request = $this->getRequest();
$output = $this->getOutput();
$output->enableOOUI();
$this->setHeaders();
$output->addModules( [ 'ext.math.wikibase.scripts' ] );
$output->setPageTitle(
$this->getPlainText( 'math-wikibase-header' )
);
// Get request
$requestId = $request->getText( self::PARAMETER );
// if there is no id requested, show the request form
if ( !$requestId ) {
$this->showForm();
} else {
$this->logger->debug( "Request qID: " . $requestId );
try {
$info = $this->wikibase->fetchWikibaseFromId( $requestId, $wgContLanguageCode );
$this->logger->debug( "Successfully fetched information for qID: " . $requestId );
self::buildPageRepresentation( $info, $requestId, $output );
} catch ( Exception $e ) {
$this->showError( $e );
}
}
}
/**
* Shows the form to request information for a specific Wikibase id
*/
private function showForm() {
$actionField = new \OOUI\ActionFieldLayout(
new \OOUI\TextInputWidget( [
'name' => self::PARAMETER,
'placeholder' => $this->getPlainText( 'math-wikibase-special-form-placeholder' ),
'required' => true,
'autocomplete' => false,
'classes' => [ 'mwe-math-wikibase-entityselector-input' ]
] ),
new OOUI\ButtonInputWidget( [
'name' => 'request-qid',
'label' => $this->getPlainText( 'math-wikibase-special-form-button' ),
'type' => 'submit',
'flags' => [ 'primary', 'progressive' ],
'icon' => 'check',
] ),
[
'label' => $this->getPlainText( 'math-wikibase-special-form-header' ),
'align' => 'top'
]
);
$formLayout = new \OOUI\FormLayout( [
'method' => 'POST',
'items' => [ $actionField ]
] );
$this->getOutput()->addHTML( $formLayout );
}
/**
* Shows an error message for the user and writes information to $logger
* @param Exception $e can potentially be any exception.
*/
private function showError( Exception $e ) {
$this->getOutput()->setPageTitle(
$this->getPlainText( 'math-wikibase-special-error-header' )
);
if ( $e instanceof InvalidArgumentException ) {
$this->logger->warning( "An invalid ID was specified. Reason: " . $e->getMessage() );
$this->getOutput()->addHTML(
wfMessage( 'math-wikibase-special-error-invalid-argument' )->inContentLanguage()
);
} else {
$this->logger->error( "An unknown error occured due fetching data from Wikibase.", [
'exception' => $e
] );
$this->getOutput()->addHTML(
wfMessage( 'math-wikibase-special-error-unknown' )->inContentLanguage()
);
}
}
/**
* Helper function to shorten i18n text processing
* @param string $key
* @return string the plain text in current content language
*/
private function getPlainText( $key ) {
return wfMessage( $key )->inContentLanguage()->plain();
}
/**
* @param MathWikibaseInfo $info
* @param string $qid
* @param OutputPage $output
*/
public static function buildPageRepresentation(
MathWikibaseInfo $info,
$qid,
OutputPage $output ) {
$output->setPageTitle( $info->getLabel() );
// if 'instance of' is specified, it can be found in the description before a colon
preg_match( '/(.*):\s*(.*)/', $info->getDescription(), $matches );
if ( count( $matches ) > 1 ) {
$output->setSubtitle( $matches[1] );
}
// add formula information
$header = wfMessage( 'math-wikibase-formula-information' )->inContentLanguage();
$output->addHTML( self::createHTMLHeader( $header ) );
if ( $info->getSymbol() ) {
$math = $info->getFormattedSymbol();
$formulaInfo = new Message( 'math-wikibase-formula-header-format' );
$formulaInfo->rawParams(
wfMessage( 'math-wikibase-formula' )->inContentLanguage(),
$math
);
$output->addHTML( HTML::rawElement( "p", [], $formulaInfo->inContentLanguage() ) );
}
$labelName = wfMessage(
'math-wikibase-formula-header-format',
wfMessage( 'math-wikibase-formula-name' )->inContentLanguage(),
$info->getLabel()
)->inContentLanguage();
$output->addHTML( HTML::rawElement( "p", [], $labelName ) );
if ( count( $matches ) > 2 ) {
$labelType = wfMessage(
'math-wikibase-formula-header-format',
wfMessage( 'math-wikibase-formula-type' )->inContentLanguage(),
$matches[1]
)->inContentLanguage();
$labelDesc = wfMessage(
'math-wikibase-formula-header-format',
wfMessage( 'math-wikibase-formula-description' )->inContentLanguage(),
$matches[2]
)->inContentLanguage();
$output->addHTML( HTML::rawElement( "p", [], $labelType ) );
$output->addHTML( HTML::rawElement( "p", [], $labelDesc ) );
} else {
$labelDesc = wfMessage(
'math-wikibase-formula-header-format',
wfMessage( 'math-wikibase-formula-description' )->inContentLanguage(),
$info->getDescription()
)->inContentLanguage();
$output->addHTML( HTML::rawElement( "p", [], $labelDesc ) );
}
// add parts of formula
if ( $info->hasParts() ) {
$elementsHeader = wfMessage( 'math-wikibase-formula-elements-header' )
->inContentLanguage()->escaped();
$output->addHTML( self::createHTMLHeader( $elementsHeader ) );
$output->addHTML( $info->generateTableOfParts() );
}
// add link information
$wikibaseHeader = wfMessage(
'math-wikibase-formula-link-header',
$info->getDescription()
)->inContentLanguage();
$output->addHTML( self::createHTMLHeader( $wikibaseHeader ) );
$url = MathWikibaseConnector::buildURL( $qid );
$link = HTML::linkButton( $url, [ "href" => $url ] );
$output->addHTML( HTML::rawElement( "p", [], $link ) );
}
/**
* Generates a header as HTML
* @param $header
* @return string
*/
private static function createHTMLHeader( $header ) {
$headerOut = HTML::openElement( "h2" );
$headerOut .= HTML::rawElement( "span", [ "class" => "mw-headline" ], $header );
$headerOut .= HTML::closeElement( "h2" );
return $headerOut;
}
/**
* Check whether Wikibase is available or not
* @return true|false
*/
public static function isWikibaseAvailable() {
return class_exists( '\Wikibase\Client\WikibaseClient' ) &&
class_exists( '\Wikibase\Repo\WikibaseRepo' );
}
}