2019-07-19 20:46:20 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
use DataValues\StringValue;
|
2019-11-26 17:24:58 +00:00
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2019-07-19 20:46:20 +00:00
|
|
|
use Wikibase\Client\WikibaseClient;
|
2019-11-26 17:24:58 +00:00
|
|
|
use Wikibase\DataModel\Entity\EntityId;
|
2019-07-19 20:46:20 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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." );
|
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();
|
|
|
|
|
|
|
|
$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
|
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 ) {
|
|
|
|
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 );
|
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 {
|
|
|
|
$entityRevisionLookup = $this->config->getEntityRevisionLookup();
|
|
|
|
$entityRevision = $entityRevisionLookup->getEntityRevision( $entityId );
|
|
|
|
$innerEntity = $entityRevision->getEntity();
|
|
|
|
if ( $innerEntity instanceof Item ) {
|
2019-11-28 10:25:31 +00:00
|
|
|
if ( $this->config->hasSite() ) {
|
2019-11-26 17:24:58 +00:00
|
|
|
$site = $this->config->getSite();
|
|
|
|
$globalID = $site->getGlobalId();
|
|
|
|
if ( $innerEntity->hasLinkToSite( $globalID ) ) {
|
|
|
|
$siteLink = $innerEntity->getSiteLink( $globalID );
|
|
|
|
return $site->getPageUrl( $siteLink->getPageName() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch ( StorageException $e ) {
|
|
|
|
$logger = LoggerFactory::getInstance( 'Math' );
|
|
|
|
$logger->warning(
|
|
|
|
"Cannot fetch URL for EntityId " . $entityId . ". Reason: " . $e->getMessage()
|
|
|
|
);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-19 20:46:20 +00:00
|
|
|
/**
|
|
|
|
* @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 ) {
|
2021-03-04 10:36:07 +00:00
|
|
|
$settings = WikibaseClient::getSettings();
|
|
|
|
$baseurl = $settings->getSetting( 'repoUrl' );
|
|
|
|
$articlePath = $settings->getSetting( 'repoArticlePath' );
|
|
|
|
$namespaces = $settings->getSetting( 'repoNamespaces' );
|
2019-07-19 20:46:20 +00:00
|
|
|
|
|
|
|
$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 );
|
|
|
|
}
|
|
|
|
}
|