2019-07-19 20:46:20 +00:00
|
|
|
<?php
|
|
|
|
|
2021-04-07 22:22:05 +00:00
|
|
|
namespace MediaWiki\Extension\Math;
|
|
|
|
|
2019-07-19 20:46:20 +00:00
|
|
|
use DataValues\StringValue;
|
2021-04-07 22:22:05 +00:00
|
|
|
use InvalidArgumentException;
|
2022-07-23 09:33:40 +00:00
|
|
|
use MediaWiki\Config\ServiceOptions;
|
2022-07-21 08:02:56 +00:00
|
|
|
use MediaWiki\Languages\LanguageFactory;
|
2021-04-07 22:22:05 +00:00
|
|
|
use MWException;
|
2022-07-20 18:14:11 +00:00
|
|
|
use Psr\Log\LoggerInterface;
|
2022-07-23 09:33:40 +00:00
|
|
|
use Site;
|
2022-07-20 18:14:11 +00:00
|
|
|
use Wikibase\Client\RepoLinker;
|
2019-11-26 17:24:58 +00:00
|
|
|
use Wikibase\DataModel\Entity\EntityId;
|
2022-07-23 09:33:40 +00:00
|
|
|
use Wikibase\DataModel\Entity\EntityIdParser;
|
2019-07-19 20:46:20 +00:00
|
|
|
use Wikibase\DataModel\Entity\EntityIdParsingException;
|
|
|
|
use Wikibase\DataModel\Entity\EntityIdValue;
|
|
|
|
use Wikibase\DataModel\Entity\Item;
|
2021-07-13 09:53:32 +00:00
|
|
|
use Wikibase\DataModel\Entity\ItemId;
|
2022-07-23 09:33:40 +00:00
|
|
|
use Wikibase\DataModel\Entity\PropertyId;
|
2019-07-19 20:46:20 +00:00
|
|
|
use Wikibase\DataModel\Services\Lookup\LabelDescriptionLookup;
|
|
|
|
use Wikibase\DataModel\Snak\PropertyValueSnak;
|
|
|
|
use Wikibase\DataModel\Snak\Snak;
|
|
|
|
use Wikibase\DataModel\Statement\StatementList;
|
2022-07-23 09:33:40 +00:00
|
|
|
use Wikibase\Lib\Store\EntityRevisionLookup;
|
|
|
|
use Wikibase\Lib\Store\FallbackLabelDescriptionLookupFactory;
|
2019-07-19 20:46:20 +00:00
|
|
|
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 MathWikibaseConnector::getInstance() to get an instance of the class
|
|
|
|
*/
|
|
|
|
class MathWikibaseConnector {
|
2022-07-23 09:33:40 +00:00
|
|
|
/** @var string[] */
|
|
|
|
public const CONSTRUCTOR_OPTIONS = [
|
|
|
|
'MathWikibasePropertyIdHasPart',
|
|
|
|
'MathWikibasePropertyIdDefiningFormula',
|
2022-07-15 12:30:15 +00:00
|
|
|
'MathWikibasePropertyIdInDefiningFormula',
|
|
|
|
'MathWikibasePropertyIdQuantitySymbol',
|
|
|
|
'MathWikibasePropertyIdSymbolRepresents'
|
2022-07-23 09:33:40 +00:00
|
|
|
];
|
2019-07-19 20:46:20 +00:00
|
|
|
|
2022-07-20 18:14:11 +00:00
|
|
|
/** @var LoggerInterface */
|
|
|
|
private $logger;
|
|
|
|
|
|
|
|
/** @var RepoLinker */
|
|
|
|
private $repoLinker;
|
|
|
|
|
2022-07-21 08:02:56 +00:00
|
|
|
/** @var LanguageFactory */
|
|
|
|
private $languageFactory;
|
|
|
|
|
2022-07-23 09:33:40 +00:00
|
|
|
/** @var EntityRevisionLookup */
|
|
|
|
private $entityRevisionLookup;
|
|
|
|
|
|
|
|
/** @var Site */
|
|
|
|
private $site;
|
|
|
|
|
|
|
|
/** @var FallbackLabelDescriptionLookupFactory */
|
|
|
|
private $labelDescriptionLookupFactory;
|
|
|
|
|
|
|
|
/** @var EntityIdParser */
|
|
|
|
private $idParser;
|
|
|
|
|
2022-07-15 12:30:15 +00:00
|
|
|
/** @var PropertyId|null */
|
2022-07-23 09:33:40 +00:00
|
|
|
private $propertyIdHasPart;
|
|
|
|
|
2022-07-15 12:30:15 +00:00
|
|
|
/** @var PropertyId|null */
|
2022-07-23 09:33:40 +00:00
|
|
|
private $propertyIdDefiningFormula;
|
|
|
|
|
2022-07-15 12:30:15 +00:00
|
|
|
/** @var PropertyId|null */
|
|
|
|
private $propertyIdInDefiningFormula;
|
|
|
|
|
|
|
|
/** @var PropertyId|null */
|
2022-07-23 09:33:40 +00:00
|
|
|
private $propertyIdQuantitySymbol;
|
|
|
|
|
2022-07-15 12:30:15 +00:00
|
|
|
/** @var PropertyId|null */
|
|
|
|
private $propertyIdSymbolRepresents;
|
|
|
|
|
2019-07-19 20:46:20 +00:00
|
|
|
/**
|
2022-07-23 09:33:40 +00:00
|
|
|
* @param ServiceOptions $options
|
2022-07-20 18:14:11 +00:00
|
|
|
* @param RepoLinker $repoLinker
|
2022-07-21 08:02:56 +00:00
|
|
|
* @param LanguageFactory $languageFactory
|
2022-07-23 09:33:40 +00:00
|
|
|
* @param EntityRevisionLookup $entityRevisionLookup
|
|
|
|
* @param FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory
|
|
|
|
* @param Site $site
|
|
|
|
* @param EntityIdParser $entityIdParser
|
2022-07-20 18:14:11 +00:00
|
|
|
* @param LoggerInterface $logger
|
2019-07-19 20:46:20 +00:00
|
|
|
*/
|
2022-07-20 18:14:11 +00:00
|
|
|
public function __construct(
|
2022-07-23 09:33:40 +00:00
|
|
|
ServiceOptions $options,
|
2022-07-20 18:14:11 +00:00
|
|
|
RepoLinker $repoLinker,
|
2022-07-21 08:02:56 +00:00
|
|
|
LanguageFactory $languageFactory,
|
2022-07-23 09:33:40 +00:00
|
|
|
EntityRevisionLookup $entityRevisionLookup,
|
|
|
|
FallbackLabelDescriptionLookupFactory $labelDescriptionLookupFactory,
|
|
|
|
Site $site,
|
|
|
|
EntityIdParser $entityIdParser,
|
2022-07-20 18:14:11 +00:00
|
|
|
LoggerInterface $logger
|
|
|
|
) {
|
2022-07-23 09:33:40 +00:00
|
|
|
$options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
2022-07-20 18:14:11 +00:00
|
|
|
$this->repoLinker = $repoLinker;
|
2022-07-21 08:02:56 +00:00
|
|
|
$this->languageFactory = $languageFactory;
|
2022-07-23 09:33:40 +00:00
|
|
|
$this->entityRevisionLookup = $entityRevisionLookup;
|
|
|
|
$this->labelDescriptionLookupFactory = $labelDescriptionLookupFactory;
|
|
|
|
$this->site = $site;
|
|
|
|
$this->idParser = $entityIdParser;
|
2022-07-20 18:14:11 +00:00
|
|
|
$this->logger = $logger;
|
2022-07-23 09:33:40 +00:00
|
|
|
|
2022-07-15 12:30:15 +00:00
|
|
|
$this->propertyIdHasPart = $this->loadPropertyId(
|
2022-07-23 09:33:40 +00:00
|
|
|
$options->get( "MathWikibasePropertyIdHasPart" )
|
|
|
|
);
|
2022-07-15 12:30:15 +00:00
|
|
|
$this->propertyIdDefiningFormula = $this->loadPropertyId(
|
2022-07-23 09:33:40 +00:00
|
|
|
$options->get( "MathWikibasePropertyIdDefiningFormula" )
|
|
|
|
);
|
2022-07-15 12:30:15 +00:00
|
|
|
$this->propertyIdInDefiningFormula = $this->loadPropertyId(
|
|
|
|
$options->get( "MathWikibasePropertyIdInDefiningFormula" )
|
|
|
|
);
|
|
|
|
$this->propertyIdQuantitySymbol = $this->loadPropertyId(
|
2022-07-23 09:33:40 +00:00
|
|
|
$options->get( "MathWikibasePropertyIdQuantitySymbol" )
|
|
|
|
);
|
2022-07-15 12:30:15 +00:00
|
|
|
$this->propertyIdSymbolRepresents = $this->loadPropertyId(
|
|
|
|
$options->get( "MathWikibasePropertyIdSymbolRepresents" )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the given PropertyId if available.
|
|
|
|
* @param string $propertyId the string of the Wikibase property
|
|
|
|
* @return EntityId|null the property object or null if unavailable
|
|
|
|
*/
|
|
|
|
private function loadPropertyId( string $propertyId ): ?EntityId {
|
|
|
|
try {
|
|
|
|
return $this->idParser->parse( $propertyId );
|
|
|
|
} catch ( \ConfigException $e ) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the inner statements from given statements for a given property ID or an empty list if the given ID
|
|
|
|
* not exists.
|
|
|
|
* @param StatementList $statements
|
|
|
|
* @param PropertyId|null $id
|
|
|
|
* @return StatementList might be empty
|
|
|
|
*/
|
|
|
|
private function getStatements( StatementList $statements, ?PropertyId $id ): StatementList {
|
|
|
|
if ( $id === null ) {
|
|
|
|
return new StatementList();
|
|
|
|
}
|
|
|
|
return $statements->getByPropertyId( $id );
|
2019-07-19 20:46:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
2022-07-15 12:30:15 +00:00
|
|
|
public function fetchWikibaseFromId( $qid, $langCode ): MathWikibaseInfo {
|
2019-07-19 20:46:20 +00:00
|
|
|
try {
|
2022-07-21 08:02:56 +00:00
|
|
|
$lang = $this->languageFactory->getLanguage( $langCode );
|
2019-07-19 20:46:20 +00:00
|
|
|
} catch ( MWException $e ) {
|
|
|
|
throw new InvalidArgumentException( "Invalid language code specified." );
|
|
|
|
}
|
|
|
|
|
2022-07-23 09:33:40 +00:00
|
|
|
$langLookup = $this->labelDescriptionLookupFactory->newLabelDescriptionLookup( $lang );
|
2019-07-19 20:46:20 +00:00
|
|
|
try {
|
2022-07-23 09:33:40 +00:00
|
|
|
$entityId = $this->idParser->parse( $qid ); // exception if the given ID is invalid
|
|
|
|
$entityRevision = $this->entityRevisionLookup->getEntityRevision( $entityId );
|
2019-07-19 20:46:20 +00:00
|
|
|
} catch ( EntityIdParsingException $e ) {
|
|
|
|
throw new InvalidArgumentException( "Invalid Wikibase ID." );
|
2020-11-20 09:26:10 +00:00
|
|
|
} catch ( RevisionedUnresolvedRedirectException | StorageException $e ) {
|
2019-07-19 20:46:20 +00:00
|
|
|
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();
|
2022-07-15 12:30:15 +00:00
|
|
|
$formulaComponentStatements = $this->getStatements( $statements, $this->propertyIdHasPart );
|
|
|
|
if ( $formulaComponentStatements->isEmpty() ) {
|
|
|
|
$formulaComponentStatements = $this->getStatements( $statements, $this->propertyIdInDefiningFormula );
|
|
|
|
}
|
|
|
|
$this->fetchHasPartSnaks( $output, $formulaComponentStatements, $langLookup );
|
2019-07-19 20:46:20 +00:00
|
|
|
|
2022-07-15 12:30:15 +00:00
|
|
|
$symbolStatement = $this->getStatements( $statements, $this->propertyIdDefiningFormula );
|
2019-07-19 20:46:20 +00:00
|
|
|
if ( $symbolStatement->count() < 1 ) { // if it's not a formula, it might be a symbol
|
2022-07-15 12:30:15 +00:00
|
|
|
$symbolStatement = $this->getStatements( $statements, $this->propertyIdQuantitySymbol );
|
2019-07-19 20:46:20 +00:00
|
|
|
}
|
|
|
|
$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();
|
2022-07-15 12:30:15 +00:00
|
|
|
if ( $snak instanceof PropertyValueSnak && $this->isSymbolSnak( $snak ) ) {
|
2019-07-19 20:46:20 +00:00
|
|
|
$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
|
2020-01-14 07:43:50 +00:00
|
|
|
* @todo refactor this method once Wikibase has a more convenient way to handle snaks
|
2019-07-19 20:46:20 +00:00
|
|
|
*/
|
|
|
|
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 ) {
|
2022-07-15 12:30:15 +00:00
|
|
|
if ( $this->isSymbolSnak( $snak ) ) {
|
2019-07-19 20:46:20 +00:00
|
|
|
$dataVal = $snak->getDataValue();
|
|
|
|
$symbol = new StringValue( $dataVal->getValue() );
|
2022-07-15 12:30:15 +00:00
|
|
|
} elseif ( $this->isFormulaItemSnak( $snak ) ) {
|
2019-07-19 20:46:20 +00:00
|
|
|
$dataVal = $snak->getDataValue();
|
|
|
|
$entityIdValue = $dataVal->getValue();
|
|
|
|
if ( $entityIdValue instanceof EntityIdValue ) {
|
|
|
|
$innerEntityId = $entityIdValue->getEntityId();
|
|
|
|
$innerInfo = new MathWikibaseInfo( $innerEntityId );
|
|
|
|
$this->fetchLabelDescription( $innerInfo, $langLookup );
|
2019-11-26 17:24:58 +00:00
|
|
|
$url = $this->fetchPageUrl( $innerEntityId );
|
|
|
|
if ( $url ) {
|
|
|
|
$innerInfo->setUrl( $url );
|
|
|
|
}
|
2019-07-19 20:46:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-21 15:51:10 +00:00
|
|
|
if ( $innerInfo && $symbol ) {
|
2019-07-19 20:46:20 +00:00
|
|
|
$innerInfo->setSymbol( $symbol );
|
|
|
|
$output->addHasPartElement( $innerInfo );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
2019-11-26 17:24:58 +00:00
|
|
|
/**
|
|
|
|
* Fetch the page url for a given entity id.
|
|
|
|
* @param EntityId $entityId
|
2020-12-17 20:53:28 +00:00
|
|
|
* @return string|false
|
2019-11-26 17:24:58 +00:00
|
|
|
*/
|
|
|
|
private function fetchPageUrl( EntityId $entityId ) {
|
|
|
|
try {
|
2022-07-23 09:33:40 +00:00
|
|
|
$entityRevision = $this->entityRevisionLookup->getEntityRevision( $entityId );
|
2019-11-26 17:24:58 +00:00
|
|
|
$innerEntity = $entityRevision->getEntity();
|
|
|
|
if ( $innerEntity instanceof Item ) {
|
2022-07-23 09:33:40 +00:00
|
|
|
$globalID = $this->site->getGlobalId();
|
2019-11-26 17:24:58 +00:00
|
|
|
if ( $innerEntity->hasLinkToSite( $globalID ) ) {
|
|
|
|
$siteLink = $innerEntity->getSiteLink( $globalID );
|
2022-07-23 09:33:40 +00:00
|
|
|
return $this->site->getPageUrl( $siteLink->getPageName() );
|
2019-11-26 17:24:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch ( StorageException $e ) {
|
2022-07-20 18:14:11 +00:00
|
|
|
$this->logger->warning(
|
2019-11-26 17:24:58 +00:00
|
|
|
"Cannot fetch URL for EntityId " . $entityId . ". Reason: " . $e->getMessage()
|
|
|
|
);
|
|
|
|
}
|
2022-07-15 12:30:15 +00:00
|
|
|
return false;
|
2019-11-26 17:24:58 +00:00
|
|
|
}
|
|
|
|
|
2019-07-19 20:46:20 +00:00
|
|
|
/**
|
|
|
|
* @param Snak $snak
|
2022-07-15 12:30:15 +00:00
|
|
|
* @return bool true if the given snak is either a defining formula, a quantity symbol, or a 'in defining formula'
|
2019-07-19 20:46:20 +00:00
|
|
|
*/
|
2022-07-15 12:30:15 +00:00
|
|
|
private function isSymbolSnak( Snak $snak ) {
|
2022-07-23 09:33:40 +00:00
|
|
|
return $snak->getPropertyId()->equals( $this->propertyIdQuantitySymbol ) ||
|
2022-07-15 12:30:15 +00:00
|
|
|
$snak->getPropertyId()->equals( $this->propertyIdDefiningFormula ) ||
|
|
|
|
$snak->getPropertyId()->equals( $this->propertyIdInDefiningFormula );
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Snak $snak
|
|
|
|
* @return bool true if the given snak is either the 'has part or parts' or the 'symbol represents' property
|
|
|
|
*/
|
|
|
|
private function isFormulaItemSnak( Snak $snak ) {
|
|
|
|
return $snak->getPropertyId()->equals( $this->propertyIdHasPart ) ||
|
|
|
|
$snak->getPropertyId()->equals( $this->propertyIdSymbolRepresents );
|
2019-07-19 20:46:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $qID
|
|
|
|
* @return string
|
|
|
|
*/
|
2022-07-20 18:14:11 +00:00
|
|
|
public function buildURL( string $qID ): string {
|
|
|
|
return $this->repoLinker->getEntityUrl( new ItemId( $qID ) );
|
2019-07-19 20:46:20 +00:00
|
|
|
}
|
|
|
|
}
|