| Current File : /home/jvzmxxx/wiki1/extensions/Wikibase/repo/includes/Hooks/LinkBeginHookHandler.php |
<?php
namespace Wikibase\Repo\Hooks;
use Action;
use DummyLinker;
use Language;
use Linker;
use RequestContext;
use SpecialPageFactory;
use Title;
use Wikibase\DataModel\Services\Lookup\TermLookup;
use Wikibase\LanguageFallbackChain;
use Wikibase\Lib\Store\StorageException;
use Wikibase\Repo\EntityNamespaceLookup;
use Wikibase\Repo\WikibaseRepo;
use Wikibase\Store\EntityIdLookup;
/**
* Handler for the LinkBegin hook, used to change the default link text of links to wikibase Entity
* pages to the respective entity's label. This is used mainly for listings on special pages or for
* edit summaries, where it is useful to see pages listed by label rather than their entity ID.
*
* Label lookups are relatively expensive if done repeatedly for individual labels. If possible,
* labels should be pre-loaded and buffered for later use via the LinkBegin hook.
*
* @see LabelPrefetchHookHandlers
*
* @since 0.5
*
* @license GPL-2.0+
* @author Katie Filbert < aude.wiki@gmail.com >
*/
class LinkBeginHookHandler {
/**
* @var EntityIdLookup
*/
private $entityIdLookup;
/**
* @var TermLookup
*/
private $termLookup;
/**
* @var EntityNamespaceLookup
*/
private $entityNamespaceLookup;
/**
* @var LanguageFallbackChain
*/
private $languageFallback;
/**
* @var Language
*/
private $pageLanguage;
/**
* @return self
*/
private static function newFromGlobalState() {
$wikibaseRepo = WikibaseRepo::getDefaultInstance();
$languageFallbackChainFactory = $wikibaseRepo->getLanguageFallbackChainFactory();
// NOTE: keep in sync with fallback chain construction in LabelPrefetchHookHandler::newFromGlobalState
$context = RequestContext::getMain();
$languageFallbackChain = $languageFallbackChainFactory->newFromContext( $context );
return new self(
$wikibaseRepo->getEntityIdLookup(),
$wikibaseRepo->getTermLookup(),
$wikibaseRepo->getEntityNamespaceLookup(),
$languageFallbackChain,
$context->getLanguage()
);
}
/**
* Special page handling where we want to display meaningful link labels instead of just the items ID.
* This is only handling special pages right now and gets disabled in normal pages.
* @see https://www.mediawiki.org/wiki/Manual:Hooks/LinkBegin
*
* @param DummyLinker $skin
* @param Title $target
* @param string $html
* @param array $customAttribs
* @param string $query
* @param array $options
* @param mixed $ret
* @return bool true
*/
public static function onLinkBegin( $skin, $target, &$html, array &$customAttribs, &$query,
&$options, &$ret
) {
$context = RequestContext::getMain();
if ( !$context->hasTitle() ) {
// Short-circuit this hook if no title is
// set in the main context (T131176)
return true;
}
$handler = self::newFromGlobalState();
$handler->doOnLinkBegin( $target, $html, $customAttribs, $context );
return true;
}
/**
* @param EntityIdLookup $entityIdLookup
* @param TermLookup $termLookup
* @param EntityNamespaceLookup $entityNamespaceLookup
* @param LanguageFallbackChain $languageFallback
* @param Language $pageLanguage
*
* @todo: Would be nicer to take a LabelDescriptionLookup instead of TermLookup + FallbackChain.
*/
public function __construct(
EntityIdLookup $entityIdLookup,
TermLookup $termLookup,
EntityNamespaceLookup $entityNamespaceLookup,
LanguageFallbackChain $languageFallback,
Language $pageLanguage
) {
$this->entityIdLookup = $entityIdLookup;
$this->termLookup = $termLookup;
$this->entityNamespaceLookup = $entityNamespaceLookup;
$this->languageFallback = $languageFallback;
$this->pageLanguage = $pageLanguage;
}
/**
* @param Title $target
* @param string &$html
* @param array &$customAttribs
* @param RequestContext $context
*/
public function doOnLinkBegin( Title $target, &$html, array &$customAttribs, RequestContext $context ) {
$out = $context->getOutput();
if ( !$this->entityNamespaceLookup->isEntityNamespace( $target->getNamespace() ) ) {
return;
}
// Only continue on pages with edit summaries (histories / diffs) or on special pages.
// Don't run this code when accessing it through the api (eg. for parsing) as the title is
// set to a special page dummy in api.php, see https://phabricator.wikimedia.org/T111346
if ( defined( 'MW_API' ) || !$this->shouldConvert( $out->getTitle(), $context ) ) {
return;
}
$targetText = $target->getText();
// Handle "fake" titles for new entities as generated by
// EditEntity::getContextForEditFilter(). For instance, a link to Property:NewProperty
// would be replaced by a link to Special:NewProperty. This is useful in logs,
// to indicate that the logged action occurred while creating an entity.
if ( SpecialPageFactory::exists( $targetText ) ) {
$target = Title::makeTitle( NS_SPECIAL, $targetText );
$html = Linker::linkKnown( $target );
return;
}
if ( !$target->exists() ) {
// The link points to a non-existing item.
return;
}
// if custom link text is given, there is no point in overwriting it
// but not if it is similar to the plain title
if ( $html !== null && $target->getFullText() !== $html ) {
return;
}
$entityId = $this->entityIdLookup->getEntityIdForTitle( $target );
if ( !$entityId ) {
return;
}
// @todo: this re-implements the logic in LanguageFallbackLabelDescriptionLookup,
// as that didn't support descriptions back when this code was written.
// NOTE: keep in sync with with fallback languages in LabelPrefetchHookHandler::newFromGlobalState
try {
$labels = $this->termLookup->getLabels( $entityId, $this->languageFallback->getFetchLanguageCodes() );
$descriptions = $this->termLookup->getDescriptions( $entityId, $this->languageFallback->getFetchLanguageCodes() );
} catch ( StorageException $ex ) {
// This shouldn't happen if $target->exists() return true!
return;
}
$labelData = $this->getPreferredTerm( $labels );
$descriptionData = $this->getPreferredTerm( $descriptions );
$html = $this->getHtml( $target, $labelData );
$customAttribs['title'] = $this->getTitleAttribute(
$target,
$labelData,
$descriptionData
);
// add wikibase styles in all cases, so we can format the link properly:
$out->addModuleStyles( array( 'wikibase.common' ) );
}
/**
* @param array $termsByLanguage
*
* @return string[]|null
*/
private function getPreferredTerm( array $termsByLanguage ) {
if ( empty( $termsByLanguage ) ) {
return null;
}
return $this->languageFallback->extractPreferredValueOrAny(
$termsByLanguage
);
}
/**
* Whether we should try to convert links on this page.
* This caches that result within a static variable,
* thus it can't change (except in phpunit tests).
*
* @param Title|null $currentTitle
* @param RequestContext $context
* @return bool
*/
private function shouldConvert( Title $currentTitle = null, RequestContext $context ) {
static $shouldConvert = null;
if ( $shouldConvert !== null && !defined( 'MW_PHPUNIT_TEST' ) ) {
return $shouldConvert;
}
$actionName = Action::getActionName( $context );
// This is how Article detects diffs
$isDiff = $actionName === 'view' && $context->getRequest()->getCheck( 'diff' );
// Only continue on pages with edit summaries (histories / diffs) or on special pages.
if (
( $currentTitle === null || !$currentTitle->isSpecialPage() )
&& $actionName !== 'history'
&& !$isDiff
) {
// Note: this may not work right with special page transclusion. If $out->getTitle()
// doesn't return the transcluded special page's title, the transcluded text will
// not have entity IDs resolved to labels.
$shouldConvert = false;
return false;
}
$shouldConvert = true;
return true;
}
/**
* @param string[]|null $termData A term record as returned by
* LanguageFallbackChain::extractPreferredValueOrAny(),
* containing the 'value' and 'language' fields, or null
* or an empty array.
*
* @see LanguageFallbackChain::extractPreferredValueOrAny
*
* @return array list( string $text, Language $language )
*/
private function extractTextAndLanguage( array $termData = null ) {
if ( $termData ) {
return array(
$termData['value'],
Language::factory( $termData['language'] )
);
} else {
return array(
'',
$this->pageLanguage
);
}
}
private function getHtml( Title $title, array $labelData = null ) {
/** @var Language $labelLang */
list( $labelText, $labelLang ) = $this->extractTextAndLanguage( $labelData );
$idHtml = '<span class="wb-itemlink-id">'
. wfMessage(
'wikibase-itemlink-id-wrapper',
$title->getText()
)->inContentLanguage()->escaped()
. '</span>';
$labelHtml = '<span class="wb-itemlink-label"'
. ' lang="' . htmlspecialchars( $labelLang->getHtmlCode() ) . '"'
. ' dir="' . htmlspecialchars( $labelLang->getDir() ) . '">'
. htmlspecialchars( $labelText )
. '</span>';
return '<span class="wb-itemlink">'
. wfMessage( 'wikibase-itemlink' )->rawParams(
$labelHtml,
$idHtml
)->inContentLanguage()->escaped()
. '</span>';
}
private function getTitleAttribute( Title $title, array $labelData = null, array $descriptionData = null ) {
/** @var Language $labelLang */
/** @var Language $descriptionLang */
list( $labelText, $labelLang ) = $this->extractTextAndLanguage( $labelData );
list( $descriptionText, $descriptionLang ) = $this->extractTextAndLanguage( $descriptionData );
// Set title attribute for constructed link, and make tricks with the directionality to get it right
$titleText = ( $labelText !== '' )
? $labelLang->getDirMark() . $labelText
. $this->pageLanguage->getDirMark()
: $title->getPrefixedText();
$descriptionText = $descriptionLang->getDirMark() . $descriptionText
. $this->pageLanguage->getDirMark();
return ( $descriptionText !== '' ) ?
wfMessage(
'wikibase-itemlink-title',
$titleText,
$descriptionText
)->inContentLanguage()->text() :
$titleText; // no description, just display the title then
}
}