]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/specials/SpecialSearch.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / specials / SpecialSearch.php
1 <?php
2 /**
3  * Implements Special:Search
4  *
5  * Copyright © 2004 Brion Vibber <brion@pobox.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  * http://www.gnu.org/copyleft/gpl.html
21  *
22  * @file
23  * @ingroup SpecialPage
24  */
25
26 use MediaWiki\MediaWikiServices;
27 use MediaWiki\Widget\Search\BasicSearchResultSetWidget;
28 use MediaWiki\Widget\Search\FullSearchResultWidget;
29 use MediaWiki\Widget\Search\InterwikiSearchResultWidget;
30 use MediaWiki\Widget\Search\InterwikiSearchResultSetWidget;
31 use MediaWiki\Widget\Search\SimpleSearchResultWidget;
32 use MediaWiki\Widget\Search\SimpleSearchResultSetWidget;
33
34 /**
35  * implements Special:Search - Run text & title search and display the output
36  * @ingroup SpecialPage
37  */
38 class SpecialSearch extends SpecialPage {
39         /**
40          * Current search profile. Search profile is just a name that identifies
41          * the active search tab on the search page (content, discussions...)
42          * For users tt replaces the set of enabled namespaces from the query
43          * string when applicable. Extensions can add new profiles with hooks
44          * with custom search options just for that profile.
45          * @var null|string
46          */
47         protected $profile;
48
49         /** @var SearchEngine Search engine */
50         protected $searchEngine;
51
52         /** @var string Search engine type, if not default */
53         protected $searchEngineType;
54
55         /** @var array For links */
56         protected $extraParams = [];
57
58         /**
59          * @var string The prefix url parameter. Set on the searcher and the
60          * is expected to treat it as prefix filter on titles.
61          */
62         protected $mPrefix;
63
64         /**
65          * @var int
66          */
67         protected $limit, $offset;
68
69         /**
70          * @var array
71          */
72         protected $namespaces;
73
74         /**
75          * @var string
76          */
77         protected $fulltext;
78
79         /**
80          * @var bool
81          */
82         protected $runSuggestion = true;
83
84         /**
85          * Search engine configurations.
86          * @var SearchEngineConfig
87          */
88         protected $searchConfig;
89
90         const NAMESPACES_CURRENT = 'sense';
91
92         public function __construct() {
93                 parent::__construct( 'Search' );
94                 $this->searchConfig = MediaWikiServices::getInstance()->getSearchEngineConfig();
95         }
96
97         /**
98          * Entry point
99          *
100          * @param string $par
101          */
102         public function execute( $par ) {
103                 $request = $this->getRequest();
104                 $out = $this->getOutput();
105
106                 // Fetch the search term
107                 $term = str_replace( "\n", " ", $request->getText( 'search' ) );
108
109                 // Historically search terms have been accepted not only in the search query
110                 // parameter, but also as part of the primary url. This can have PII implications
111                 // in releasing page view data. As such issue a 301 redirect to the correct
112                 // URL.
113                 if ( strlen( $par ) && !strlen( $term ) ) {
114                         $query = $request->getValues();
115                         unset( $query['title'] );
116                         // Strip underscores from title parameter; most of the time we'll want
117                         // text form here. But don't strip underscores from actual text params!
118                         $query['search'] = str_replace( '_', ' ', $par );
119                         $out->redirect( $this->getPageTitle()->getFullURL( $query ), 301 );
120                         return;
121                 }
122
123                 // Need to load selected namespaces before handling nsRemember
124                 $this->load();
125                 // TODO: This performs database actions on GET request, which is going to
126                 // be a problem for our multi-datacenter work.
127                 if ( !is_null( $request->getVal( 'nsRemember' ) ) ) {
128                         $this->saveNamespaces();
129                         // Remove the token from the URL to prevent the user from inadvertently
130                         // exposing it (e.g. by pasting it into a public wiki page) or undoing
131                         // later settings changes (e.g. by reloading the page).
132                         $query = $request->getValues();
133                         unset( $query['title'], $query['nsRemember'] );
134                         $out->redirect( $this->getPageTitle()->getFullURL( $query ) );
135                         return;
136                 }
137
138                 $this->searchEngineType = $request->getVal( 'srbackend' );
139                 if (
140                         !$request->getVal( 'fulltext' ) &&
141                         $request->getVal( 'offset' ) === null
142                 ) {
143                         $url = $this->goResult( $term );
144                         if ( $url !== null ) {
145                                 // successful 'go'
146                                 $out->redirect( $url );
147                                 return;
148                         }
149                         // No match. If it could plausibly be a title
150                         // run the No go match hook.
151                         $title = Title::newFromText( $term );
152                         if ( !is_null( $title ) ) {
153                                 Hooks::run( 'SpecialSearchNogomatch', [ &$title ] );
154                         }
155                 }
156
157                 $this->setupPage( $term );
158
159                 if ( $this->getConfig()->get( 'DisableTextSearch' ) ) {
160                         $searchForwardUrl = $this->getConfig()->get( 'SearchForwardUrl' );
161                         if ( $searchForwardUrl ) {
162                                 $url = str_replace( '$1', urlencode( $term ), $searchForwardUrl );
163                                 $out->redirect( $url );
164                         } else {
165                                 $out->addHTML(
166                                         "<fieldset>" .
167                                                 "<legend>" .
168                                                         $this->msg( 'search-external' )->escaped() .
169                                                 "</legend>" .
170                                                 "<p class='mw-searchdisabled'>" .
171                                                         $this->msg( 'searchdisabled' )->escaped() .
172                                                 "</p>" .
173                                                 $this->msg( 'googlesearch' )->rawParams(
174                                                         htmlspecialchars( $term ),
175                                                         'UTF-8',
176                                                         $this->msg( 'searchbutton' )->escaped()
177                                                 )->text() .
178                                         "</fieldset>"
179                                 );
180                         }
181
182                         return;
183                 }
184
185                 $this->showResults( $term );
186         }
187
188         /**
189          * Set up basic search parameters from the request and user settings.
190          *
191          * @see tests/phpunit/includes/specials/SpecialSearchTest.php
192          */
193         public function load() {
194                 $request = $this->getRequest();
195                 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, '' );
196                 $this->mPrefix = $request->getVal( 'prefix', '' );
197
198                 $user = $this->getUser();
199
200                 # Extract manually requested namespaces
201                 $nslist = $this->powerSearch( $request );
202                 if ( !count( $nslist ) ) {
203                         # Fallback to user preference
204                         $nslist = $this->searchConfig->userNamespaces( $user );
205                 }
206
207                 $profile = null;
208                 if ( !count( $nslist ) ) {
209                         $profile = 'default';
210                 }
211
212                 $profile = $request->getVal( 'profile', $profile );
213                 $profiles = $this->getSearchProfiles();
214                 if ( $profile === null ) {
215                         // BC with old request format
216                         $profile = 'advanced';
217                         foreach ( $profiles as $key => $data ) {
218                                 if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) {
219                                         $profile = $key;
220                                 }
221                         }
222                         $this->namespaces = $nslist;
223                 } elseif ( $profile === 'advanced' ) {
224                         $this->namespaces = $nslist;
225                 } else {
226                         if ( isset( $profiles[$profile]['namespaces'] ) ) {
227                                 $this->namespaces = $profiles[$profile]['namespaces'];
228                         } else {
229                                 // Unknown profile requested
230                                 $profile = 'default';
231                                 $this->namespaces = $profiles['default']['namespaces'];
232                         }
233                 }
234
235                 $this->fulltext = $request->getVal( 'fulltext' );
236                 $this->runSuggestion = (bool)$request->getVal( 'runsuggestion', true );
237                 $this->profile = $profile;
238         }
239
240         /**
241          * If an exact title match can be found, jump straight ahead to it.
242          *
243          * @param string $term
244          * @return string|null The url to redirect to, or null if no redirect.
245          */
246         public function goResult( $term ) {
247                 # If the string cannot be used to create a title
248                 if ( is_null( Title::newFromText( $term ) ) ) {
249                         return null;
250                 }
251                 # If there's an exact or very near match, jump right there.
252                 $title = $this->getSearchEngine()
253                         ->getNearMatcher( $this->getConfig() )->getNearMatch( $term );
254                 if ( is_null( $title ) ) {
255                         return null;
256                 }
257                 $url = null;
258                 if ( !Hooks::run( 'SpecialSearchGoResult', [ $term, $title, &$url ] ) ) {
259                         return null;
260                 }
261
262                 return $url === null ? $title->getFullUrlForRedirect() : $url;
263         }
264
265         /**
266          * @param string $term
267          */
268         public function showResults( $term ) {
269                 global $wgContLang;
270
271                 if ( $this->searchEngineType !== null ) {
272                         $this->setExtraParam( 'srbackend', $this->searchEngineType );
273                 }
274
275                 $out = $this->getOutput();
276                 $formWidget = new MediaWiki\Widget\Search\SearchFormWidget(
277                         $this,
278                         $this->searchConfig,
279                         $this->getSearchProfiles()
280                 );
281                 $filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
282                 if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
283                         // Empty query -- straight view of search form
284                         if ( !Hooks::run( 'SpecialSearchResultsPrepend', [ $this, $out, $term ] ) ) {
285                                 # Hook requested termination
286                                 return;
287                         }
288                         $out->enableOOUI();
289                         // The form also contains the 'Showing results 0 - 20 of 1234' so we can
290                         // only do the form render here for the empty $term case. Rendering
291                         // the form when a search is provided is repeated below.
292                         $out->addHTML( $formWidget->render(
293                                 $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch()
294                         ) );
295                         return;
296                 }
297
298                 $search = $this->getSearchEngine();
299                 $search->setFeatureData( 'rewrite', $this->runSuggestion );
300                 $search->setLimitOffset( $this->limit, $this->offset );
301                 $search->setNamespaces( $this->namespaces );
302                 $search->prefix = $this->mPrefix;
303                 $term = $search->transformSearchTerm( $term );
304
305                 Hooks::run( 'SpecialSearchSetupEngine', [ $this, $this->profile, $search ] );
306                 if ( !Hooks::run( 'SpecialSearchResultsPrepend', [ $this, $out, $term ] ) ) {
307                         # Hook requested termination
308                         return;
309                 }
310
311                 $title = Title::newFromText( $term );
312                 $showSuggestion = $title === null || !$title->isKnown();
313                 $search->setShowSuggestion( $showSuggestion );
314
315                 // fetch search results
316                 $rewritten = $search->replacePrefixes( $term );
317
318                 $titleMatches = $search->searchTitle( $rewritten );
319                 $textMatches = $search->searchText( $rewritten );
320
321                 $textStatus = null;
322                 if ( $textMatches instanceof Status ) {
323                         $textStatus = $textMatches;
324                         $textMatches = $textStatus->getValue();
325                 }
326
327                 // Get number of results
328                 $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
329                 if ( $titleMatches ) {
330                         $titleMatchesNum = $titleMatches->numRows();
331                         $numTitleMatches = $titleMatches->getTotalHits();
332                 }
333                 if ( $textMatches ) {
334                         $textMatchesNum = $textMatches->numRows();
335                         $numTextMatches = $textMatches->getTotalHits();
336                         if ( $textMatchesNum > 0 ) {
337                                 $search->augmentSearchResults( $textMatches );
338                         }
339                 }
340                 $num = $titleMatchesNum + $textMatchesNum;
341                 $totalRes = $numTitleMatches + $numTextMatches;
342
343                 // start rendering the page
344                 $out->enableOOUI();
345                 $out->addHTML( $formWidget->render(
346                         $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch()
347                 ) );
348
349                 // did you mean... suggestions
350                 if ( $textMatches ) {
351                         $dymWidget = new MediaWiki\Widget\Search\DidYouMeanWidget( $this );
352                         $out->addHTML( $dymWidget->render( $term, $textMatches ) );
353                 }
354
355                 $hasErrors = $textStatus && $textStatus->getErrors() !== [];
356                 $hasOtherResults = $textMatches &&
357                         $textMatches->hasInterwikiResults( SearchResultSet::INLINE_RESULTS );
358
359                 if ( $textMatches && $textMatches->hasInterwikiResults( SearchResultSet::SECONDARY_RESULTS ) ) {
360                         $out->addHTML( '<div class="searchresults mw-searchresults-has-iw">' );
361                 } else {
362                         $out->addHTML( '<div class="searchresults">' );
363                 }
364
365                 if ( $hasErrors ) {
366                         list( $error, $warning ) = $textStatus->splitByErrorType();
367                         if ( $error->getErrors() ) {
368                                 $out->addHTML( Html::rawElement(
369                                         'div',
370                                         [ 'class' => 'errorbox' ],
371                                         $error->getHTML( 'search-error' )
372                                 ) );
373                         }
374                         if ( $warning->getErrors() ) {
375                                 $out->addHTML( Html::rawElement(
376                                         'div',
377                                         [ 'class' => 'warningbox' ],
378                                         $warning->getHTML( 'search-warning' )
379                                 ) );
380                         }
381                 }
382
383                 // Show the create link ahead
384                 $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
385
386                 Hooks::run( 'SpecialSearchResults', [ $term, &$titleMatches, &$textMatches ] );
387
388                 // If we have no results and have not already displayed an error message
389                 if ( $num === 0 && !$hasErrors ) {
390                         $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
391                                 $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
392                                 wfEscapeWikiText( $term )
393                         ] );
394                 }
395
396                 // Although $num might be 0 there can still be secondary or inline
397                 // results to display.
398                 $linkRenderer = $this->getLinkRenderer();
399                 $mainResultWidget = new FullSearchResultWidget( $this, $linkRenderer );
400
401                 if ( $search->getFeatureData( 'enable-new-crossproject-page' ) ) {
402                         $sidebarResultWidget = new InterwikiSearchResultWidget( $this, $linkRenderer );
403                         $sidebarResultsWidget = new InterwikiSearchResultSetWidget(
404                                 $this,
405                                 $sidebarResultWidget,
406                                 $linkRenderer,
407                                 MediaWikiServices::getInstance()->getInterwikiLookup(),
408                                 $search->getFeatureData( 'show-multimedia-search-results' )
409                         );
410                 } else {
411                         $sidebarResultWidget = new SimpleSearchResultWidget( $this, $linkRenderer );
412                         $sidebarResultsWidget = new SimpleSearchResultSetWidget(
413                                 $this,
414                                 $sidebarResultWidget,
415                                 $linkRenderer,
416                                 MediaWikiServices::getInstance()->getInterwikiLookup()
417                         );
418                 }
419
420                 $widget = new BasicSearchResultSetWidget( $this, $mainResultWidget, $sidebarResultsWidget );
421
422                 $out->addHTML( $widget->render(
423                         $term, $this->offset, $titleMatches, $textMatches
424                 ) );
425
426                 if ( $titleMatches ) {
427                         $titleMatches->free();
428                 }
429
430                 if ( $textMatches ) {
431                         $textMatches->free();
432                 }
433
434                 $out->addHTML( '<div class="mw-search-visualclear"></div>' );
435
436                 // prev/next links
437                 if ( $totalRes > $this->limit || $this->offset ) {
438                         // Allow matches to define the correct offset, as interleaved
439                         // AB testing may require a different next page offset.
440                         if ( $textMatches && $textMatches->getOffset() !== null ) {
441                                 $offset = $textMatches->getOffset();
442                         } else {
443                                 $offset = $this->offset;
444                         }
445
446                         $prevnext = $this->getLanguage()->viewPrevNext(
447                                 $this->getPageTitle(),
448                                 $offset,
449                                 $this->limit,
450                                 $this->powerSearchOptions() + [ 'search' => $term ],
451                                 $this->limit + $this->offset >= $totalRes
452                         );
453                         $out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
454                 }
455
456                 // Close <div class='searchresults'>
457                 $out->addHTML( "</div>" );
458
459                 Hooks::run( 'SpecialSearchResultsAppend', [ $this, $out, $term ] );
460         }
461
462         /**
463          * @param Title $title
464          * @param int $num The number of search results found
465          * @param null|SearchResultSet $titleMatches Results from title search
466          * @param null|SearchResultSet $textMatches Results from text search
467          */
468         protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) {
469                 // show direct page/create link if applicable
470
471                 // Check DBkey !== '' in case of fragment link only.
472                 if ( is_null( $title ) || $title->getDBkey() === ''
473                         || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() )
474                         || ( $textMatches !== null && $textMatches->searchContainedSyntax() )
475                 ) {
476                         // invalid title
477                         // preserve the paragraph for margins etc...
478                         $this->getOutput()->addHTML( '<p></p>' );
479
480                         return;
481                 }
482
483                 $messageName = 'searchmenu-new-nocreate';
484                 $linkClass = 'mw-search-createlink';
485
486                 if ( !$title->isExternal() ) {
487                         if ( $title->isKnown() ) {
488                                 $messageName = 'searchmenu-exists';
489                                 $linkClass = 'mw-search-exists';
490                         } elseif ( ContentHandler::getForTitle( $title )->supportsDirectEditing()
491                                 && $title->quickUserCan( 'create', $this->getUser() )
492                         ) {
493                                 $messageName = 'searchmenu-new';
494                         }
495                 }
496
497                 $params = [
498                         $messageName,
499                         wfEscapeWikiText( $title->getPrefixedText() ),
500                         Message::numParam( $num )
501                 ];
502                 Hooks::run( 'SpecialSearchCreateLink', [ $title, &$params ] );
503
504                 // Extensions using the hook might still return an empty $messageName
505                 if ( $messageName ) {
506                         $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params );
507                 } else {
508                         // preserve the paragraph for margins etc...
509                         $this->getOutput()->addHTML( '<p></p>' );
510                 }
511         }
512
513         /**
514          * Sets up everything for the HTML output page including styles, javascript,
515          * page title, etc.
516          *
517          * @param string $term
518          */
519         protected function setupPage( $term ) {
520                 $out = $this->getOutput();
521
522                 $this->setHeaders();
523                 $this->outputHeader();
524                 // TODO: Is this true? The namespace remember uses a user token
525                 // on save.
526                 $out->allowClickjacking();
527                 $this->addHelpLink( 'Help:Searching' );
528
529                 if ( strval( $term ) !== '' ) {
530                         $out->setPageTitle( $this->msg( 'searchresults' ) );
531                         $out->setHTMLTitle( $this->msg( 'pagetitle' )
532                                 ->rawParams( $this->msg( 'searchresults-title' )->rawParams( $term )->text() )
533                                 ->inContentLanguage()->text()
534                         );
535                 }
536
537                 $out->addJsConfigVars( [ 'searchTerm' => $term ] );
538                 $out->addModules( 'mediawiki.special.search' );
539                 $out->addModuleStyles( [
540                         'mediawiki.special', 'mediawiki.special.search.styles', 'mediawiki.ui', 'mediawiki.ui.button',
541                         'mediawiki.ui.input', 'mediawiki.widgets.SearchInputWidget.styles',
542                 ] );
543         }
544
545         /**
546          * Return true if current search is a power (advanced) search
547          *
548          * @return bool
549          */
550         protected function isPowerSearch() {
551                 return $this->profile === 'advanced';
552         }
553
554         /**
555          * Extract "power search" namespace settings from the request object,
556          * returning a list of index numbers to search.
557          *
558          * @param WebRequest &$request
559          * @return array
560          */
561         protected function powerSearch( &$request ) {
562                 $arr = [];
563                 foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
564                         if ( $request->getCheck( 'ns' . $ns ) ) {
565                                 $arr[] = $ns;
566                         }
567                 }
568
569                 return $arr;
570         }
571
572         /**
573          * Reconstruct the 'power search' options for links
574          * TODO: Instead of exposing this publicly, could we instead expose
575          *  a function for creating search links?
576          *
577          * @return array
578          */
579         public function powerSearchOptions() {
580                 $opt = [];
581                 if ( $this->isPowerSearch() ) {
582                         foreach ( $this->namespaces as $n ) {
583                                 $opt['ns' . $n] = 1;
584                         }
585                 } else {
586                         $opt['profile'] = $this->profile;
587                 }
588
589                 return $opt + $this->extraParams;
590         }
591
592         /**
593          * Save namespace preferences when we're supposed to
594          *
595          * @return bool Whether we wrote something
596          */
597         protected function saveNamespaces() {
598                 $user = $this->getUser();
599                 $request = $this->getRequest();
600
601                 if ( $user->isLoggedIn() &&
602                         $user->matchEditToken(
603                                 $request->getVal( 'nsRemember' ),
604                                 'searchnamespace',
605                                 $request
606                         ) && !wfReadOnly()
607                 ) {
608                         // Reset namespace preferences: namespaces are not searched
609                         // when they're not mentioned in the URL parameters.
610                         foreach ( MWNamespace::getValidNamespaces() as $n ) {
611                                 $user->setOption( 'searchNs' . $n, false );
612                         }
613                         // The request parameters include all the namespaces to be searched.
614                         // Even if they're the same as an existing profile, they're not eaten.
615                         foreach ( $this->namespaces as $n ) {
616                                 $user->setOption( 'searchNs' . $n, true );
617                         }
618
619                         DeferredUpdates::addCallableUpdate( function () use ( $user ) {
620                                 $user->saveSettings();
621                         } );
622
623                         return true;
624                 }
625
626                 return false;
627         }
628
629         /**
630          * @return array
631          */
632         protected function getSearchProfiles() {
633                 // Builds list of Search Types (profiles)
634                 $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
635                 $defaultNs = $this->searchConfig->defaultNamespaces();
636                 $profiles = [
637                         'default' => [
638                                 'message' => 'searchprofile-articles',
639                                 'tooltip' => 'searchprofile-articles-tooltip',
640                                 'namespaces' => $defaultNs,
641                                 'namespace-messages' => $this->searchConfig->namespacesAsText(
642                                         $defaultNs
643                                 ),
644                         ],
645                         'images' => [
646                                 'message' => 'searchprofile-images',
647                                 'tooltip' => 'searchprofile-images-tooltip',
648                                 'namespaces' => [ NS_FILE ],
649                         ],
650                         'all' => [
651                                 'message' => 'searchprofile-everything',
652                                 'tooltip' => 'searchprofile-everything-tooltip',
653                                 'namespaces' => $nsAllSet,
654                         ],
655                         'advanced' => [
656                                 'message' => 'searchprofile-advanced',
657                                 'tooltip' => 'searchprofile-advanced-tooltip',
658                                 'namespaces' => self::NAMESPACES_CURRENT,
659                         ]
660                 ];
661
662                 Hooks::run( 'SpecialSearchProfiles', [ &$profiles ] );
663
664                 foreach ( $profiles as &$data ) {
665                         if ( !is_array( $data['namespaces'] ) ) {
666                                 continue;
667                         }
668                         sort( $data['namespaces'] );
669                 }
670
671                 return $profiles;
672         }
673
674         /**
675          * @since 1.18
676          *
677          * @return SearchEngine
678          */
679         public function getSearchEngine() {
680                 if ( $this->searchEngine === null ) {
681                         $this->searchEngine = $this->searchEngineType ?
682                                 MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $this->searchEngineType ) :
683                                 MediaWikiServices::getInstance()->newSearchEngine();
684                 }
685
686                 return $this->searchEngine;
687         }
688
689         /**
690          * Current search profile.
691          * @return null|string
692          */
693         function getProfile() {
694                 return $this->profile;
695         }
696
697         /**
698          * Current namespaces.
699          * @return array
700          */
701         function getNamespaces() {
702                 return $this->namespaces;
703         }
704
705         /**
706          * Users of hook SpecialSearchSetupEngine can use this to
707          * add more params to links to not lose selection when
708          * user navigates search results.
709          * @since 1.18
710          *
711          * @param string $key
712          * @param mixed $value
713          */
714         public function setExtraParam( $key, $value ) {
715                 $this->extraParams[$key] = $value;
716         }
717
718         protected function getGroupName() {
719                 return 'pages';
720         }
721 }