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