This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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
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 |