Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 395 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
SearchTranslationsSpecialPage | |
0.00% |
0 / 395 |
|
0.00% |
0 / 12 |
3192 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 178 |
|
0.00% |
0 / 1 |
552 | |||
getLanguages | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
42 | |||
getGroups | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
makeGroupFacetRows | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
72 | |||
showSearch | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
20 | |||
showEmptySearch | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
showSearchError | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
ellipsisSelector | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
messageSelector | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
56 | |||
getSearchInput | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\TtmServer; |
5 | |
6 | use CrossLanguageTranslationSearchQuery; |
7 | use ErrorPageError; |
8 | use FormatJson; |
9 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
10 | use MediaWiki\Extension\Translate\MessageLoading\MessageHandle; |
11 | use MediaWiki\Extension\Translate\TranslatorInterface\Aid\CurrentTranslationAid; |
12 | use MediaWiki\Extension\Translate\TranslatorInterface\Aid\TranslationAidDataProvider; |
13 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
14 | use MediaWiki\Html\FormOptions; |
15 | use MediaWiki\Html\Html; |
16 | use MediaWiki\Languages\LanguageFactory; |
17 | use MediaWiki\Languages\LanguageNameUtils; |
18 | use MediaWiki\Title\Title; |
19 | use MediaWiki\Utils\UrlUtils; |
20 | use MediaWiki\WikiMap\WikiMap; |
21 | use Message; |
22 | use SpecialPage; |
23 | use TTMServerException; |
24 | |
25 | /** |
26 | * Contains logic to search for translations |
27 | * |
28 | * @author Niklas Laxström |
29 | * @license GPL-2.0-or-later |
30 | * @ingroup SpecialPage TranslateSpecialPage |
31 | */ |
32 | class SearchTranslationsSpecialPage extends SpecialPage { |
33 | private FormOptions $opts; |
34 | /** |
35 | * Placeholders used for highlighting. Search backend can mark the beginning and |
36 | * end but, we need to run htmlspecialchars on the result first and then |
37 | * replace the placeholders with the html. It is assumed placeholders |
38 | * don't contain any chars that are escaped in html. |
39 | */ |
40 | private array $hl; |
41 | /** How many search results to display per page */ |
42 | protected int $limit = 25; |
43 | private TtmServerFactory $ttmServerFactory; |
44 | private LanguageFactory $languageFactory; |
45 | private UrlUtils $urlUtils; |
46 | |
47 | public function __construct( |
48 | TtmServerFactory $ttmServerFactory, |
49 | LanguageFactory $languageFactory, |
50 | UrlUtils $urlUtils |
51 | ) { |
52 | parent::__construct( 'SearchTranslations' ); |
53 | $this->hl = [ |
54 | Utilities::getPlaceholder(), |
55 | Utilities::getPlaceholder(), |
56 | ]; |
57 | |
58 | $this->ttmServerFactory = $ttmServerFactory; |
59 | $this->languageFactory = $languageFactory; |
60 | $this->urlUtils = $urlUtils; |
61 | } |
62 | |
63 | public function execute( $subPage ) { |
64 | global $wgLanguageCode; |
65 | $this->setHeaders(); |
66 | $this->checkPermissions(); |
67 | |
68 | $server = $this->ttmServerFactory->getDefaultForQuerying(); |
69 | if ( !$server instanceof SearchableTtmServer ) { |
70 | throw new ErrorPageError( 'tux-sst-nosolr-title', 'tux-sst-nosolr-body' ); |
71 | } |
72 | |
73 | $out = $this->getOutput(); |
74 | $out->addModuleStyles( 'jquery.uls.grid' ); |
75 | $out->addModuleStyles( 'ext.translate.specialpages.styles' ); |
76 | $out->addModuleStyles( 'ext.translate.special.translate.styles' ); |
77 | $out->addModuleStyles( [ 'mediawiki.ui.button', 'mediawiki.ui.input', 'mediawiki.ui.checkbox' ] ); |
78 | $out->addModules( 'ext.translate.special.searchtranslations' ); |
79 | $out->addHelpLink( 'Help:Extension:Translate#searching' ); |
80 | $out->addJsConfigVars( |
81 | 'wgTranslateLanguages', |
82 | Utilities::getLanguageNames( LanguageNameUtils::AUTONYMS ) |
83 | ); |
84 | |
85 | $this->opts = $opts = new FormOptions(); |
86 | $opts->add( 'query', '' ); |
87 | $opts->add( 'sourcelanguage', $wgLanguageCode ); |
88 | $opts->add( 'language', '' ); |
89 | $opts->add( 'group', '' ); |
90 | $opts->add( 'grouppath', '' ); |
91 | $opts->add( 'filter', '' ); |
92 | $opts->add( 'match', '' ); |
93 | $opts->add( 'case', '' ); |
94 | $opts->add( 'limit', $this->limit ); |
95 | $opts->add( 'offset', 0 ); |
96 | |
97 | $opts->fetchValuesFromRequest( $this->getRequest() ); |
98 | |
99 | $queryString = $opts->getValue( 'query' ); |
100 | |
101 | if ( $queryString === '' ) { |
102 | $this->showEmptySearch(); |
103 | return; |
104 | } |
105 | |
106 | $search = $this->getSearchInput( $queryString ); |
107 | |
108 | $options = $params = $opts->getAllValues(); |
109 | $filter = $opts->getValue( 'filter' ); |
110 | try { |
111 | if ( $opts->getValue( 'language' ) === '' ) { |
112 | $options['language'] = $this->getLanguage()->getCode(); |
113 | } |
114 | $translationSearch = new CrossLanguageTranslationSearchQuery( $options, $server ); |
115 | if ( in_array( $filter, $translationSearch->getAvailableFilters() ) ) { |
116 | if ( $options['language'] === $options['sourcelanguage'] ) { |
117 | $this->showSearchError( $search, $this->msg( 'tux-sst-error-language' ) ); |
118 | return; |
119 | } |
120 | |
121 | $opts->setValue( 'language', $options['language'] ); |
122 | $documents = $translationSearch->getDocuments(); |
123 | $total = $translationSearch->getTotalHits(); |
124 | $resultSet = $translationSearch->getResultSet(); |
125 | } else { |
126 | $resultSet = $server->search( $queryString, $params, $this->hl ); |
127 | $documents = $server->getDocuments( $resultSet ); |
128 | $total = $server->getTotalHits( $resultSet ); |
129 | } |
130 | } catch ( TTMServerException $e ) { |
131 | $message = $e->getMessage(); |
132 | // Known exceptions |
133 | if ( preg_match( '/^Result window is too large/', $message ) ) { |
134 | $this->showSearchError( $search, $this->msg( 'tux-sst-error-offset' ) ); |
135 | return; |
136 | } |
137 | |
138 | // Other exceptions |
139 | error_log( 'Translation search server unavailable: ' . $e->getMessage() ); |
140 | throw new ErrorPageError( 'tux-sst-solr-offline-title', 'tux-sst-solr-offline-body' ); |
141 | } |
142 | |
143 | // Part 1: facets |
144 | $facets = $server->getFacets( $resultSet ); |
145 | $facetHtml = ''; |
146 | |
147 | if ( $facets['language'] !== [] ) { |
148 | if ( $filter !== '' ) { |
149 | $facets['language'] = array_merge( |
150 | $facets['language'], |
151 | [ $opts->getValue( 'language' ) => $total ] |
152 | ); |
153 | } |
154 | $facetHtml = Html::element( 'div', |
155 | [ 'class' => 'row facet languages', |
156 | 'data-facets' => FormatJson::encode( $this->getLanguages( $facets['language'] ) ), |
157 | 'data-language' => $opts->getValue( 'language' ), |
158 | ], |
159 | $this->msg( 'tux-sst-facet-language' )->text() |
160 | ); |
161 | } |
162 | |
163 | if ( $facets['group'] !== [] ) { |
164 | $facetHtml .= Html::element( 'div', |
165 | [ 'class' => 'row facet groups', |
166 | 'data-facets' => FormatJson::encode( $this->getGroups( $facets['group'] ) ), |
167 | 'data-group' => $opts->getValue( 'group' ) ], |
168 | $this->msg( 'tux-sst-facet-group' )->text() |
169 | ); |
170 | } |
171 | |
172 | // Part 2: results |
173 | $resultsHtml = ''; |
174 | |
175 | $title = Title::newFromText( $queryString ); |
176 | if ( $title && !in_array( $filter, $translationSearch->getAvailableFilters() ) ) { |
177 | $handle = new MessageHandle( $title ); |
178 | $code = $handle->getCode(); |
179 | $language = $opts->getValue( 'language' ); |
180 | if ( $code !== '' && $code !== $language && $handle->isValid() ) { |
181 | $dataProvider = new TranslationAidDataProvider( $handle ); |
182 | $aid = new CurrentTranslationAid( |
183 | $handle->getGroup(), |
184 | $handle, |
185 | $this->getContext(), |
186 | $dataProvider |
187 | ); |
188 | $document = [ |
189 | 'wiki' => WikiMap::getCurrentWikiId(), |
190 | 'localid' => $handle->getTitleForBase()->getPrefixedText(), |
191 | 'content' => $aid->getData()['value'], |
192 | 'language' => $handle->getCode(), |
193 | ]; |
194 | array_unshift( $documents, $document ); |
195 | $total++; |
196 | } |
197 | } |
198 | |
199 | foreach ( $documents as $document ) { |
200 | $text = $document['content']; |
201 | if ( $text === null ) { |
202 | continue; |
203 | } |
204 | $text = Utilities::convertWhiteSpaceToHTML( $text ); |
205 | |
206 | [ $pre, $post ] = $this->hl; |
207 | $text = str_replace( $pre, '<strong class="tux-search-highlight">', $text ); |
208 | $text = str_replace( $post, '</strong>', $text ); |
209 | |
210 | $title = Title::newFromText( $document['localid'] . '/' . $document['language'] ); |
211 | if ( !$title ) { |
212 | // Should not ever happen but who knows... |
213 | continue; |
214 | } |
215 | |
216 | $resultAttribs = [ |
217 | 'class' => 'row tux-message', |
218 | 'data-title' => $title->getPrefixedText(), |
219 | 'data-language' => $document['language'], |
220 | ]; |
221 | |
222 | $handle = new MessageHandle( $title ); |
223 | |
224 | if ( $handle->isValid() ) { |
225 | $uri = Utilities::getEditorUrl( $handle ); |
226 | $link = Html::element( |
227 | 'a', |
228 | [ 'href' => $uri ], |
229 | $this->msg( 'tux-sst-edit' )->text() |
230 | ); |
231 | } else { |
232 | $url = $this->urlUtils->parse( $document['uri'] ); |
233 | if ( !$url ) { |
234 | continue; |
235 | } |
236 | $domain = $url['host']; |
237 | $link = Html::element( |
238 | 'a', |
239 | [ 'href' => $document['uri'] ], |
240 | $this->msg( 'tux-sst-view-foreign', $domain )->text() |
241 | ); |
242 | } |
243 | |
244 | $access = Html::rawElement( |
245 | 'div', |
246 | [ 'class' => 'row tux-edit tux-message-item' ], |
247 | $link |
248 | ); |
249 | |
250 | $titleText = $title->getPrefixedText(); |
251 | $titleAttribs = [ |
252 | 'class' => 'row tux-title', |
253 | 'dir' => 'ltr', |
254 | ]; |
255 | |
256 | $language = $this->languageFactory->getLanguage( $document['language'] ); |
257 | $textAttribs = [ |
258 | 'class' => 'row tux-text', |
259 | 'lang' => $language->getHtmlCode(), |
260 | 'dir' => $language->getDir(), |
261 | ]; |
262 | |
263 | $resultsHtml .= Html::openElement( 'div', $resultAttribs ) |
264 | . Html::rawElement( 'div', $textAttribs, $text ) |
265 | . Html::element( 'div', $titleAttribs, $titleText ) |
266 | . $access |
267 | . Html::closeElement( 'div' ); |
268 | } |
269 | |
270 | $resultsHtml .= Html::rawElement( 'hr', [ 'class' => 'tux-pagination-line' ] ); |
271 | |
272 | $prev = $next = ''; |
273 | $offset = $this->opts->getValue( 'offset' ); |
274 | $params = $this->opts->getChangedValues(); |
275 | |
276 | if ( $total - $offset > $this->limit ) { |
277 | $newParams = [ 'offset' => $offset + $this->limit ] + $params; |
278 | $attribs = [ |
279 | 'class' => 'mw-ui-button pager-next', |
280 | 'href' => $this->getPageTitle()->getLocalURL( $newParams ), |
281 | ]; |
282 | $next = Html::element( 'a', $attribs, $this->msg( 'tux-sst-next' )->text() ); |
283 | } |
284 | if ( $offset ) { |
285 | $newParams = [ 'offset' => max( 0, $offset - $this->limit ) ] + $params; |
286 | $attribs = [ |
287 | 'class' => 'mw-ui-button pager-prev', |
288 | 'href' => $this->getPageTitle()->getLocalURL( $newParams ), |
289 | ]; |
290 | $prev = Html::element( 'a', $attribs, $this->msg( 'tux-sst-prev' )->text() ); |
291 | } |
292 | |
293 | $resultsHtml .= Html::rawElement( 'div', [ 'class' => 'tux-pagination-links' ], |
294 | "$prev $next" |
295 | ); |
296 | |
297 | $count = $this->msg( 'tux-sst-count' )->numParams( $total )->escaped(); |
298 | |
299 | $this->showSearch( $search, $count, $facetHtml, $resultsHtml, $total ); |
300 | } |
301 | |
302 | private function getLanguages( array $facet ): array { |
303 | $output = []; |
304 | |
305 | $nonDefaults = $this->opts->getChangedValues(); |
306 | $selected = $this->opts->getValue( 'language' ); |
307 | $filter = $this->opts->getValue( 'filter' ); |
308 | |
309 | foreach ( $facet as $key => $value ) { |
310 | if ( $filter !== '' && $key === $selected ) { |
311 | unset( $nonDefaults['language'] ); |
312 | unset( $nonDefaults['filter'] ); |
313 | } elseif ( $filter !== '' ) { |
314 | $nonDefaults['language'] = $key; |
315 | $nonDefaults['filter'] = $filter; |
316 | } elseif ( $key === $selected ) { |
317 | unset( $nonDefaults['language'] ); |
318 | } else { |
319 | $nonDefaults['language'] = $key; |
320 | } |
321 | |
322 | $url = $this->getPageTitle()->getLocalURL( $nonDefaults ); |
323 | $value = $this->getLanguage()->formatNum( $value ); |
324 | |
325 | $output[$key] = [ |
326 | 'count' => $value, |
327 | 'url' => $url |
328 | ]; |
329 | } |
330 | |
331 | return $output; |
332 | } |
333 | |
334 | private function getGroups( array $facet ): array { |
335 | $structure = MessageGroups::getGroupStructure(); |
336 | return $this->makeGroupFacetRows( $structure, $facet ); |
337 | } |
338 | |
339 | private function makeGroupFacetRows( |
340 | array $groups, |
341 | array $counts, |
342 | int $level = 0, |
343 | string $pathString = '' |
344 | ): array { |
345 | $output = []; |
346 | |
347 | $nonDefaults = $this->opts->getChangedValues(); |
348 | $selected = $this->opts->getValue( 'group' ); |
349 | $path = explode( '|', $this->opts->getValue( 'grouppath' ) ); |
350 | |
351 | foreach ( $groups as $mixed ) { |
352 | $subgroups = $group = $mixed; |
353 | |
354 | if ( is_array( $mixed ) ) { |
355 | $group = array_shift( $subgroups ); |
356 | } else { |
357 | $subgroups = []; |
358 | } |
359 | '@phan-var \MessageGroup $group'; |
360 | $id = $group->getId(); |
361 | |
362 | if ( $id !== $selected && !isset( $counts[$id] ) ) { |
363 | continue; |
364 | } |
365 | |
366 | if ( $id === $selected ) { |
367 | unset( $nonDefaults['group'] ); |
368 | $nonDefaults['grouppath'] = $pathString; |
369 | } else { |
370 | $nonDefaults['group'] = $id; |
371 | $nonDefaults['grouppath'] = $pathString . $id; |
372 | } |
373 | |
374 | $value = $counts[$id] ?? 0; |
375 | |
376 | $output[$id] = [ |
377 | 'id' => $id, |
378 | 'count' => $value, |
379 | 'label' => $group->getLabel(), |
380 | ]; |
381 | |
382 | if ( isset( $path[$level] ) && $path[$level] === $id ) { |
383 | $output[$id]['groups'] = $this->makeGroupFacetRows( |
384 | $subgroups, |
385 | $counts, |
386 | $level + 1, |
387 | "$pathString$id|" |
388 | ); |
389 | } |
390 | } |
391 | |
392 | return $output; |
393 | } |
394 | |
395 | private function showSearch( |
396 | string $search, |
397 | string $count, |
398 | string $facets, |
399 | string $results, |
400 | int $total |
401 | ): void { |
402 | $messageSelector = $this->messageSelector(); |
403 | $this->getOutput()->addHTML( |
404 | <<<HTML |
405 | <div class="grid tux-searchpage"> |
406 | <div class="row tux-searchboxform"> |
407 | <div class="tux-search-tabs offset-by-three">$messageSelector</div> |
408 | <div class="row tux-search-options"> |
409 | <div class="offset-by-three nine columns tux-search-inputs"> |
410 | <div class="row searchinput">$search</div> |
411 | <div class="row count">$count</div> |
412 | </div> |
413 | </div> |
414 | </div> |
415 | HTML |
416 | ); |
417 | |
418 | $query = trim( $this->opts->getValue( 'query' ) ); |
419 | $hasSpace = preg_match( '/\s/', $query ); |
420 | $match = $this->opts->getValue( 'match' ); |
421 | $size = 100; |
422 | if ( $total > $size && $match !== 'all' && $hasSpace ) { |
423 | $params = $this->opts->getChangedValues(); |
424 | $params = [ 'match' => 'all' ] + $params; |
425 | $linkText = $this->msg( 'tux-sst-link-all-match' )->text(); |
426 | $link = $this->getPageTitle()->getFullURL( $params ); |
427 | $link = "<span class='plainlinks'>[$link $linkText]</span>"; |
428 | |
429 | $out = $this->getOutput(); |
430 | $out->addHTML( |
431 | Html::successBox( |
432 | $out->msg( 'tux-sst-match-message', $link )->parse() |
433 | ) |
434 | ); |
435 | } |
436 | |
437 | $this->getOutput()->addHTML( |
438 | <<<HTML |
439 | <div class="row searchcontent"> |
440 | <div class="three columns facets">$facets</div> |
441 | <div class="nine columns results">$results</div> |
442 | </div> |
443 | </div> |
444 | HTML |
445 | ); |
446 | } |
447 | |
448 | private function showEmptySearch(): void { |
449 | $search = $this->getSearchInput( '' ); |
450 | $this->getOutput()->addHTML( |
451 | <<<HTML |
452 | <div class="grid tux-searchpage"> |
453 | <div class="row searchinput"> |
454 | <div class="nine columns offset-by-three">$search</div> |
455 | </div> |
456 | </div> |
457 | HTML |
458 | ); |
459 | } |
460 | |
461 | private function showSearchError( string $search, Message $message ): void { |
462 | $messageSelector = $this->messageSelector(); |
463 | $messageHTML = Html::errorBox( |
464 | $message->escaped(), |
465 | '', |
466 | 'row' |
467 | ); |
468 | $this->getOutput()->addHTML( |
469 | <<<HTML |
470 | <div class="grid tux-searchpage"> |
471 | <div class="row tux-searchboxform"> |
472 | <div class="tux-search-tabs offset-by-three">$messageSelector</div> |
473 | <div class="row tux-search-options"> |
474 | <div class="offset-by-three nine columns tux-search-inputs"> |
475 | <div class="row searchinput">$search</div> |
476 | $messageHTML |
477 | </div> |
478 | </div> |
479 | </div> |
480 | </div> |
481 | HTML |
482 | ); |
483 | } |
484 | |
485 | /** Build ellipsis to select options */ |
486 | private function ellipsisSelector( string $key, string $value ): string { |
487 | $nonDefaults = $this->opts->getChangedValues(); |
488 | $taskParams = [ 'filter' => $value ] + $nonDefaults; |
489 | ksort( $taskParams ); |
490 | $href = $this->getPageTitle()->getLocalURL( $taskParams ); |
491 | $link = Html::element( 'a', |
492 | [ 'href' => $href ], |
493 | // Messages for grepping: |
494 | // tux-sst-ellipsis-untranslated |
495 | // tux-sst-ellipsis-outdated |
496 | $this->msg( 'tux-sst-ellipsis-' . $key )->text() |
497 | ); |
498 | |
499 | return Html::rawElement( 'li', [ |
500 | 'class' => 'column', |
501 | 'data-filter' => $value, |
502 | 'data-title' => $key, |
503 | ], $link ); |
504 | } |
505 | |
506 | /** Design the tabs */ |
507 | private function messageSelector(): string { |
508 | $nonDefaults = $this->opts->getChangedValues(); |
509 | $output = Html::openElement( 'div', [ 'class' => 'row tux-messagetable-header' ] ); |
510 | $output .= Html::openElement( 'div', [ 'class' => 'twelve columns' ] ); |
511 | $output .= Html::openElement( 'ul', [ 'class' => 'row tux-message-selector' ] ); |
512 | $tabs = [ |
513 | 'default' => '', |
514 | 'translated' => 'translated', |
515 | 'untranslated' => 'untranslated' |
516 | ]; |
517 | |
518 | $ellipsisOptions = [ |
519 | 'outdated' => 'fuzzy' |
520 | ]; |
521 | |
522 | $selected = $this->opts->getValue( 'filter' ); |
523 | if ( in_array( $selected, $ellipsisOptions ) ) { |
524 | $ellipsisOptions = array_slice( $tabs, -1 ); |
525 | |
526 | // Remove the last tab |
527 | array_pop( $tabs ); |
528 | $tabs = array_merge( $tabs, [ 'outdated' => $selected ] ); |
529 | } elseif ( !in_array( $selected, $tabs ) ) { |
530 | $selected = ''; |
531 | } |
532 | |
533 | $container = Html::openElement( 'ul', [ 'class' => 'column tux-message-selector' ] ); |
534 | foreach ( $ellipsisOptions as $optKey => $optValue ) { |
535 | $container .= $this->ellipsisSelector( $optKey, $optValue ); |
536 | } |
537 | |
538 | $sourceLanguage = $this->opts->getValue( 'sourcelanguage' ); |
539 | $sourceLanguage = Utilities::getLanguageName( $sourceLanguage ); |
540 | foreach ( $tabs as $tab => $filter ) { |
541 | // Messages for grepping: |
542 | // tux-sst-default |
543 | // tux-sst-translated |
544 | // tux-sst-untranslated |
545 | // tux-sst-outdated |
546 | $tabClass = "tux-sst-$tab"; |
547 | $taskParams = [ 'filter' => $filter ] + $nonDefaults; |
548 | ksort( $taskParams ); |
549 | $href = $this->getPageTitle()->getLocalURL( $taskParams ); |
550 | if ( $tab === 'default' ) { |
551 | $link = Html::element( |
552 | 'a', |
553 | [ 'href' => $href ], |
554 | $this->msg( $tabClass )->text() |
555 | ); |
556 | } else { |
557 | $link = Html::element( |
558 | 'a', |
559 | [ 'href' => $href ], |
560 | $this->msg( $tabClass, $sourceLanguage )->text() |
561 | ); |
562 | } |
563 | |
564 | if ( $selected === $filter ) { |
565 | $tabClass .= ' selected'; |
566 | } |
567 | $output .= Html::rawElement( 'li', [ |
568 | 'class' => [ 'column', $tabClass ], |
569 | 'data-filter' => $filter, |
570 | 'data-title' => $tab, |
571 | ], $link ); |
572 | } |
573 | |
574 | // More column |
575 | $output .= Html::rawElement( 'li', [ 'class' => 'column more' ], '...' . $container ); |
576 | $output .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' ) . Html::closeElement( 'div' ); |
577 | |
578 | return $output; |
579 | } |
580 | |
581 | private function getSearchInput( string $query ): string { |
582 | $attribs = [ |
583 | 'placeholder' => $this->msg( 'tux-sst-search-ph' )->text(), |
584 | 'class' => 'searchinputbox mw-ui-input', |
585 | 'dir' => $this->getLanguage()->getDir() |
586 | ]; |
587 | |
588 | $title = Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ); |
589 | $input = Html::input( 'query', $query, 'text', $attribs ); |
590 | $submit = Html::submitButton( |
591 | $this->msg( 'tux-sst-search' )->text(), |
592 | [ 'class' => 'mw-ui-button mw-ui-progressive' ] |
593 | ); |
594 | |
595 | $typeHint = Html::rawElement( |
596 | 'div', |
597 | [ 'class' => 'tux-searchinputbox-hint' ], |
598 | $this->msg( 'tux-sst-search-info' )->parse() |
599 | ); |
600 | |
601 | $nonDefaults = $this->opts->getChangedValues(); |
602 | $checkLabel = Html::element( 'input', [ |
603 | 'type' => 'checkbox', 'name' => 'case', 'value' => '1', |
604 | 'checked' => isset( $nonDefaults['case'] ), |
605 | 'id' => 'tux-case-sensitive', |
606 | ] ) . "\u{00A0}" . Html::label( |
607 | $this->msg( 'tux-sst-case-sensitive' )->text(), |
608 | 'tux-case-sensitive' |
609 | ); |
610 | $checkLabel = Html::rawElement( |
611 | 'div', |
612 | [ 'class' => 'tux-search-operators mw-ui-checkbox' ], |
613 | $checkLabel |
614 | ); |
615 | |
616 | $lang = $this->getRequest()->getVal( 'language' ); |
617 | $language = $lang === null ? '' : Html::hidden( 'language', $lang ); |
618 | |
619 | return Html::rawElement( |
620 | 'form', |
621 | [ 'action' => wfScript(), 'name' => 'searchform' ], |
622 | $title . $input . $submit . $typeHint . $checkLabel . $language |
623 | ); |
624 | } |
625 | |
626 | protected function getGroupName() { |
627 | return 'translation'; |
628 | } |
629 | } |