Skip to content

Commit 574e5a1

Browse files
authored
Rewrite item link with page sitelink (#24)
1 parent 30d9a7c commit 574e5a1

File tree

7 files changed

+232
-50
lines changed

7 files changed

+232
-50
lines changed

phpstan-baseline.neon

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,13 @@
11
parameters:
22
ignoreErrors:
33
-
4-
message: '#^Call to method addHTML\(\) on an unknown class OutputPage\.$#'
5-
identifier: class.notFound
4+
message: '#^Method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:rewriteLinkForNonEntityResult\(\) never assigns null to &\$titleSnippet so it can be removed from the by\-ref type\.$#'
5+
identifier: parameterByRef.unusedType
66
count: 1
77
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
88

99
-
10-
message: '#^Call to method addModuleStyles\(\) on an unknown class OutputPage\.$#'
11-
identifier: class.notFound
12-
count: 1
13-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
14-
15-
-
16-
message: '#^Call to method getNamespace\(\) on an unknown class Title\.$#'
17-
identifier: class.notFound
18-
count: 1
19-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
20-
21-
-
22-
message: '#^Call to static method element\(\) on an unknown class Html\.$#'
23-
identifier: class.notFound
24-
count: 1
25-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
26-
27-
-
28-
message: '#^Constant WB_NS_ITEM not found\.$#'
29-
identifier: constant.notFound
30-
count: 1
31-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
32-
33-
-
34-
message: '#^Parameter \$output of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onSpecialSearchResultsAppend\(\) has invalid type OutputPage\.$#'
35-
identifier: class.notFound
36-
count: 1
37-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
38-
39-
-
40-
message: '#^Parameter \$specialSearch of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onShowSearchHitTitle\(\) has invalid type SpecialSearch\.$#'
41-
identifier: class.notFound
42-
count: 1
43-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
44-
45-
-
46-
message: '#^Parameter \$specialSearch of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onSpecialSearchResultsAppend\(\) has invalid type SpecialSearch\.$#'
47-
identifier: class.notFound
48-
count: 1
49-
path: src/EntryPoints/WikibaseFacetedSearchHooks.php
50-
51-
-
52-
message: '#^Parameter \$title of method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:onShowSearchHitTitle\(\) has invalid type Title\.$#'
53-
identifier: class.notFound
10+
message: '#^Method ProfessionalWiki\\WikibaseFacetedSearch\\EntryPoints\\WikibaseFacetedSearchHooks\:\:rewriteLinkForNonEntityResult\(\) never assigns string to &\$titleSnippet so it can be removed from the by\-ref type\.$#'
11+
identifier: parameterByRef.unusedType
5412
count: 1
5513
path: src/EntryPoints/WikibaseFacetedSearchHooks.php

phpstan.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ parameters:
99
- ../../includes
1010
- ../../tests/phpunit
1111
- ../../vendor
12+
- ../../extensions/CirrusSearch
1213
- ../../extensions/Wikibase
14+
- ../../extensions/WikibaseCirrusSearch
15+
bootstrapFiles:
16+
- ../../includes/AutoLoader.php

src/EntryPoints/WikibaseFacetedSearchHooks.php

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,26 @@
55
namespace ProfessionalWiki\WikibaseFacetedSearch\EntryPoints;
66

77
use HtmlArmor;
8+
use IContextSource;
9+
use Language;
810
use OutputPage;
11+
use ProfessionalWiki\WikibaseFacetedSearch\WikibaseFacetedSearchExtension;
912
use SearchResult;
1013
use SpecialSearch;
1114
use Title;
15+
use Wikibase\DataModel\Entity\ItemId;
16+
use Wikibase\DataModel\Term\TermFallback;
17+
use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookup;
18+
use Wikibase\Repo\Hooks\Formatters\EntityLinkFormatter;
19+
use Wikibase\Repo\WikibaseRepo;
20+
use Wikibase\Search\Elastic\EntityResult;
1221

1322
class WikibaseFacetedSearchHooks {
1423

1524
/**
1625
* @param string[] $terms
17-
* @param array<string, mixed> $query
18-
* @param array<string, mixed> $attributes
26+
* @param string[] $query
27+
* @param string[] $attributes
1928
*/
2029
public static function onShowSearchHitTitle(
2130
Title &$title,
@@ -26,11 +35,94 @@ public static function onShowSearchHitTitle(
2635
array &$query,
2736
array &$attributes
2837
): void {
29-
if ( $title->getNamespace() !== WB_NS_ITEM ) {
38+
$itemId = self::getItemId( $title );
39+
40+
if ( $itemId === null ) {
41+
return;
42+
}
43+
44+
$pageTitle = self::getItemPage( $itemId );
45+
46+
if ( $pageTitle === null ) {
3047
return;
3148
}
3249

33-
// TODO: get item site link and replace $title
50+
$title = $pageTitle;
51+
52+
if ( !( $result instanceof EntityResult ) ) {
53+
self::rewriteLinkForNonEntityResult(
54+
self::newLabelDescriptionLookup( $specialSearch->getContext() ),
55+
self::newLinkFormatter( $specialSearch->getLanguage() ),
56+
$itemId,
57+
$titleSnippet,
58+
$attributes
59+
);
60+
}
61+
}
62+
63+
private static function getItemId( Title $title ): ?ItemId {
64+
$entityId = WikibaseRepo::getEntityIdLookup()->getEntityIdForTitle( $title );
65+
66+
if ( $entityId instanceof ItemId ) {
67+
return $entityId;
68+
}
69+
70+
return null;
71+
}
72+
73+
private static function getItemPage( ItemId $itemId ): ?Title {
74+
return WikibaseFacetedSearchExtension::getInstance()->getItemPageLookup()->getPageTitle( $itemId );
75+
}
76+
77+
private static function newLinkFormatter( Language $language ): EntityLinkFormatter {
78+
return WikibaseRepo::getEntityLinkFormatterFactory()->getDefaultLinkFormatter( $language );
79+
}
80+
81+
private static function newLabelDescriptionLookup( IContextSource $context ): LanguageFallbackLabelDescriptionLookup {
82+
return new LanguageFallbackLabelDescriptionLookup(
83+
WikibaseRepo::getTermLookup(),
84+
WikibaseRepo::getLanguageFallbackChainFactory()->newFromContext( $context )
85+
);
86+
}
87+
88+
/**
89+
* @param string[] $attributes
90+
*/
91+
private static function rewriteLinkForNonEntityResult(
92+
LanguageFallbackLabelDescriptionLookup $labelDescriptionLookup,
93+
EntityLinkFormatter $linkFormatter,
94+
ItemId $itemId,
95+
string|HtmlArmor|null &$titleSnippet,
96+
array &$attributes
97+
): void {
98+
$labelData = self::termFallbackToTermData(
99+
$labelDescriptionLookup->getLabel( $itemId )
100+
);
101+
$descriptionData = self::termFallbackToTermData(
102+
$labelDescriptionLookup->getDescription( $itemId )
103+
);
104+
105+
$titleSnippet = new HtmlArmor( $linkFormatter->getHtml( $itemId, $labelData ) );
106+
107+
$attributes['title'] = $linkFormatter->getTitleAttribute(
108+
$itemId,
109+
$labelData,
110+
$descriptionData
111+
);
112+
}
113+
114+
/**
115+
* @return string[]|null
116+
*/
117+
private static function termFallbackToTermData( ?TermFallback $term = null ): ?array {
118+
if ( $term ) {
119+
return [
120+
'value' => $term->getText(),
121+
'language' => $term->getActualLanguageCode(),
122+
];
123+
}
124+
125+
return null;
34126
}
35127

36128
public static function onSpecialSearchResultsAppend(

src/Persistence/ItemPageLookup.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace ProfessionalWiki\WikibaseFacetedSearch\Persistence;
6+
7+
use Title;
8+
use Wikibase\DataModel\Entity\ItemId;
9+
10+
interface ItemPageLookup {
11+
12+
public function getPageTitle( ItemId $itemId ): ?Title;
13+
14+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace ProfessionalWiki\WikibaseFacetedSearch\Persistence;
6+
7+
use Title;
8+
use Wikibase\DataModel\Entity\ItemId;
9+
use Wikibase\Lib\Store\SiteLinkLookup;
10+
11+
class SiteLinkItemPageLookup implements ItemPageLookup {
12+
13+
public function __construct(
14+
private readonly SiteLinkLookup $siteLinksStore,
15+
private readonly string $siteLinkSiteId
16+
) {
17+
}
18+
19+
public function getPageTitle( ItemId $itemId ): ?Title {
20+
$siteLink = array_filter(
21+
$this->siteLinksStore->getSiteLinksForItem( $itemId ),
22+
fn( $siteLink ) => $siteLink->getSiteId() === $this->siteLinkSiteId
23+
)[0] ?? null;
24+
25+
if ( $siteLink === null ) {
26+
return null;
27+
}
28+
29+
return Title::newFromText( $siteLink->getPageName() );
30+
}
31+
32+
}

src/WikibaseFacetedSearchExtension.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
namespace ProfessionalWiki\WikibaseFacetedSearch;
66

7+
use ProfessionalWiki\WikibaseFacetedSearch\Persistence\ItemPageLookup;
8+
use ProfessionalWiki\WikibaseFacetedSearch\Persistence\SiteLinkItemPageLookup;
9+
use Wikibase\Repo\WikibaseRepo;
10+
711
class WikibaseFacetedSearchExtension {
812

913
public static function getInstance(): self {
@@ -13,4 +17,12 @@ public static function getInstance(): self {
1317
return $instance;
1418
}
1519

20+
public function getItemPageLookup(): ItemPageLookup {
21+
return new SiteLinkItemPageLookup(
22+
WikibaseRepo::getStore()->newSiteLinkStore(),
23+
// TODO: https://github.com/ProfessionalWiki/WikibaseFacetedSearch/issues/12
24+
'mardi'
25+
);
26+
}
27+
1628
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
3+
declare( strict_types = 1 );
4+
5+
namespace ProfessionalWiki\WikibaseFacetedSearch\Tests\Persistence;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use ProfessionalWiki\WikibaseFacetedSearch\Persistence\SiteLinkItemPageLookup;
9+
use Title;
10+
use Wikibase\DataModel\Entity\Item;
11+
use Wikibase\DataModel\Entity\ItemId;
12+
use Wikibase\Lib\Store\HashSiteLinkStore;
13+
14+
/**
15+
* @covers \ProfessionalWiki\WikibaseFacetedSearch\Persistence\SiteLinkItemPageLookup
16+
*/
17+
class SiteLinkItemPageLookupTest extends TestCase {
18+
19+
private const SITE_ID = 'testSiteId';
20+
21+
private HashSiteLinkStore $siteLinkStore;
22+
private SiteLinkItemPageLookup $lookup;
23+
24+
protected function setUp(): void {
25+
$this->siteLinkStore = new HashSiteLinkStore();
26+
$this->lookup = new SiteLinkItemPageLookup(
27+
$this->siteLinkStore,
28+
self::SITE_ID
29+
);
30+
}
31+
32+
public function testReturnsPageWhenSiteLinkExists(): void {
33+
$this->createItemWithSiteLink( 'Q42', self::SITE_ID, 'Page for Q42' );
34+
35+
$this->assertItemPageHasTitle( 'Q42', 'Page for Q42' );
36+
}
37+
38+
private function createItemWithSiteLink( string $itemId, string $siteId, string $pageName ): void {
39+
$item = new Item( new ItemId( $itemId ) );
40+
$item->getSiteLinkList()->addNewSiteLink( $siteId, $pageName );
41+
$this->siteLinkStore->saveLinksOfItem( $item );
42+
}
43+
44+
private function assertItemPageHasTitle( string $itemId, $pageTitle ): void {
45+
$this->assertEquals(
46+
Title::newFromText( $pageTitle ),
47+
$this->lookup->getPageTitle( new ItemId( $itemId ) )
48+
);
49+
}
50+
51+
public function testReturnsNullWhenNoSiteLinksExist(): void {
52+
$this->assertNull( $this->lookup->getPageTitle( new ItemId( 'Q404' ) ) );
53+
}
54+
55+
public function testReturnsNullWhenOnlyOtherSiteLinksExist(): void {
56+
$this->createItemWithSiteLink( 'Q100', 'otherSiteId', 'Other page' );
57+
$this->createItemWithSiteLink( 'Q200', 'anotherSiteId', 'Another page' );
58+
59+
$this->assertNull( $this->lookup->getPageTitle( new ItemId( 'Q42' ) ) );
60+
}
61+
62+
public function testReturnsPageWhenManySiteLinksExist(): void {
63+
$this->createItemWithSiteLink( 'Q42', self::SITE_ID, 'Page for Q42' );
64+
$this->createItemWithSiteLink( 'Q100', 'otherSiteId', 'Other page' );
65+
$this->createItemWithSiteLink( 'Q200', 'anotherSiteId', 'Another page' );
66+
67+
$this->assertItemPageHasTitle( 'Q42', 'Page for Q42' );
68+
}
69+
70+
}

0 commit comments

Comments
 (0)