Merge "Localize some special pages into Korean (ko)"
[lhc/web/wiklou.git] / includes / widget / search / SearchFormWidget.php
1 <?php
2
3 namespace MediaWiki\Widget\Search;
4
5 use Hooks;
6 use Html;
7 use MediaWiki\Widget\SearchInputWidget;
8 use MWNamespace;
9 use SearchEngineConfig;
10 use SpecialSearch;
11 use Xml;
12
13 class SearchFormWidget {
14 /** @var SpecialSearch */
15 protected $specialSearch;
16 /** @var SearchEngineConfig */
17 protected $searchConfig;
18 /** @var array */
19 protected $profiles;
20
21 /**
22 * @param SpecialSearch $specialSearch
23 * @param SearchEngineConfig $searchConfig
24 * @param array $profiles
25 */
26 public function __construct(
27 SpecialSearch $specialSearch,
28 SearchEngineConfig $searchConfig,
29 array $profiles
30 ) {
31 $this->specialSearch = $specialSearch;
32 $this->searchConfig = $searchConfig;
33 $this->profiles = $profiles;
34 }
35
36 /**
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 * @return string HTML
44 */
45 public function render(
46 $profile,
47 $term,
48 $numResults,
49 $totalResults,
50 $offset,
51 $isPowerSearch
52 ) {
53 return '<div class="mw-search-form-wrapper">' .
54 Xml::openElement(
55 'form',
56 [
57 'id' => $isPowerSearch ? 'powersearch' : 'search',
58 'method' => 'get',
59 'action' => wfScript(),
60 ]
61 ) .
62 '<div id="mw-search-top-table">' .
63 $this->shortDialogHtml( $profile, $term, $numResults, $totalResults, $offset ) .
64 '</div>' .
65 "<div class='mw-search-visualclear'></div>" .
66 "<div class='mw-search-profile-tabs'>" .
67 $this->profileTabsHtml( $profile, $term ) .
68 "<div style='clear:both'></div>" .
69 "</div>" .
70 $this->optionsHtml( $term, $isPowerSearch, $profile ) .
71 '</form>' .
72 '</div>';
73 }
74
75 /**
76 * @param string $profile The current search profile
77 * @param string $term The current search term
78 * @param int $numResults The number of results shown
79 * @param int $totalResults The total estimated results found
80 * @param int $offset Current offset in search results
81 * @return string HTML
82 */
83 protected function shortDialogHtml( $profile, $term, $numResults, $totalResults, $offset ) {
84 $html = '';
85
86 $searchWidget = new SearchInputWidget( [
87 'id' => 'searchText',
88 'name' => 'search',
89 'autofocus' => trim( $term ) === '',
90 'value' => $term,
91 'dataLocation' => 'content',
92 'infusable' => true,
93 ] );
94
95 $layout = new \OOUI\ActionFieldLayout( $searchWidget, new \OOUI\ButtonInputWidget( [
96 'type' => 'submit',
97 'label' => $this->specialSearch->msg( 'searchbutton' )->text(),
98 'flags' => [ 'progressive', 'primary' ],
99 ] ), [
100 'align' => 'top',
101 ] );
102
103 $html .= $layout;
104
105 if ( $this->specialSearch->getPrefix() !== '' ) {
106 $html .= Html::hidden( 'prefix', $this->specialSearch->getPrefix() );
107 }
108
109 if ( $totalResults > 0 && $offset < $totalResults ) {
110 $html .= Xml::tags(
111 'div',
112 [
113 'class' => 'results-info',
114 'data-mw-num-results-offset' => $offset,
115 'data-mw-num-results-total' => $totalResults
116 ],
117 $this->specialSearch->msg( 'search-showingresults' )
118 ->numParams( $offset + 1, $offset + $numResults, $totalResults )
119 ->numParams( $numResults )
120 ->parse()
121 );
122 }
123
124 $html .=
125 Html::hidden( 'title', $this->specialSearch->getPageTitle()->getPrefixedText() ) .
126 Html::hidden( 'profile', $profile ) .
127 Html::hidden( 'fulltext', '1' );
128
129 return $html;
130 }
131
132 /**
133 * Generates HTML for the list of available search profiles.
134 *
135 * @param string $profile The currently selected profile
136 * @param string $term The user provided search terms
137 * @return string HTML
138 */
139 protected function profileTabsHtml( $profile, $term ) {
140 $bareterm = $this->startsWithImage( $term )
141 ? substr( $term, strpos( $term, ':' ) + 1 )
142 : $term;
143 $lang = $this->specialSearch->getLanguage();
144 $items = [];
145 foreach ( $this->profiles as $id => $profileConfig ) {
146 $profileConfig['parameters']['profile'] = $id;
147 $tooltipParam = isset( $profileConfig['namespace-messages'] )
148 ? $lang->commaList( $profileConfig['namespace-messages'] )
149 : null;
150 $items[] = Xml::tags(
151 'li',
152 [ 'class' => $profile === $id ? 'current' : 'normal' ],
153 $this->makeSearchLink(
154 $bareterm,
155 $this->specialSearch->msg( $profileConfig['message'] )->text(),
156 $this->specialSearch->msg( $profileConfig['tooltip'], $tooltipParam )->text(),
157 $profileConfig['parameters']
158 )
159 );
160 }
161
162 return "<div class='search-types'>" .
163 "<ul>" . implode( '', $items ) . "</ul>" .
164 "</div>";
165 }
166
167 /**
168 * Check if query starts with image: prefix
169 *
170 * @param string $term The string to check
171 * @return bool
172 */
173 protected function startsWithImage( $term ) {
174 global $wgContLang;
175
176 $parts = explode( ':', $term );
177 return count( $parts ) > 1
178 ? $wgContLang->getNsIndex( $parts[0] ) === NS_FILE
179 : false;
180 }
181
182 /**
183 * Make a search link with some target namespaces
184 *
185 * @param string $term The term to search for
186 * @param string $label Link's text
187 * @param string $tooltip Link's tooltip
188 * @param array $params Query string parameters
189 * @return string HTML fragment
190 */
191 protected function makeSearchLink( $term, $label, $tooltip, array $params = [] ) {
192 $params += [
193 'search' => $term,
194 'fulltext' => 1,
195 ];
196
197 return Xml::element(
198 'a',
199 [
200 'href' => $this->specialSearch->getPageTitle()->getLocalURL( $params ),
201 'title' => $tooltip,
202 ],
203 $label
204 );
205 }
206
207 /**
208 * Generates HTML for advanced options available with the currently
209 * selected search profile.
210 *
211 * @param string $term User provided search term
212 * @param bool $isPowerSearch Is the advanced search profile enabled?
213 * @param string $profile The current search profile
214 * @return string HTML
215 */
216 protected function optionsHtml( $term, $isPowerSearch, $profile ) {
217 $html = '';
218
219 if ( $isPowerSearch ) {
220 $html .= $this->powerSearchBox( $term, [] );
221 } else {
222 $form = '';
223 Hooks::run( 'SpecialSearchProfileForm', [
224 $this->specialSearch, &$form, $profile, $term, []
225 ] );
226 $html .= $form;
227 }
228
229 return $html;
230 }
231
232 /**
233 * @param string $term The current search term
234 * @param array $opts Additional key/value pairs that will be submitted
235 * with the generated form.
236 * @return string HTML
237 */
238 protected function powerSearchBox( $term, array $opts ) {
239 global $wgContLang;
240
241 $rows = [];
242 $activeNamespaces = $this->specialSearch->getNamespaces();
243 foreach ( $this->searchConfig->searchableNamespaces() as $namespace => $name ) {
244 $subject = MWNamespace::getSubject( $namespace );
245 if ( !isset( $rows[$subject] ) ) {
246 $rows[$subject] = "";
247 }
248
249 $name = $wgContLang->getConverter()->convertNamespace( $namespace );
250 if ( $name === '' ) {
251 $name = $this->specialSearch->msg( 'blanknamespace' )->text();
252 }
253
254 $rows[$subject] .=
255 '<td>' .
256 Xml::checkLabel(
257 $name,
258 "ns{$namespace}",
259 "mw-search-ns{$namespace}",
260 in_array( $namespace, $activeNamespaces )
261 ) .
262 '</td>';
263 }
264
265 // Lays out namespaces in multiple floating two-column tables so they'll
266 // be arranged nicely while still accomodating diferent screen widths
267 $tableRows = [];
268 foreach ( $rows as $row ) {
269 $tableRows[] = "<tr>{$row}</tr>";
270 }
271 $namespaceTables = [];
272 foreach ( array_chunk( $tableRows, 4 ) as $chunk ) {
273 $namespaceTables[] = implode( '', $chunk );
274 }
275
276 $showSections = [
277 'namespaceTables' => "<table>" . implode( '</table><table>', $namespaceTables ) . '</table>',
278 ];
279 Hooks::run( 'SpecialSearchPowerBox', [ &$showSections, $term, &$opts ] );
280
281 $hidden = '';
282 foreach ( $opts as $key => $value ) {
283 $hidden .= Html::hidden( $key, $value );
284 }
285
286 $divider = "<div class='divider'></div>";
287
288 // Stuff to feed SpecialSearch::saveNamespaces()
289 $user = $this->specialSearch->getUser();
290 $remember = '';
291 if ( $user->isLoggedIn() ) {
292 $remember = $divider . Xml::checkLabel(
293 $this->specialSearch->msg( 'powersearch-remember' )->text(),
294 'nsRemember',
295 'mw-search-powersearch-remember',
296 false,
297 // The token goes here rather than in a hidden field so it
298 // is only sent when necessary (not every form submission)
299 [ 'value' => $user->getEditToken(
300 'searchnamespace',
301 $this->specialSearch->getRequest()
302 ) ]
303 );
304 }
305
306 return "<fieldset id='mw-searchoptions'>" .
307 "<legend>" . $this->specialSearch->msg( 'powersearch-legend' )->escaped() . '</legend>' .
308 "<h4>" . $this->specialSearch->msg( 'powersearch-ns' )->parse() . '</h4>' .
309 // populated by js if available
310 "<div id='mw-search-togglebox'></div>" .
311 $divider .
312 implode(
313 $divider,
314 $showSections
315 ) .
316 $hidden .
317 $remember .
318 "</fieldset>";
319 }
320 }