jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/362215 )

Change subject: Use language fallback when showing descriptions in search 
results
......................................................................


Use language fallback when showing descriptions in search results

At the beginning, there was just a todo comment in the code.
A new hook handler has been created to make it testable.

Change-Id: I45039f579054c0130162c9e14c87c94dfe3cb1c3
---
M repo/Wikibase.hooks.php
M repo/Wikibase.php
A repo/includes/Hooks/ShowSearchHitHandler.php
A repo/tests/phpunit/includes/Hooks/ShowSearchHitHandlerTest.php
4 files changed, 404 insertions(+), 49 deletions(-)

Approvals:
  jenkins-bot: Verified
  Thiemo Mättig (WMDE): Looks good to me, approved



diff --git a/repo/Wikibase.hooks.php b/repo/Wikibase.hooks.php
index ced38e0..d6563a1 100644
--- a/repo/Wikibase.hooks.php
+++ b/repo/Wikibase.hooks.php
@@ -29,7 +29,6 @@
 use StubUserLang;
 use Title;
 use User;
-use Wikibase\DataModel\Term\DescriptionsProvider;
 use Wikibase\Lib\AutoCommentFormatter;
 use Wikibase\Lib\Store\Sql\EntityChangeLookup;
 use Wikibase\Repo\Content\EntityHandler;
@@ -540,53 +539,6 @@
                }
 
                return true;
-       }
-
-       /**
-        * Format the output when the search result contains entities
-        *
-        * @param SpecialSearch $searchPage
-        * @param SearchResult $result
-        * @param array $terms
-        * @param string &$link
-        * @param string &$redirect
-        * @param string &$section
-        * @param string &$extract
-        * @param string &$score
-        * @param string &$size
-        * @param string &$date
-        * @param string &$related
-        * @param string &$html
-        */
-       public static function onShowSearchHit( SpecialSearch $searchPage, 
SearchResult $result, $terms,
-               &$link, &$redirect, &$section, &$extract, &$score, &$size, 
&$date, &$related, &$html
-       ) {
-               $entityContentFactory = 
WikibaseRepo::getDefaultInstance()->getEntityContentFactory();
-
-               $title = $result->getTitle();
-               $contentModel = $title->getContentModel();
-
-               if ( $entityContentFactory->isEntityContentModel( $contentModel 
) ) {
-                       /** @var EntityContent $content */
-                       $page = WikiPage::factory( $title );
-                       $content = $page->getContent();
-
-                       if ( $content && !$content->isRedirect() ) {
-                               $entity = $content->getEntity();
-                               $languageCode = 
$searchPage->getLanguage()->getCode(); // TODO: language fallback!
-
-                               if ( $entity instanceof DescriptionsProvider &&
-                                       
$entity->getDescriptions()->hasTermForLanguage( $languageCode )
-                               ) {
-                                       $description = 
$entity->getDescriptions()->getByLanguage( $languageCode )->getText();
-                                       $attr = [ 'class' => 
'wb-itemlink-description' ];
-                                       $link .= $searchPage->msg( 
'colon-separator' )->text();
-                                       $link .= Html::element( 'span', $attr, 
$description );
-                               }
-                       }
-
-                       $extract = ''; // TODO: set this to something useful.
-               }
        }
 
        /**
diff --git a/repo/Wikibase.php b/repo/Wikibase.php
index ca66bbd..76a74c9 100644
--- a/repo/Wikibase.php
+++ b/repo/Wikibase.php
@@ -987,7 +987,7 @@
        $wgHooks['PageHistoryLineEnding'][] = 
'Wikibase\RepoHooks::onPageHistoryLineEnding';
        $wgHooks['ApiCheckCanExecute'][] = 
'Wikibase\RepoHooks::onApiCheckCanExecute';
        $wgHooks['SetupAfterCache'][] = 'Wikibase\RepoHooks::onSetupAfterCache';
-       $wgHooks['ShowSearchHit'][] = 'Wikibase\RepoHooks::onShowSearchHit';
+       $wgHooks['ShowSearchHit'][] = 
'Wikibase\Repo\Hooks\ShowSearchHitHandler::onShowSearchHit';
        $wgHooks['ShowSearchHitTitle'][] = 
'Wikibase\RepoHooks::onShowSearchHitTitle';
        $wgHooks['TitleGetRestrictionTypes'][] = 
'Wikibase\RepoHooks::onTitleGetRestrictionTypes';
        $wgHooks['AbuseFilter-contentToString'][] = 
'Wikibase\RepoHooks::onAbuseFilterContentToString';
diff --git a/repo/includes/Hooks/ShowSearchHitHandler.php 
b/repo/includes/Hooks/ShowSearchHitHandler.php
new file mode 100644
index 0000000..a60b8f0
--- /dev/null
+++ b/repo/includes/Hooks/ShowSearchHitHandler.php
@@ -0,0 +1,147 @@
+<?php
+
+namespace Wikibase\Repo\Hooks;
+
+use Html;
+use IContextSource;
+use SearchResult;
+use SpecialSearch;
+use Title;
+use Wikibase\DataModel\Entity\EntityDocument;
+use Wikibase\DataModel\Services\Lookup\EntityLookup;
+use Wikibase\DataModel\Term\DescriptionsProvider;
+use Wikibase\LanguageFallbackChain;
+use Wikibase\Repo\Content\EntityContentFactory;
+use Wikibase\Repo\WikibaseRepo;
+use Wikibase\Store\EntityIdLookup;
+
+/**
+ * Handler to format entities in the search results
+ *
+ * @license GPL-2.0+
+ * @author Matěj Suchánek
+ * @author Daniel Kinzler
+ */
+class ShowSearchHitHandler {
+
+       /**
+        * @var EntityContentFactory
+        */
+       private $entityContentFactory;
+
+       /**
+        * @var LanguageFallbackChain
+        */
+       private $languageFallbackChain;
+
+       /**
+        * @var EntityIdLookup
+        */
+       private $entityIdLookup;
+
+       /**
+        * @var EntityLookup
+        */
+       private $entityLookup;
+
+       public function __construct(
+               EntityContentFactory $entityContentFactory,
+               LanguageFallbackChain $languageFallbackChain,
+               EntityIdLookup $entityIdLookup,
+               EntityLookup $entityLookup
+       ) {
+               $this->entityContentFactory = $entityContentFactory;
+               $this->languageFallbackChain = $languageFallbackChain;
+               $this->entityIdLookup = $entityIdLookup;
+               $this->entityLookup = $entityLookup;
+       }
+
+       /**
+        * @param IContextSource $context
+        * @return self
+        */
+       private static function newFromGlobalState( IContextSource $context ) {
+               $wikibaseRepo = WikibaseRepo::getDefaultInstance();
+               $languageFallbackChainFactory = 
$wikibaseRepo->getLanguageFallbackChainFactory();
+
+               return new self(
+                       $wikibaseRepo->getEntityContentFactory(),
+                       $languageFallbackChainFactory->newFromContext( $context 
),
+                       $wikibaseRepo->getEntityIdLookup(),
+                       $wikibaseRepo->getEntityLookup()
+               );
+       }
+
+       /**
+        * Format the output when the search result contains entities
+        * @see https://www.mediawiki.org/wiki/Manual:Hooks/ShowSearchHit
+        * @see doShowSearchHit
+        *
+        * @param SpecialSearch $searchPage
+        * @param SearchResult $result
+        * @param array $terms
+        * @param string &$link
+        * @param string &$redirect
+        * @param string &$section
+        * @param string &$extract
+        * @param string &$score
+        * @param string &$size
+        * @param string &$date
+        * @param string &$related
+        * @param string &$html
+        */
+       public static function onShowSearchHit( SpecialSearch $searchPage, 
SearchResult $result, array $terms,
+               &$link, &$redirect, &$section, &$extract, &$score, &$size, 
&$date, &$related, &$html
+       ) {
+               $self = self::newFromGlobalState( $searchPage->getContext() );
+               $self->doShowSearchHit( $searchPage, $result, $terms, $link, 
$redirect, $section, $extract,
+                       $score, $size, $date, $related, $html );
+       }
+
+       public function doShowSearchHit( SpecialSearch $searchPage, 
SearchResult $result, array $terms,
+               &$link, &$redirect, &$section, &$extract, &$score, &$size, 
&$date, &$related, &$html
+       ) {
+               $title = $result->getTitle();
+               $contentModel = $title->getContentModel();
+
+               if ( !$this->entityContentFactory->isEntityContentModel( 
$contentModel ) ) {
+                       return;
+               }
+
+               $extract = ''; // TODO: set this to something useful.
+
+               $entity = $this->getEntity( $title );
+               if ( !( $entity instanceof DescriptionsProvider ) ) {
+                       return;
+               }
+
+               $terms = $entity->getDescriptions()->toTextArray();
+               $termData = 
$this->languageFallbackChain->extractPreferredValue( $terms );
+               if ( $termData !== null ) {
+                       $this->addDescription( $link, $termData, $searchPage );
+               }
+       }
+
+       private function addDescription( &$link, array $termData, SpecialSearch 
$searchPage ) {
+               $description = $termData['value'];
+               $attr = [ 'class' => 'wb-itemlink-description' ];
+               if ( $termData['language'] !== 
$searchPage->getLanguage()->getCode() ) {
+                       $attr += [ 'dir' => 'auto', 'lang' => wfBCP47( 
$termData['language'] ) ];
+               }
+               $link .= $searchPage->msg( 'colon-separator' )->escaped();
+               $link .= Html::element( 'span', $attr, $description );
+       }
+
+       /**
+        * @param Title $title
+        * @return EntityDocument|null
+        */
+       private function getEntity( Title $title ) {
+               $entityId = $this->entityIdLookup->getEntityIdForTitle( $title 
);
+               if ( $entityId ) {
+                       return $this->entityLookup->getEntity( $entityId );
+               }
+               return null;
+       }
+
+}
diff --git a/repo/tests/phpunit/includes/Hooks/ShowSearchHitHandlerTest.php 
b/repo/tests/phpunit/includes/Hooks/ShowSearchHitHandlerTest.php
new file mode 100644
index 0000000..4682852
--- /dev/null
+++ b/repo/tests/phpunit/includes/Hooks/ShowSearchHitHandlerTest.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace Wikibase\Repo\Tests\Hooks;
+
+use Language;
+use MediaWikiTestCase;
+use Message;
+use SearchResult;
+use SpecialSearch;
+use Title;
+use Wikibase\DataModel\Entity\Item;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Services\Lookup\EntityLookup;
+use Wikibase\DataModel\Term\TermList;
+use Wikibase\LanguageFallbackChain;
+use Wikibase\Repo\Content\EntityContentFactory;
+use Wikibase\Repo\Hooks\ShowSearchHitHandler;
+use Wikibase\Store\EntityIdLookup;
+
+/**
+ * @covers Wikibase\Repo\Hooks\ShowSearchHitHandler
+ *
+ * @group Wikibase
+ *
+ * @license GPL-2.0+
+ * @author Matěj Suchánek
+ */
+class ShowSearchHitHandlerTest extends MediaWikiTestCase {
+
+       const NON_ENTITY_TITLE = 'foo';
+       const DE_DESCRIPTION_ITEM_ID = 'Q1';
+       const FALLBACK_DESCRIPTION_ITEM_ID = 'Q2';
+       const NO_FALLBACK_DESCRIPTION_ITEM_ID = 'Q3';
+       const EMPTY_ITEM_ID = 'Q4';
+
+       /**
+        * @return SpecialSearch
+        */
+       private function getSearchPage() {
+               $searchPage = $this->getMockBuilder( SpecialSearch::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $searchPage->method( 'msg' )
+                       ->with( $this->equalTo( 'colon-separator' ) )
+                       ->will( $this->returnCallback( function ( $key ) {
+                               $msg = $this->getMockBuilder( Message::class )
+                                       ->disableOriginalConstructor()
+                                       ->getMock();
+                               $msg->method( 'escaped' )->willReturn( ': ' );
+
+                               return $msg;
+                       } ) );
+               $searchPage->method( 'getLanguage' )
+                       ->willReturn( Language::factory( 'de' ) );
+
+               return $searchPage;
+       }
+
+       /**
+        * @param string $title
+        * @return SearchResult
+        */
+       private function getSearchResult( $title ) {
+               $mockTitle = $this->getMock( Title::class );
+               $mockTitle->method( 'getText' )->willReturn( $title );
+               // hack: content model equals title/id
+               $mockTitle->method( 'getContentModel' )->willReturn( $title );
+
+               $searchResult = $this->getMockBuilder( SearchResult::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $searchResult->method( 'getTitle' )->willReturn( $mockTitle );
+
+               return $searchResult;
+       }
+
+       /**
+        * @see LanguageFallbackChain::extractPreferredValue
+        * @param array $terms
+        * @return array|null
+        */
+       private function extractPreferredValue( array $terms ) {
+               if ( array_key_exists( 'de', $terms ) ) {
+                       return [
+                               'value' => $terms['de'],
+                               'language' => 'de',
+                       ];
+               } elseif ( array_key_exists( 'en', $terms ) ) {
+                       return [
+                               'value' => $terms['en'],
+                               'language' => 'en',
+                       ];
+               }
+
+               return null;
+       }
+
+       /**
+        * @return LanguageFallbackChain
+        */
+       private function getLanguageFallbackChain() {
+               $languageFallbackChain = $this->getMockBuilder( 
LanguageFallbackChain::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $languageFallbackChain->method( 'extractPreferredValue' )
+                       ->will( $this->returnCallback( function ( array $terms 
) {
+                               return $this->extractPreferredValue( $terms );
+                       } ) );
+
+               return $languageFallbackChain;
+       }
+
+       /**
+        * @return EntityContentFactory
+        */
+       private function getEntityContentFactory() {
+               $entityContentFactory = $this->getMockBuilder( 
EntityContentFactory::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $entityContentFactory->method( 'isEntityContentModel' )
+                       ->will( $this->returnCallback( function ( $contentModel 
) {
+                               // hack: content model equals title/id
+                               return $contentModel !== self::NON_ENTITY_TITLE;
+                       } ) );
+
+               return $entityContentFactory;
+       }
+
+       /**
+        * @return EntityIdLookup
+        */
+       private function getEntityIdLookup() {
+               $entityIdLookup = $this->getMock( EntityIdLookup::class );
+               $entityIdLookup->method( 'getEntityIdForTitle' )
+                       ->will( $this->returnCallback( function ( Title $title 
) {
+                               return new ItemId( $title->getText() );
+                       } ) );
+
+               return $entityIdLookup;
+       }
+
+       /**
+        * @param ItemId $itemId
+        * @return string[]
+        */
+       private function getDescriptionsArray( ItemId $itemId ) {
+               $terms = [];
+               switch ( $itemId->getSerialization() ) {
+                       case self::DE_DESCRIPTION_ITEM_ID:
+                               $terms['de'] = '<b>German description</b>';
+                       case self::FALLBACK_DESCRIPTION_ITEM_ID:
+                               $terms['en'] = 'fallback description';
+                       case self::NO_FALLBACK_DESCRIPTION_ITEM_ID:
+                               $terms['fr'] = 'unused description';
+                       case self::EMPTY_ITEM_ID:
+                               break;
+               }
+               return $terms;
+       }
+
+       /**
+        * @param ItemId $itemId
+        * @return Item $item
+        */
+       private function getEntity( ItemId $itemId ) {
+               $termList = $this->getMockBuilder( TermList::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $termList->method( 'toTextArray' )
+                       ->willReturn( $this->getDescriptionsArray( $itemId ) );
+
+               $item = $this->getMockBuilder( Item::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $item->method( 'getDescriptions' )->willReturn( $termList );
+
+               return $item;
+       }
+
+       /**
+        * @return EntityLookup
+        */
+       private function getEntityLookup() {
+               $entityLookup = $this->getMock( EntityLookup::class );
+               $entityLookup->method( 'getEntity' )
+                       ->will( $this->returnCallback( function ( ItemId 
$itemId ) {
+                               return $this->getEntity( $itemId );
+                       } ) );
+
+               return $entityLookup;
+       }
+
+       /**
+        * @dataProvider showSearchHitProvider
+        */
+       public function testShowSearchHit( $title, $expected, $newExtract ) {
+               $searchPage = $this->getSearchPage();
+               $searchResult = $this->getSearchResult( $title );
+               $handler = new ShowSearchHitHandler(
+                       $this->getEntityContentFactory(),
+                       $this->getLanguageFallbackChain(),
+                       $this->getEntityIdLookup(),
+                       $this->getEntityLookup()
+               );
+               $link = '<a>link</a>';
+               $extract = '<span>extract</span>';
+               $redirect = $section = $score = $size = $date = $related = 
$html = '';
+               $handler->doShowSearchHit(
+                       $searchPage,
+                       $searchResult,
+                       [],
+                       $link,
+                       $redirect,
+                       $section,
+                       $extract,
+                       $score,
+                       $size,
+                       $date,
+                       $related,
+                       $html
+               );
+               $this->assertEquals( $expected, $link );
+               $this->assertEquals( $newExtract, $extract );
+       }
+
+       public function showSearchHitProvider() {
+               return [
+                       'non-entity' => [
+                               self::NON_ENTITY_TITLE,
+                               '<a>link</a>',
+                               '<span>extract</span>',
+                       ],
+                       'German description' => [
+                               self::DE_DESCRIPTION_ITEM_ID,
+                               '<a>link</a>: <span 
class="wb-itemlink-description">&lt;b>German description&lt;/b></span>',
+                               '',
+                       ],
+                       'fallback description' => [
+                               self::FALLBACK_DESCRIPTION_ITEM_ID,
+                               '<a>link</a>: <span 
class="wb-itemlink-description" dir="auto" lang="en">fallback 
description</span>',
+                               '',
+                       ],
+                       'no available fallback description' => [
+                               self::NO_FALLBACK_DESCRIPTION_ITEM_ID,
+                               '<a>link</a>',
+                               '',
+                       ],
+                       'description-less item' => [
+                               self::EMPTY_ITEM_ID,
+                               '<a>link</a>',
+                               '',
+                       ],
+               ];
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/362215
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I45039f579054c0130162c9e14c87c94dfe3cb1c3
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Matěj Suchánek <[email protected]>
Gerrit-Reviewer: Fomafix <[email protected]>
Gerrit-Reviewer: Jonas Kress (WMDE) <[email protected]>
Gerrit-Reviewer: Matěj Suchánek <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: WMDE-leszek <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to