Hoo man has uploaded a new change for review. https://gerrit.wikimedia.org/r/304162
Change subject: Add Wikibase item links into the toolbox on article placeholders ...................................................................... Add Wikibase item links into the toolbox on article placeholders Bug: T126873 Change-Id: I3ac0c78e6fd05cc042757a874e93cb6b8b614b17 --- M extension.json A includes/BaseTemplateToolboxHookHandler.php A tests/phpunit/includes/BaseTemplateToolboxHookHandlerTest.php 3 files changed, 356 insertions(+), 0 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/ArticlePlaceholder refs/changes/62/304162/1 diff --git a/extension.json b/extension.json index d9dd0ba..75ebe1f 100644 --- a/extension.json +++ b/extension.json @@ -62,6 +62,9 @@ "remoteExtPath": "ArticlePlaceholder/modules" }, "Hooks": { + "BaseTemplateToolbox": [ + "ArticlePlaceholder\\BaseTemplateToolboxHookHandler::onBaseTemplateToolbox" + ], "ScribuntoExternalLibraries": [ "ArticlePlaceholder\\Hooks::onScribuntoExternalLibraries" ], diff --git a/includes/BaseTemplateToolboxHookHandler.php b/includes/BaseTemplateToolboxHookHandler.php new file mode 100644 index 0000000..42a962b --- /dev/null +++ b/includes/BaseTemplateToolboxHookHandler.php @@ -0,0 +1,142 @@ +<?php + +namespace ArticlePlaceholder; + +use BaseTemplate; +use Exception; +use SpecialPage; +use SpecialPageFactory; +use Title; +use WebRequest; +use Wikibase\Client\RepoLinker; +use Wikibase\Client\WikibaseClient; +use Wikibase\DataModel\Entity\EntityIdParser; +use Wikibase\DataModel\Entity\ItemId; +use Wikibase\DataModel\Services\Lookup\EntityLookup; + +/** + * Add Wikibase item link in toolbox for placeholders: Handler for the "BaseTemplateToolbox" hook. + * + * @license GPL-2.0+ + * @author Marius Hoch < h...@online.de > + */ +class BaseTemplateToolboxHookHandler { + + /** + * @var EntityIdParser + */ + private $entityIdParser; + + /** + * @var RepoLinker + */ + private $repoLinker; + + /** + * @var EntityLookup + */ + private $entityLookup; + + /** + * @param SpecialPage $specialPage + * + * @return self + */ + private static function newFromGlobalState() { + $wikibaseClient = WikibaseClient::getDefaultInstance(); + + return new self( + $wikibaseClient->getEntityIdParser(), + $wikibaseClient->newRepoLinker(), + $wikibaseClient->getStore()->getEntityLookup() + ); + } + + /** + * @param EntityIdParser $entityIdParser + * @param RepoLinker $repoLinker + * @param EntityLookup $entityLookup + */ + public function __construct( + EntityIdParser $entityIdParser, + RepoLinker $repoLinker, + EntityLookup $entityLookup + ) { + $this->entityIdParser = $entityIdParser; + $this->repoLinker = $repoLinker; + $this->entityLookup = $entityLookup; + } + + /** + * @param BaseTemplate $baseTemplate + * @param array &$toolbox + */ + public static function onBaseTemplateToolbox( BaseTemplate $baseTemplate, array &$toolbox ) { + // Return early (for performance reasons) in case we're not on + // Special:AboutTopic (even before calling newFromGlobalState) + $title = $baseTemplate->getSkin()->getTitle(); + + if ( $title->getNamespace() !== NS_SPECIAL ) { + return; + } + + $canonicalSpecialPageName = SpecialPageFactory::resolveAlias( $title->getText() )[0]; + if ( $canonicalSpecialPageName !== 'AboutTopic' ) { + return; + } + + $self = self::newFromGlobalState(); + $self->doBaseTemplateToolbox( $baseTemplate, $toolbox ); + } + + /** + * @param BaseTemplate $baseTemplate + * @param array &$toolbox + */ + public function doBaseTemplateToolbox( BaseTemplate $baseTemplate, array &$toolbox ) { + $itemId = $this->getItemId( + $baseTemplate->getSkin()->getTitle(), + $baseTemplate->getSkin()->getRequest() + ); + + if ( $itemId && $this->entityLookup->hasEntity( $itemId ) ) { + // Duplicated from Wikibase\ClientHooks::onBaseTemplateToolbox + $toolbox['wikibase'] = array( + 'text' => $baseTemplate->getMsg( 'wikibase-dataitem' )->text(), + 'href' => $this->repoLinker->getEntityUrl( $itemId ), + 'id' => 't-wikibase' + ); + } + } + + /** + * @param Title $title + * @param WebRequest $webRequest + * + * @return ItemId + */ + private function getItemId( Title $title, WebRequest $webRequest ) { + $idSerialization = $webRequest->getText( + 'entityid', + SpecialPageFactory::resolveAlias( $title->getText() )[1] + ); + + if ( !$idSerialization ) { + return null; + } + + try { + $id = $this->entityIdParser->parse( $idSerialization ); + if ( !( $id instanceof ItemId ) ) { + return null; + } + + return $id; + } catch ( Exception $ex ) { + // Ignore + } + + return null; + } + +} diff --git a/tests/phpunit/includes/BaseTemplateToolboxHookHandlerTest.php b/tests/phpunit/includes/BaseTemplateToolboxHookHandlerTest.php new file mode 100644 index 0000000..88a5991 --- /dev/null +++ b/tests/phpunit/includes/BaseTemplateToolboxHookHandlerTest.php @@ -0,0 +1,211 @@ +<?php + +namespace ArticlePlaceholder\Tests; + +use ArticlePlaceholder\BaseTemplateToolboxHookHandler; +use BaseTemplate; +use MediaWikiTestCase; +use Skin; +use Title; +use WebRequest; +use Wikibase\Client\WikibaseClient; +use Wikibase\DataModel\Entity\Item; +use Wikibase\DataModel\Entity\ItemId; +use Wikibase\Test\MockClientStore; + +/** + * @group ArticlePlaceholder + * + * @covers ArticlePlaceholder\BaseTemplateToolboxHookHandler + * + * @license GPL-2.0+ + * @author Marius Hoch < h...@online.de > + */ +class BaseTemplateToolboxHookHandlerTest extends MediaWikiTestCase { + + public function setUp() { + parent::setUp(); + + $clientStore = new MockClientStore(); + $wikibaseClient = WikibaseClient::getDefaultInstance(); + $wikibaseClient->overrideStore( $clientStore ); + + $mockRepository = $clientStore->getEntityRevisionLookup(); + $item = new Item( new ItemId( 'Q2013' ) ); + + $mockRepository->putEntity( $item ); + } + + public function tearDown() { + parent::tearDown(); + + WikibaseClient::getDefaultInstance( 'reset' ); + } + + public function testOnBaseTemplateToolbox_wrongNamespace() { + $title = $this->getMock( Title::class ); + $title->expects( $this->once() ) + ->method( 'getNamespace' ) + ->will( $this->returnValue( 2 ) ); + + // We abort early, before Title::getText is ever called + $title->expects( $this->never() ) + ->method( 'getText' ); + + $baseTemplate = $this->getBaseTemplate( $title ); + + $arr = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $arr ); + + $this->assertSame( [], $arr ); + } + + public function testOnBaseTemplateToolbox_wrongSpecialPageName() { + $title = $this->getMock( Title::class ); + $title->expects( $this->once() ) + ->method( 'getNamespace' ) + ->will( $this->returnValue( NS_SPECIAL ) ); + + // Title::getText is only called once, because we abort after checking the name + $title->expects( $this->once() ) + ->method( 'getText' ) + ->will( $this->returnValue( 'Preferences' ) ); + + $baseTemplate = $this->getBaseTemplate( $title ); + + $arr = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $arr ); + + $this->assertSame( [], $arr ); + } + + public function testOnBaseTemplateToolbox_invalidItemId() { + $title = $this->getAboutTopicTitle( 'not-an-item-id' ); + + $baseTemplate = $this->getBaseTemplate( $title ); + + $arr = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $arr ); + + $this->assertSame( [], $arr ); + } + + public function testOnBaseTemplateToolbox_unknownItemId() { + $title = $this->getAboutTopicTitle( 'Q42' ); + + $baseTemplate = $this->getBaseTemplate( $title ); + + $arr = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $arr ); + + $this->assertSame( [], $arr ); + } + + public function testOnBaseTemplateToolbox_subPage() { + $title = $this->getAboutTopicTitle( 'Q2013' ); + + $baseTemplate = $this->getBaseTemplate( $title ); + + $toolbox = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $toolbox ); + + $this->assertAmendedToolbox( $toolbox ); + } + + public function testOnBaseTemplateToolbox_entityidParam() { + $title = $this->getAboutTopicTitle(); + + $baseTemplate = $this->getBaseTemplate( $title, 'Q2013' ); + + $toolbox = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $toolbox ); + + $this->assertAmendedToolbox( $toolbox ); + } + + public function testOnBaseTemplateToolbox_entityidParamOverridesSubPage() { + // This matches the special page's behaviour. + $title = $this->getAboutTopicTitle( 'Q123' ); + + $baseTemplate = $this->getBaseTemplate( $title, 'Q2013' ); + + $toolbox = []; + BaseTemplateToolboxHookHandler::onBaseTemplateToolbox( $baseTemplate, $toolbox ); + + $this->assertAmendedToolbox( $toolbox ); + } + + private function assertAmendedToolbox( $toolbox ) { + $itemId = new ItemId( 'Q2013' ); + $text = wfMessage( 'wikibase-dataitem' )->text(); + $href = WikibaseClient::getDefaultInstance()->newRepoLinker()->getEntityUrl( $itemId ); + + $this->assertSame( + [ + 'wikibase' => [ + 'text' => $text, + 'href' => $href, + 'id' => 't-wikibase' + ] + ], + $toolbox + ); + } + + private function getAboutTopicTitle( $subPage = null ) { + $titleText = 'AboutTopic'; + + if ( $subPage ) { + $titleText .= '/' . $subPage; + } + + $title = $this->getMock( Title::class ); + $title->expects( $this->once() ) + ->method( 'getNamespace' ) + ->will( $this->returnValue( NS_SPECIAL ) ); + + $title->expects( $this->exactly( 2 ) ) + ->method( 'getText' ) + ->will( $this->returnValue( $titleText ) ); + + return $title; + } + + private function getBaseTemplate( Title $title, $itemIdParam = null ) { + $request = $this->getMock( WebRequest::class ); + $request->expects( $this->any() ) + ->method( 'getText' ) + ->will( $this->returnCallback( function( $name, $default ) use ( $itemIdParam ) { + $this->assertSame( 'entityid', $name ); + + return $itemIdParam ?: $default; + } ) ); + + $skin = $this->getMockBuilder( Skin::class ) + ->setMethods( [ 'getTitle', 'getRequest' ] ) + ->getMockForAbstractClass(); + + $skin->expects( $this->any() ) + ->method( 'getTitle' ) + ->will( $this->returnValue( $title ) ); + + $skin->expects( $this->any() ) + ->method( 'getRequest' ) + ->will( $this->returnValue( $request ) ); + + $skin->expects( $this->any() ) + ->method( 'getRequest' ) + ->will( $this->returnValue( $request ) ); + + $baseTemplate = $this->getMockBuilder( BaseTemplate::class ) + ->setMethods( [ 'getSkin' ] ) + ->getMockForAbstractClass(); + + $baseTemplate->expects( $this->any() ) + ->method( 'getSkin' ) + ->will( $this->returnValue( $skin ) ); + + return $baseTemplate; + } + +} -- To view, visit https://gerrit.wikimedia.org/r/304162 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I3ac0c78e6fd05cc042757a874e93cb6b8b614b17 Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/ArticlePlaceholder Gerrit-Branch: master Gerrit-Owner: Hoo man <h...@online.de> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits