ShowSearchHitHandler   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 9
dl 0
loc 211
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A factory() 0 10 1
B onShowSearchHit() 0 53 8
A isTitleEntity() 0 4 1
A getEntity() 0 7 2
A addLanguageAttrs() 0 11 3
A addDescription() 0 11 1
A onShowSearchHitTitle() 0 16 3
A withLanguage() 0 17 4
1
<?php
2
3
namespace Wikibase\Repo\Hooks;
4
5
use Html;
6
use HtmlArmor;
7
use InvalidArgumentException;
8
use Language;
9
use MediaWiki\Search\Hook\ShowSearchHitHook;
10
use MediaWiki\Search\Hook\ShowSearchHitTitleHook;
11
use MWException;
12
use RequestContext;
13
use SearchResult;
14
use SpecialSearch;
15
use Title;
16
use Wikibase\DataModel\Entity\EntityDocument;
17
use Wikibase\DataModel\Entity\Item;
18
use Wikibase\DataModel\Services\Lookup\EntityLookup;
19
use Wikibase\DataModel\Statement\StatementListProvider;
20
use Wikibase\DataModel\Term\DescriptionsProvider;
21
use Wikibase\DataModel\Term\TermFallback;
22
use Wikibase\Lib\LanguageFallbackChainFactory;
23
use Wikibase\Lib\LanguageFallbackIndicator;
24
use Wikibase\Lib\LanguageNameLookup;
25
use Wikibase\Lib\Store\EntityIdLookup;
26
use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
27
use Wikibase\Repo\Content\EntityContentFactory;
28
use Wikibase\Repo\Search\ExtendedResult;
29
use Wikibase\Repo\WikibaseRepo;
30
31
/**
32
 * Handler to format entities in the search results
33
 *
34
 * @license GPL-2.0-or-later
35
 * @author Matěj Suchánek
36
 * @author Daniel Kinzler
37
 */
38
class ShowSearchHitHandler implements ShowSearchHitHook, ShowSearchHitTitleHook {
39
40
	private $entityContentFactory;
41
	private $entityIdLookup;
42
	private $entityLookup;
43
	private $fallbackChainFactory;
44
45
	public function __construct(
46
		EntityContentFactory $entityContentFactory,
47
		EntityIdLookup $entityIdLookup,
48
		EntityLookup $entityLookup,
49
		LanguageFallbackChainFactory $fallbackChainFactory
50
	) {
51
		$this->entityContentFactory = $entityContentFactory;
52
		$this->entityIdLookup = $entityIdLookup;
53
		$this->entityLookup = $entityLookup;
54
		$this->fallbackChainFactory = $fallbackChainFactory;
55
	}
56
57
	public static function factory(): self {
58
		$wikibaseRepo = WikibaseRepo::getDefaultInstance();
59
60
		return new self(
61
			$wikibaseRepo->getEntityContentFactory(),
62
			$wikibaseRepo->getEntityIdLookup(),
63
			$wikibaseRepo->getEntityLookup(),
64
			$wikibaseRepo->getLanguageFallbackChainFactory()
65
		);
66
	}
67
68
	/**
69
	 * Format the output when the search result contains entities
70
	 * @see https://www.mediawiki.org/wiki/Manual:Hooks/ShowSearchHit
71
	 * @see showEntityResultHit
72
	 * @see showPlainSearchHit
73
	 * @param SpecialSearch $searchPage
74
	 * @param SearchResult $result
75
	 * @param string[] $terms
76
	 * @param string &$link
77
	 * @param string &$redirect
78
	 * @param string &$section
79
	 * @param string &$extract
80
	 * @param string &$score
81
	 * @param string &$size
82
	 * @param string &$date
83
	 * @param string &$related
84
	 * @param string &$html
85
	 * @return void
86
	 */
87
	public function onShowSearchHit( $searchPage, $result,
88
		$terms, &$link, &$redirect, &$section, &$extract, &$score, &$size, &$date, &$related,
89
		&$html
90
	): void {
91
		if ( $result instanceof ExtendedResult ) {
92
			return;
93
		}
94
95
		$languageFallbackChain = $this->fallbackChainFactory->newFromContext( $searchPage->getContext() );
96
97
		$title = $result->getTitle();
98
99
		if ( !$this->isTitleEntity( $title ) ) {
100
			return;
101
		}
102
103
		try {
104
			$entity = $this->getEntity( $title );
105
		} catch ( RevisionedUnresolvedRedirectException $exception ) {
106
			return;
107
		}
108
109
		if ( !( $entity instanceof DescriptionsProvider ) ) {
110
			return;
111
		}
112
113
		$extract = '';
114
115
		$entityTerms = $entity->getDescriptions()->toTextArray();
116
		$termData = $languageFallbackChain->extractPreferredValue( $entityTerms );
117
		if ( $termData !== null ) {
118
			// TODO: do something akin to SearchResult::getTextSnippet here?
119
			self::addDescription( $extract, $termData, $searchPage );
120
		}
121
122
		if ( $entity instanceof StatementListProvider ) {
123
			$statementCount = $entity->getStatements()->count();
124
		} else {
125
			$statementCount = 0;
126
		}
127
		if ( $entity instanceof Item ) {
128
			$linkCount = $entity->getSiteLinkList()->count();
129
		} else {
130
			$linkCount = 0;
131
		}
132
133
		// set $size to size metrics
134
		$size = $searchPage->msg(
135
			'wikibase-search-result-stats',
136
			$statementCount,
137
			$linkCount
138
		)->escaped();
139
	}
140
141
	private function isTitleEntity( Title $title ): bool {
142
		$contentModel = $title->getContentModel();
143
		return $this->entityContentFactory->isEntityContentModel( $contentModel );
144
	}
145
146
	private function getEntity( Title $title ): ?EntityDocument {
147
		$entityId = $this->entityIdLookup->getEntityIdForTitle( $title );
148
		if ( $entityId ) {
149
			return $this->entityLookup->getEntity( $entityId );
150
		}
151
		return null;
152
	}
153
154
	/**
155
	 * Add attributes appropriate for language of this text.
156
	 * @param array &$attr Link attributes, to be modified if needed
157
	 * @param string $displayLanguage
158
	 * @param array $text Text description array, with language in ['language']
159
	 */
160
	public static function addLanguageAttrs( array &$attr, string $displayLanguage, array $text ) {
161
		if ( $text['language'] !== $displayLanguage ) {
162
			try {
163
				$language = Language::factory( $text['language'] );
164
			} catch ( MWException $e ) {
0 ignored issues
show
Bug introduced by
The class MWException does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
165
				// If somebody fed us broken language, ignore it
166
				return;
167
			}
168
			$attr += [ 'dir' => $language->getDir(), 'lang' => $language->getHtmlCode() ];
169
		}
170
	}
171
172
	/**
173
	 * Add HTML description to search result.
174
	 * @param string &$html The html of the description will be appended here.
175
	 * @param string[] $description Description as [language, value] array
176
	 * @param SpecialSearch $searchPage
177
	 */
178
	public static function addDescription( string &$html, array $description, SpecialSearch $searchPage ) {
179
		RequestContext::getMain()->getOutput()->addModuleStyles( [ 'wikibase.common' ] );
180
		$displayLanguage = $searchPage->getLanguage()->getCode();
181
		$description = self::withLanguage( $description, $displayLanguage );
182
		$attr = [ 'class' => 'wb-itemlink-description' ];
183
		self::addLanguageAttrs( $attr, $displayLanguage, $description );
184
		// Wrap with searchresult div, as original code does
185
		$html .= Html::rawElement( 'div', [ 'class' => 'searchresult' ],
186
			Html::rawElement( 'span', $attr, HtmlArmor::getHtml( $description['value'] ) )
187
		);
188
	}
189
190
	/**
191
	 * Remove span tag placed around title search hit for entity titles
192
	 * to highlight matches in bold.
193
	 *
194
	 * @todo Add highlighting when Q##-id matches and not label text.
195
	 *
196
	 * @param Title &$title
197
	 * @param string|HtmlArmor|null &$titleSnippet
198
	 * @param SearchResult $result
199
	 * @param array $terms
200
	 * @param SpecialSearch $specialSearch
201
	 * @param string[] &$query
202
	 * @param string[] &$attributes
203
	 * @return void
204
	 */
205
	public function onShowSearchHitTitle(
206
		&$title,
207
		&$titleSnippet,
208
		$result,
209
		$terms,
210
		$specialSearch,
211
		&$query,
212
		&$attributes
213
	): void {
214
		if ( $result instanceof ExtendedResult ) {
215
			return;
216
		}
217
		if ( $this->isTitleEntity( $title ) ) {
218
			$titleSnippet = $title->getFullText();
219
		}
220
	}
221
222
	/**
223
	 * If text's language is not the same as display language, add
224
	 * marker with language name to the string.
225
	 *
226
	 * @param string[] $text ['language' => LANG, 'value' => TEXT]
227
	 * @param string $displayLanguage
228
	 * @return array ['language' => LANG, 'value' => TEXT]
229
	 */
230
	public static function withLanguage( $text, $displayLanguage ) {
231
		if ( $text['language'] == $displayLanguage || $text['value'] == '' ) {
232
			return $text;
233
		}
234
		try {
235
			$termFallback = new TermFallback( $displayLanguage, HtmlArmor::getHtml( $text['value'] ),
236
					$text['language'], null );
237
		} catch ( InvalidArgumentException $e ) {
238
			return $text;
239
		}
240
		$fallback = new LanguageFallbackIndicator( new LanguageNameLookup( $displayLanguage ) );
241
		$markedText = HtmlArmor::getHtml( $text['value'] ) . $fallback->getHtml( $termFallback );
242
		return [
243
			'language' => $text['language'],
244
			'value' => new HtmlArmor( $markedText )
245
		];
246
	}
247
248
}
249