Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
89.66% |
78 / 87 |
|
87.50% |
7 / 8 |
CRAP | |
0.00% |
0 / 1 |
SearchHookHandler | |
89.66% |
78 / 87 |
|
87.50% |
7 / 8 |
17.32 | |
0.00% |
0 / 1 |
newFromGlobalState | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
1 | |||
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
onSpecialSearchResultsAppend | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
addToSearch | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
getTermSearchResults | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
renderTermSearchResults | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
renderTermSearchResult | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
searchEntities | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace ArticlePlaceholder; |
4 | |
5 | use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface; |
6 | use MediaWiki\MediaWikiServices; |
7 | use OutputPage; |
8 | use SpecialPage; |
9 | use SpecialSearch; |
10 | use Wikibase\Client\WikibaseClient; |
11 | use Wikibase\Lib\Interactors\TermSearchInteractor; |
12 | use Wikibase\Lib\Interactors\TermSearchResult; |
13 | use Wikibase\Lib\TermIndexEntry; |
14 | use Wikimedia\Rdbms\SessionConsistentConnectionManager; |
15 | |
16 | /** |
17 | * Adding results from ArticlePlaceholder to search |
18 | * |
19 | * @license GPL-2.0-or-later |
20 | * @author Lucie-Aimée Kaffee |
21 | */ |
22 | class SearchHookHandler { |
23 | |
24 | /** |
25 | * @var TermSearchInteractor |
26 | */ |
27 | private $termSearchInteractor; |
28 | |
29 | /** |
30 | * @var string |
31 | */ |
32 | private $languageCode; |
33 | |
34 | /** |
35 | * @var ItemNotabilityFilter |
36 | */ |
37 | private $itemNotabilityFilter; |
38 | |
39 | /** |
40 | * @var StatsdDataFactoryInterface |
41 | */ |
42 | private $statsdDataFactory; |
43 | |
44 | /** |
45 | * @param SpecialPage $specialPage |
46 | * |
47 | * @return self |
48 | */ |
49 | private static function newFromGlobalState( SpecialPage $specialPage ) { |
50 | // TODO inject services into hook handler instance |
51 | $mwServices = MediaWikiServices::getInstance(); |
52 | $repoDB = WikibaseClient::getItemAndPropertySource()->getDatabaseName(); |
53 | $lbFactory = $mwServices->getDBLoadBalancerFactory(); |
54 | $clientSettings = WikibaseClient::getSettings( $mwServices ); |
55 | |
56 | $itemNotabilityFilter = new ItemNotabilityFilter( |
57 | new SessionConsistentConnectionManager( $lbFactory->getMainLB( $repoDB ), $repoDB ), |
58 | WikibaseClient::getEntityNamespaceLookup( $mwServices ), |
59 | WikibaseClient::getStore()->getSiteLinkLookup(), |
60 | $clientSettings->getSetting( 'siteGlobalID' ) |
61 | ); |
62 | |
63 | $statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory(); |
64 | $config = MediaWikiServices::getInstance()->getMainConfig(); |
65 | |
66 | $termSearchInteractor = new TermSearchApiInteractor( |
67 | new RepoApiInteractor( |
68 | $config->get( 'ArticlePlaceholderRepoApiUrl' ), |
69 | $statsdDataFactory, |
70 | MediaWikiServices::getInstance()->getHttpRequestFactory() |
71 | ), |
72 | WikibaseClient::getEntityIdParser() |
73 | ); |
74 | |
75 | return new self( |
76 | $termSearchInteractor, |
77 | $specialPage->getConfig()->get( 'LanguageCode' ), |
78 | $itemNotabilityFilter, |
79 | $statsdDataFactory |
80 | ); |
81 | } |
82 | |
83 | /** |
84 | * @param TermSearchInteractor $termSearchInteractor |
85 | * @param string $languageCode content language |
86 | * @param ItemNotabilityFilter $itemNotabilityFilter |
87 | * @param StatsdDataFactoryInterface $statsdDataFactory |
88 | */ |
89 | public function __construct( |
90 | TermSearchInteractor $termSearchInteractor, |
91 | $languageCode, |
92 | ItemNotabilityFilter $itemNotabilityFilter, |
93 | StatsdDataFactoryInterface $statsdDataFactory |
94 | ) { |
95 | $this->termSearchInteractor = $termSearchInteractor; |
96 | $this->languageCode = $languageCode; |
97 | $this->itemNotabilityFilter = $itemNotabilityFilter; |
98 | $this->statsdDataFactory = $statsdDataFactory; |
99 | } |
100 | |
101 | /** |
102 | * @param SpecialSearch $specialSearch |
103 | * @param OutputPage $output |
104 | * @param string $term |
105 | */ |
106 | public static function onSpecialSearchResultsAppend( |
107 | SpecialSearch $specialSearch, |
108 | OutputPage $output, |
109 | $term |
110 | ) { |
111 | if ( trim( $term ) === '' ) { |
112 | return; |
113 | } |
114 | |
115 | $articlePlaceholderSearchEnabled = MediaWikiServices::getInstance()->getMainConfig()->get( |
116 | 'ArticlePlaceholderSearchIntegrationEnabled' |
117 | ); |
118 | if ( $articlePlaceholderSearchEnabled !== true ) { |
119 | return; |
120 | } |
121 | |
122 | $instance = self::newFromGlobalState( $specialSearch ); |
123 | $instance->addToSearch( $output, $term ); |
124 | } |
125 | |
126 | /** |
127 | * @param OutputPage $output |
128 | * @param string $term |
129 | */ |
130 | private function addToSearch( OutputPage $output, $term ): void { |
131 | $termSearchResults = $this->getTermSearchResults( $term ); |
132 | |
133 | if ( !empty( $termSearchResults ) ) { |
134 | $renderedTermSearchResults = $this->renderTermSearchResults( $termSearchResults ); |
135 | |
136 | if ( $renderedTermSearchResults !== '' ) { |
137 | $output->addWikiTextAsInterface( |
138 | '==' . |
139 | $output->msg( 'articleplaceholder-search-header' )->plain() . |
140 | '==' |
141 | ); |
142 | |
143 | $output->addWikiTextAsInterface( $renderedTermSearchResults ); |
144 | |
145 | $this->statsdDataFactory->increment( |
146 | 'wikibase.articleplaceholder.search.has_results' |
147 | ); |
148 | |
149 | return; |
150 | } |
151 | } |
152 | |
153 | $this->statsdDataFactory->increment( |
154 | 'wikibase.articleplaceholder.search.no_results' |
155 | ); |
156 | } |
157 | |
158 | /** |
159 | * @param string $term |
160 | * |
161 | * @return TermSearchResult[] |
162 | */ |
163 | private function getTermSearchResults( $term ) { |
164 | $termSearchResults = []; |
165 | |
166 | foreach ( $this->searchEntities( $term ) as $searchResult ) { |
167 | $entityId = $searchResult->getEntityId()->getSerialization(); |
168 | |
169 | $termSearchResults[ $entityId ] = $searchResult; |
170 | } |
171 | |
172 | return $termSearchResults; |
173 | } |
174 | |
175 | /** |
176 | * Render search results, filtered for notability. |
177 | * |
178 | * @param TermSearchResult[] $termSearchResults |
179 | * |
180 | * @return string Wikitext |
181 | */ |
182 | private function renderTermSearchResults( array $termSearchResults ) { |
183 | $wikitext = ''; |
184 | |
185 | $itemIds = []; |
186 | foreach ( $termSearchResults as $termSearchResult ) { |
187 | $itemIds[] = $termSearchResult->getEntityId(); |
188 | } |
189 | |
190 | $notableEntityIds = $this->itemNotabilityFilter->getNotableEntityIds( $itemIds ); |
191 | |
192 | foreach ( $notableEntityIds as $entityId ) { |
193 | $result = $this->renderTermSearchResult( $termSearchResults[ $entityId->getSerialization() ] ); |
194 | |
195 | $wikitext .= '<div class="article-placeholder-searchResult">' |
196 | . $result |
197 | . '</div>'; |
198 | } |
199 | |
200 | return $wikitext; |
201 | } |
202 | |
203 | /** |
204 | * @param TermSearchResult $searchResult |
205 | * |
206 | * @return string Wikitext |
207 | */ |
208 | private function renderTermSearchResult( TermSearchResult $searchResult ) { |
209 | $entityId = $searchResult->getEntityId(); |
210 | |
211 | $displayLabel = $searchResult->getDisplayLabel(); |
212 | $displayDescription = $searchResult->getDisplayDescription(); |
213 | |
214 | $label = $displayLabel ? $displayLabel->getText() : $entityId->getSerialization(); |
215 | |
216 | // TODO: Properly construct the page name of the special page. |
217 | $wikitext = '[[Special:AboutTopic/' . wfEscapeWikiText( $entityId ) . '|' |
218 | . wfEscapeWikiText( $label ) . ']]'; |
219 | |
220 | if ( $displayDescription ) { |
221 | $wikitext .= ': ' . wfEscapeWikiText( $displayDescription->getText() ); |
222 | } |
223 | |
224 | return $wikitext; |
225 | } |
226 | |
227 | /** |
228 | * @param string $term |
229 | * |
230 | * @return TermSearchResult[] |
231 | */ |
232 | private function searchEntities( $term ) { |
233 | return $this->termSearchInteractor->searchForEntities( |
234 | $term, |
235 | $this->languageCode, |
236 | 'item', |
237 | [ TermIndexEntry::TYPE_LABEL, TermIndexEntry::TYPE_ALIAS ] |
238 | ); |
239 | } |
240 | |
241 | } |