3 namespace MediaWiki\Widget\Search
;
7 use MediaWiki\MediaWikiServices
;
8 use MediaWiki\Widget\SearchInputWidget
;
9 use SearchEngineConfig
;
13 class SearchFormWidget
{
14 /** @var SpecialSearch */
15 protected $specialSearch;
16 /** @var SearchEngineConfig */
17 protected $searchConfig;
22 * @param SpecialSearch $specialSearch
23 * @param SearchEngineConfig $searchConfig
24 * @param array $profiles
26 public function __construct(
27 SpecialSearch
$specialSearch,
28 SearchEngineConfig
$searchConfig,
31 $this->specialSearch
= $specialSearch;
32 $this->searchConfig
= $searchConfig;
33 $this->profiles
= $profiles;
37 * @param string $profile The current search profile
38 * @param string $term The current search term
39 * @param int $numResults The number of results shown
40 * @param int $totalResults The total estimated results found
41 * @param int $offset Current offset in search results
42 * @param bool $isPowerSearch Is the 'advanced' section open?
43 * @param array $options Widget options
46 public function render(
55 $user = $this->specialSearch
->getUser();
57 return '<div class="mw-search-form-wrapper">' .
61 'id' => $isPowerSearch ?
'powersearch' : 'search',
62 // T151903: default to POST in case JS is disabled
63 'method' => ( $isPowerSearch && $user->isLoggedIn() ) ?
'post' : 'get',
64 'action' => wfScript(),
67 '<div id="mw-search-top-table">' .
68 $this->shortDialogHtml( $profile, $term, $numResults, $totalResults, $offset, $options ) .
70 "<div class='mw-search-visualclear'></div>" .
71 "<div class='mw-search-profile-tabs'>" .
72 $this->profileTabsHtml( $profile, $term ) .
73 "<div style='clear:both'></div>" .
75 $this->optionsHtml( $term, $isPowerSearch, $profile ) .
81 * @param string $profile The current search profile
82 * @param string $term The current search term
83 * @param int $numResults The number of results shown
84 * @param int $totalResults The total estimated results found
85 * @param int $offset Current offset in search results
86 * @param array $options Widget options
89 protected function shortDialogHtml(
99 $searchWidget = new SearchInputWidget( $options +
[
100 'id' => 'searchText',
102 'autofocus' => trim( $term ) === '',
104 'dataLocation' => 'content',
108 $layout = new \OOUI\
ActionFieldLayout( $searchWidget, new \OOUI\
ButtonInputWidget( [
110 'label' => $this->specialSearch
->msg( 'searchbutton' )->text(),
111 'flags' => [ 'progressive', 'primary' ],
118 if ( $this->specialSearch
->getPrefix() !== '' ) {
119 $html .= Html
::hidden( 'prefix', $this->specialSearch
->getPrefix() );
122 if ( $totalResults > 0 && $offset < $totalResults ) {
126 'class' => 'results-info',
127 'data-mw-num-results-offset' => $offset,
128 'data-mw-num-results-total' => $totalResults
130 $this->specialSearch
->msg( 'search-showingresults' )
131 ->numParams( $offset +
1, $offset +
$numResults, $totalResults )
132 ->numParams( $numResults )
138 Html
::hidden( 'title', $this->specialSearch
->getPageTitle()->getPrefixedText() ) .
139 Html
::hidden( 'profile', $profile ) .
140 Html
::hidden( 'fulltext', '1' );
146 * Generates HTML for the list of available search profiles.
148 * @param string $profile The currently selected profile
149 * @param string $term The user provided search terms
150 * @return string HTML
151 * @suppress PhanTypeArraySuspiciousNullable
153 protected function profileTabsHtml( $profile, $term ) {
154 $bareterm = $this->startsWithImage( $term )
155 ?
substr( $term, strpos( $term, ':' ) +
1 )
157 $lang = $this->specialSearch
->getLanguage();
159 foreach ( $this->profiles
as $id => $profileConfig ) {
160 $profileConfig['parameters']['profile'] = $id;
161 $tooltipParam = isset( $profileConfig['namespace-messages'] )
162 ?
$lang->commaList( $profileConfig['namespace-messages'] )
164 $items[] = Xml
::tags(
166 [ 'class' => $profile === $id ?
'current' : 'normal' ],
167 $this->makeSearchLink(
169 $this->specialSearch
->msg( $profileConfig['message'] )->text(),
170 $this->specialSearch
->msg( $profileConfig['tooltip'], $tooltipParam )->text(),
171 $profileConfig['parameters']
176 return "<div class='search-types'>" .
177 "<ul>" . implode( '', $items ) . "</ul>" .
182 * Check if query starts with image: prefix
184 * @param string $term The string to check
187 protected function startsWithImage( $term ) {
188 $parts = explode( ':', $term );
189 return count( $parts ) > 1
190 ? MediaWikiServices
::getInstance()->getContentLanguage()->getNsIndex( $parts[0] ) ===
196 * Make a search link with some target namespaces
198 * @param string $term The term to search for
199 * @param string $label Link's text
200 * @param string $tooltip Link's tooltip
201 * @param array $params Query string parameters
202 * @return string HTML fragment
204 protected function makeSearchLink( $term, $label, $tooltip, array $params = [] ) {
213 'href' => $this->specialSearch
->getPageTitle()->getLocalURL( $params ),
221 * Generates HTML for advanced options available with the currently
222 * selected search profile.
224 * @param string $term User provided search term
225 * @param bool $isPowerSearch Is the advanced search profile enabled?
226 * @param string $profile The current search profile
227 * @return string HTML
229 protected function optionsHtml( $term, $isPowerSearch, $profile ) {
232 if ( $isPowerSearch ) {
233 $html .= $this->powerSearchBox( $term, [] );
236 Hooks
::run( 'SpecialSearchProfileForm', [
237 $this->specialSearch
, &$form, $profile, $term, []
246 * @param string $term The current search term
247 * @param array $opts Additional key/value pairs that will be submitted
248 * with the generated form.
249 * @return string HTML
251 protected function powerSearchBox( $term, array $opts ) {
253 $activeNamespaces = $this->specialSearch
->getNamespaces();
254 $langConverter = $this->specialSearch
->getLanguage();
255 foreach ( $this->searchConfig
->searchableNamespaces() as $namespace => $name ) {
256 $subject = MediaWikiServices
::getInstance()->getNamespaceInfo()->
257 getSubject( $namespace );
258 if ( !isset( $rows[$subject] ) ) {
259 $rows[$subject] = "";
262 $name = $langConverter->convertNamespace( $namespace );
263 if ( $name === '' ) {
264 $name = $this->specialSearch
->msg( 'blanknamespace' )->text();
272 "mw-search-ns{$namespace}",
273 in_array( $namespace, $activeNamespaces )
278 // Lays out namespaces in multiple floating two-column tables so they'll
279 // be arranged nicely while still accomodating diferent screen widths
281 foreach ( $rows as $row ) {
282 $tableRows[] = "<tr>{$row}</tr>";
284 $namespaceTables = [];
285 foreach ( array_chunk( $tableRows, 4 ) as $chunk ) {
286 $namespaceTables[] = implode( '', $chunk );
290 'namespaceTables' => "<table>" . implode( '</table><table>', $namespaceTables ) . '</table>',
292 Hooks
::run( 'SpecialSearchPowerBox', [ &$showSections, $term, &$opts ] );
295 foreach ( $opts as $key => $value ) {
296 $hidden .= Html
::hidden( $key, $value );
299 $divider = "<div class='divider'></div>";
301 // Stuff to feed SpecialSearch::saveNamespaces()
302 $user = $this->specialSearch
->getUser();
304 if ( $user->isLoggedIn() ) {
305 $remember = $divider . Xml
::checkLabel(
306 $this->specialSearch
->msg( 'powersearch-remember' )->text(),
308 'mw-search-powersearch-remember',
310 // The token goes here rather than in a hidden field so it
311 // is only sent when necessary (not every form submission)
312 [ 'value' => $user->getEditToken(
314 $this->specialSearch
->getRequest()
319 return "<fieldset id='mw-searchoptions'>" .
320 "<legend>" . $this->specialSearch
->msg( 'powersearch-legend' )->escaped() . '</legend>' .
321 "<h4>" . $this->specialSearch
->msg( 'powersearch-ns' )->parse() . '</h4>' .
322 // Handled by JavaScript if available
323 '<div id="mw-search-togglebox">' .
324 '<label>' . $this->specialSearch
->msg( 'powersearch-togglelabel' )->escaped() . '</label>' .
325 '<input type="button" id="mw-search-toggleall" value="' .
326 $this->specialSearch
->msg( 'powersearch-toggleall' )->escaped() . '"/>' .
327 '<input type="button" id="mw-search-togglenone" value="' .
328 $this->specialSearch
->msg( 'powersearch-togglenone' )->escaped() . '"/>' .