Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 96 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
InterwikiSearchResultSetWidget | |
0.00% |
0 / 96 |
|
0.00% |
0 / 8 |
650 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
render | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
72 | |||
headerHtml | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
footerHtml | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
loadCustomCaptions | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
iwIcon | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
generateLogoName | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
generateIconFromFavicon | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Search\SearchWidgets; |
4 | |
5 | use ISearchResultSet; |
6 | use MediaWiki\Html\Html; |
7 | use MediaWiki\Interwiki\InterwikiLookup; |
8 | use MediaWiki\Linker\LinkRenderer; |
9 | use MediaWiki\Output\OutputPage; |
10 | use MediaWiki\Specials\SpecialSearch; |
11 | use MediaWiki\Title\Title; |
12 | use OOUI; |
13 | |
14 | /** |
15 | * Renders one or more ISearchResultSets into a sidebar grouped by |
16 | * interwiki prefix. Includes a per-wiki header indicating where |
17 | * the results are from. |
18 | */ |
19 | class InterwikiSearchResultSetWidget implements SearchResultSetWidget { |
20 | /** @var SpecialSearch */ |
21 | protected $specialSearch; |
22 | /** @var SearchResultWidget */ |
23 | protected $resultWidget; |
24 | /** @var string[]|null */ |
25 | protected $customCaptions; |
26 | /** @var LinkRenderer */ |
27 | protected $linkRenderer; |
28 | /** @var InterwikiLookup */ |
29 | protected $iwLookup; |
30 | /** @var OutputPage */ |
31 | protected $output; |
32 | /** @var bool */ |
33 | protected $showMultimedia; |
34 | /** @var array */ |
35 | protected $iwLogoOverrides; |
36 | |
37 | public function __construct( |
38 | SpecialSearch $specialSearch, |
39 | SearchResultWidget $resultWidget, |
40 | LinkRenderer $linkRenderer, |
41 | InterwikiLookup $iwLookup, |
42 | $showMultimedia = false |
43 | ) { |
44 | $this->specialSearch = $specialSearch; |
45 | $this->resultWidget = $resultWidget; |
46 | $this->linkRenderer = $linkRenderer; |
47 | $this->iwLookup = $iwLookup; |
48 | $this->output = $specialSearch->getOutput(); |
49 | $this->showMultimedia = $showMultimedia; |
50 | $this->iwLogoOverrides = $this->specialSearch->getConfig()->get( 'InterwikiLogoOverride' ); |
51 | } |
52 | |
53 | /** |
54 | * @param string $term User provided search term |
55 | * @param ISearchResultSet|ISearchResultSet[] $resultSets List of interwiki |
56 | * results to render. |
57 | * @return string HTML |
58 | */ |
59 | public function render( $term, $resultSets ) { |
60 | if ( !is_array( $resultSets ) ) { |
61 | $resultSets = [ $resultSets ]; |
62 | } |
63 | |
64 | $this->loadCustomCaptions(); |
65 | |
66 | if ( $this->showMultimedia ) { |
67 | $this->output->addModules( 'mediawiki.special.search.commonsInterwikiWidget' ); |
68 | } |
69 | $this->output->addModuleStyles( 'mediawiki.special.search.interwikiwidget.styles' ); |
70 | $this->output->addModuleStyles( 'oojs-ui.styles.icons-wikimedia' ); |
71 | |
72 | $iwResults = []; |
73 | foreach ( $resultSets as $resultSet ) { |
74 | foreach ( $resultSet as $result ) { |
75 | if ( !$result->isBrokenTitle() ) { |
76 | $iwResults[$result->getTitle()->getInterwiki()][] = $result; |
77 | } |
78 | } |
79 | } |
80 | |
81 | $iwResultSetPos = 1; |
82 | $iwResultListOutput = ''; |
83 | |
84 | foreach ( $iwResults as $iwPrefix => $results ) { |
85 | // TODO: Assumes interwiki results are never paginated |
86 | $position = 0; |
87 | $iwResultItemOutput = ''; |
88 | |
89 | foreach ( $results as $result ) { |
90 | $iwResultItemOutput .= $this->resultWidget->render( $result, $position++ ); |
91 | } |
92 | |
93 | $headerHtml = $this->headerHtml( $term, $iwPrefix ); |
94 | $footerHtml = $this->footerHtml( $term, $iwPrefix ); |
95 | $iwResultListOutput .= Html::rawElement( 'li', |
96 | [ |
97 | 'class' => 'iw-resultset', |
98 | 'data-iw-resultset-pos' => $iwResultSetPos, |
99 | 'data-iw-resultset-source' => $iwPrefix |
100 | ], |
101 | |
102 | $headerHtml . |
103 | $iwResultItemOutput . |
104 | $footerHtml |
105 | ); |
106 | $iwResultSetPos++; |
107 | } |
108 | |
109 | return Html::rawElement( |
110 | 'div', |
111 | [ 'id' => 'mw-interwiki-results' ], |
112 | Html::rawElement( |
113 | 'ul', [ 'class' => 'iw-results', ], $iwResultListOutput |
114 | ) |
115 | ); |
116 | } |
117 | |
118 | /** |
119 | * Generates an HTML header for the given interwiki prefix |
120 | * |
121 | * @param string $term User provided search term |
122 | * @param string $iwPrefix Interwiki prefix of wiki to show heading for |
123 | * @return string HTML |
124 | */ |
125 | protected function headerHtml( $term, $iwPrefix ) { |
126 | $href = Title::makeTitle( NS_SPECIAL, 'Search', '', $iwPrefix )->getLocalURL( |
127 | [ 'search' => $term, 'fulltext' => 1 ] |
128 | ); |
129 | |
130 | $interwiki = $this->iwLookup->fetch( $iwPrefix ); |
131 | $parsed = wfParseUrl( wfExpandUrl( $interwiki ? $interwiki->getURL() : '/' ) ); |
132 | |
133 | $caption = $this->customCaptions[$iwPrefix] ?? $parsed['host']; |
134 | |
135 | $searchLink = Html::rawElement( 'a', [ 'href' => $href, 'target' => '_blank' ], $caption ); |
136 | |
137 | return Html::rawElement( 'div', |
138 | [ 'class' => 'iw-result__header' ], |
139 | $this->iwIcon( $iwPrefix ) . $searchLink ); |
140 | } |
141 | |
142 | /** |
143 | * Generates an HTML footer for the given interwiki prefix |
144 | * |
145 | * @param string $term User provided search term |
146 | * @param string $iwPrefix Interwiki prefix of wiki to show heading for |
147 | * @return string HTML |
148 | */ |
149 | protected function footerHtml( $term, $iwPrefix ) { |
150 | $href = Title::makeTitle( NS_SPECIAL, 'Search', '', $iwPrefix )->getLocalURL( |
151 | [ 'search' => $term, 'fulltext' => 1 ] |
152 | ); |
153 | |
154 | $interwiki = $this->iwLookup->fetch( $iwPrefix ); |
155 | $parsed = wfParseUrl( wfExpandUrl( $interwiki ? $interwiki->getURL() : '/' ) ); |
156 | |
157 | $caption = $this->specialSearch->msg( 'search-interwiki-resultset-link', $parsed['host'] )->escaped(); |
158 | |
159 | $searchLink = Html::rawElement( 'a', [ 'href' => $href, 'target' => '_blank' ], $caption ); |
160 | |
161 | return Html::rawElement( 'div', |
162 | [ 'class' => 'iw-result__footer' ], |
163 | $searchLink ); |
164 | } |
165 | |
166 | protected function loadCustomCaptions() { |
167 | if ( $this->customCaptions !== null ) { |
168 | return; |
169 | } |
170 | |
171 | $this->customCaptions = []; |
172 | $customLines = explode( "\n", $this->specialSearch->msg( 'search-interwiki-custom' )->escaped() ); |
173 | foreach ( $customLines as $line ) { |
174 | $parts = explode( ':', $line, 2 ); |
175 | if ( count( $parts ) === 2 ) { |
176 | $this->customCaptions[$parts[0]] = $parts[1]; |
177 | } |
178 | } |
179 | } |
180 | |
181 | /** |
182 | * Generates a custom OOUI icon element. |
183 | * These icons are either generated by fetching the interwiki favicon. |
184 | * or by using config 'InterwikiLogoOverrides'. |
185 | * |
186 | * @param string $iwPrefix Interwiki prefix |
187 | * @return OOUI\IconWidget |
188 | */ |
189 | protected function iwIcon( $iwPrefix ) { |
190 | $logoName = $this->generateLogoName( $iwPrefix ); |
191 | // If the value is an URL we use the favicon |
192 | if ( filter_var( $logoName, FILTER_VALIDATE_URL ) || $logoName === "/" ) { |
193 | return $this->generateIconFromFavicon( $logoName ); |
194 | } |
195 | |
196 | $iwIcon = new OOUI\IconWidget( [ |
197 | 'icon' => $logoName |
198 | ] ); |
199 | |
200 | return $iwIcon; |
201 | } |
202 | |
203 | /** |
204 | * Generates the logo name used to render the interwiki icon. |
205 | * The logo name can be defined in two ways: |
206 | * 1) The logo is generated using interwiki getURL to fetch the site favicon |
207 | * 2) The logo name is defined using config `wgInterwikiLogoOverride`. This accept |
208 | * Codex icon names and URLs. |
209 | * |
210 | * @param string $prefix Interwiki prefix |
211 | * @return string logoName |
212 | */ |
213 | protected function generateLogoName( $prefix ) { |
214 | $logoOverridesKeys = array_keys( $this->iwLogoOverrides ); |
215 | if ( in_array( $prefix, $logoOverridesKeys ) ) { |
216 | return $this->iwLogoOverrides[ $prefix ]; |
217 | } |
218 | |
219 | $interwiki = $this->iwLookup->fetch( $prefix ); |
220 | return $interwiki ? $interwiki->getURL() : '/'; |
221 | } |
222 | |
223 | /** |
224 | * Fetches the favicon of the provided URL. |
225 | * |
226 | * @param string $logoUrl |
227 | * @return OOUI\IconWidget |
228 | */ |
229 | protected function generateIconFromFavicon( $logoUrl ) { |
230 | $parsed = wfParseUrl( wfExpandUrl( $logoUrl ) ); |
231 | $iwIconUrl = $parsed['scheme'] . |
232 | $parsed['delimiter'] . |
233 | $parsed['host'] . |
234 | ( isset( $parsed['port'] ) ? ':' . $parsed['port'] : '' ) . |
235 | '/favicon.ico'; |
236 | |
237 | $iwIcon = new OOUI\IconWidget( [ |
238 | 'icon' => 'favicon' |
239 | ] ); |
240 | |
241 | return $iwIcon->setAttributes( [ 'style' => "background-image:url($iwIconUrl);" ] ); |
242 | } |
243 | } |