* @ingroup SpecialPage
*/
-/**
- * Entry point
- *
- * @param $par String: (default '')
- */
-function wfSpecialSearch( $par = '' ) {
- global $wgRequest, $wgUser, $wgOut;
- $wgOut->allowClickjacking();
-
- // Strip underscores from title parameter; most of the time we'll want
- // text form here. But don't strip underscores from actual text params!
- $titleParam = str_replace( '_', ' ', $par );
- // Fetch the search term
- $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) );
- $searchPage = new SpecialSearch( $wgRequest, $wgUser );
- if( $wgRequest->getVal( 'fulltext' )
- || !is_null( $wgRequest->getVal( 'offset' ))
- || !is_null( $wgRequest->getVal( 'searchx' )) )
- {
- $searchPage->showResults( $search );
- } else {
- $searchPage->goResult( $search );
- }
-}
+use MediaWiki\MediaWikiServices;
+use MediaWiki\Widget\Search\BasicSearchResultSetWidget;
+use MediaWiki\Widget\Search\FullSearchResultWidget;
+use MediaWiki\Widget\Search\InterwikiSearchResultWidget;
+use MediaWiki\Widget\Search\InterwikiSearchResultSetWidget;
+use MediaWiki\Widget\Search\SimpleSearchResultWidget;
+use MediaWiki\Widget\Search\SimpleSearchResultSetWidget;
/**
* implements Special:Search - Run text & title search and display the output
* @ingroup SpecialPage
*/
-class SpecialSearch {
-
+class SpecialSearch extends SpecialPage {
/**
- * Set up basic search parameters from the request and user settings.
- * Typically you'll pass $wgRequest and $wgUser.
- *
- * @param $request WebRequest
- * @param $user User
+ * Current search profile. Search profile is just a name that identifies
+ * the active search tab on the search page (content, discussions...)
+ * For users tt replaces the set of enabled namespaces from the query
+ * string when applicable. Extensions can add new profiles with hooks
+ * with custom search options just for that profile.
+ * @var null|string
*/
- public function __construct( &$request, &$user ) {
- list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
- $this->mPrefix = $request->getVal('prefix', '');
- # Extract requested namespaces
- $this->namespaces = $this->powerSearch( $request );
- if( empty( $this->namespaces ) ) {
- $this->namespaces = SearchEngine::userNamespaces( $user );
- }
- $this->searchRedirects = $request->getCheck( 'redirs' );
- $this->searchAdvanced = $request->getVal( 'advanced' );
- $this->active = 'advanced';
- $this->sk = $user->getSkin();
- $this->didYouMeanHtml = ''; # html of did you mean... link
- $this->fulltext = $request->getVal('fulltext');
- }
+ protected $profile;
+
+ /** @var SearchEngine Search engine */
+ protected $searchEngine;
+
+ /** @var string Search engine type, if not default */
+ protected $searchEngineType;
+
+ /** @var array For links */
+ protected $extraParams = [];
/**
- * If an exact title match can be found, jump straight ahead to it.
- *
- * @param $term String
+ * @var string The prefix url parameter. Set on the searcher and the
+ * is expected to treat it as prefix filter on titles.
*/
- public function goResult( $term ) {
- global $wgOut;
- $this->setupPage( $term );
- # Try to go to page as entered.
- $t = Title::newFromText( $term );
- # If the string cannot be used to create a title
- if( is_null( $t ) ) {
- return $this->showResults( $term );
- }
- # If there's an exact or very near match, jump right there.
- $t = SearchEngine::getNearMatch( $term );
-
- if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) {
- # Hook requested termination
- return;
- }
-
- if( !is_null( $t ) ) {
- $wgOut->redirect( $t->getFullURL() );
- return;
- }
- # No match, generate an edit URL
- $t = Title::newFromText( $term );
- if( !is_null( $t ) ) {
- global $wgGoToEdit;
- wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
- wfDebugLog( 'nogomatch', $t->getText(), false );
-
- # If the feature is enabled, go straight to the edit page
- if( $wgGoToEdit ) {
- $wgOut->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
- return;
- }
- }
- return $this->showResults( $term );
- }
+ protected $mPrefix;
/**
- * @param $term String
+ * @var int
*/
- public function showResults( $term ) {
- global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang, $wgScript;
- wfProfileIn( __METHOD__ );
+ protected $limit, $offset;
- $sk = $wgUser->getSkin();
-
- $this->searchEngine = SearchEngine::create();
- $search =& $this->searchEngine;
- $search->setLimitOffset( $this->limit, $this->offset );
- $search->setNamespaces( $this->namespaces );
- $search->showRedirects = $this->searchRedirects;
- $search->prefix = $this->mPrefix;
- $term = $search->transformSearchTerm($term);
-
- $this->setupPage( $term );
-
- if( $wgDisableTextSearch ) {
- global $wgSearchForwardUrl;
- if( $wgSearchForwardUrl ) {
- $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
- $wgOut->redirect( $url );
- wfProfileOut( __METHOD__ );
- return;
- }
- global $wgInputEncoding;
- $wgOut->addHTML(
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'search-external' ) ) .
- Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), wfMsg( 'searchdisabled' ) ) .
- wfMsg( 'googlesearch',
- htmlspecialchars( $term ),
- htmlspecialchars( $wgInputEncoding ),
- htmlspecialchars( wfMsg( 'searchbutton' ) )
- ) .
- Xml::closeElement( 'fieldset' )
- );
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $t = Title::newFromText( $term );
-
- // fetch search results
- $rewritten = $search->replacePrefixes($term);
-
- $titleMatches = $search->searchTitle( $rewritten );
- if( !($titleMatches instanceof SearchResultTooMany))
- $textMatches = $search->searchText( $rewritten );
-
- // did you mean... suggestions
- if( $textMatches && $textMatches->hasSuggestion() ) {
- $st = SpecialPage::getTitleFor( 'Search' );
-
- # mirror Go/Search behaviour of original request ..
- $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
-
- if($this->fulltext != null)
- $didYouMeanParams['fulltext'] = $this->fulltext;
+ /**
+ * @var array
+ */
+ protected $namespaces;
- $stParams = array_merge(
- $didYouMeanParams,
- $this->powerSearchOptions()
- );
+ /**
+ * @var string
+ */
+ protected $fulltext;
- $suggestionSnippet = $textMatches->getSuggestionSnippet();
+ /**
+ * @var bool
+ */
+ protected $runSuggestion = true;
- if( $suggestionSnippet == '' )
- $suggestionSnippet = null;
+ /**
+ * Search engine configurations.
+ * @var SearchEngineConfig
+ */
+ protected $searchConfig;
- $suggestLink = $sk->linkKnown(
- $st,
- $suggestionSnippet,
- array(),
- $stParams
- );
+ const NAMESPACES_CURRENT = 'sense';
- $this->didYouMeanHtml = '<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>';
- }
- // start rendering the page
- $wgOut->addHtml(
- Xml::openElement(
- 'form',
- array(
- 'id' => ( $this->searchAdvanced ? 'powersearch' : 'search' ),
- 'method' => 'get',
- 'action' => $wgScript
- )
- )
- );
- $wgOut->addHtml(
- Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
- Xml::openElement( 'tr' ) .
- Xml::openElement( 'td' ) . "\n" .
- $this->shortDialog( $term ) .
- Xml::closeElement('td') .
- Xml::closeElement('tr') .
- Xml::closeElement('table')
- );
+ public function __construct() {
+ parent::__construct( 'Search' );
+ $this->searchConfig = MediaWikiServices::getInstance()->getSearchEngineConfig();
+ }
- // Sometimes the search engine knows there are too many hits
- if( $titleMatches instanceof SearchResultTooMany ) {
- $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" );
- wfProfileOut( __METHOD__ );
+ /**
+ * Entry point
+ *
+ * @param string $par
+ */
+ public function execute( $par ) {
+ $request = $this->getRequest();
+ $out = $this->getOutput();
+
+ // Fetch the search term
+ $term = str_replace( "\n", " ", $request->getText( 'search' ) );
+
+ // Historically search terms have been accepted not only in the search query
+ // parameter, but also as part of the primary url. This can have PII implications
+ // in releasing page view data. As such issue a 301 redirect to the correct
+ // URL.
+ if ( strlen( $par ) && !strlen( $term ) ) {
+ $query = $request->getValues();
+ unset( $query['title'] );
+ // Strip underscores from title parameter; most of the time we'll want
+ // text form here. But don't strip underscores from actual text params!
+ $query['search'] = str_replace( '_', ' ', $par );
+ $out->redirect( $this->getPageTitle()->getFullURL( $query ), 301 );
return;
}
- $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
- if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
- $wgOut->addHTML( $this->formHeader($term, 0, 0));
- if( $this->searchAdvanced ) {
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- }
- $wgOut->addHTML( '</form>' );
- // Empty query -- straight view of search form
- wfProfileOut( __METHOD__ );
+ // Need to load selected namespaces before handling nsRemember
+ $this->load();
+ // TODO: This performs database actions on GET request, which is going to
+ // be a problem for our multi-datacenter work.
+ if ( !is_null( $request->getVal( 'nsRemember' ) ) ) {
+ $this->saveNamespaces();
+ // Remove the token from the URL to prevent the user from inadvertently
+ // exposing it (e.g. by pasting it into a public wiki page) or undoing
+ // later settings changes (e.g. by reloading the page).
+ $query = $request->getValues();
+ unset( $query['title'], $query['nsRemember'] );
+ $out->redirect( $this->getPageTitle()->getFullURL( $query ) );
return;
}
- // Get number of results
- $titleMatchesNum = $titleMatches ? $titleMatches->numRows() : 0;
- $textMatchesNum = $textMatches ? $textMatches->numRows() : 0;
- // Total initial query matches (possible false positives)
- $num = $titleMatchesNum + $textMatchesNum;
-
- // Get total actual results (after second filtering, if any)
- $numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ?
- $titleMatches->getTotalHits() : $titleMatchesNum;
- $numTextMatches = $textMatches && !is_null( $textMatches->getTotalHits() ) ?
- $textMatches->getTotalHits() : $textMatchesNum;
-
- // get total number of results if backend can calculate it
- $totalRes = 0;
- if($titleMatches && !is_null( $titleMatches->getTotalHits() ) )
- $totalRes += $titleMatches->getTotalHits();
- if($textMatches && !is_null( $textMatches->getTotalHits() ))
- $totalRes += $textMatches->getTotalHits();
-
- // show number of results and current offset
- $wgOut->addHTML( $this->formHeader($term, $num, $totalRes));
- if( $this->searchAdvanced ) {
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- }
-
- $wgOut->addHtml( Xml::closeElement( 'form' ) );
- $wgOut->addHtml( "<div class='searchresults'>" );
-
- // prev/next links
- if( $num || $this->offset ) {
- // Show the create link ahead
- $this->showCreateLink( $t );
- $prevnext = wfViewPrevNext( $this->offset, $this->limit,
- SpecialPage::getTitleFor( 'Search' ),
- wfArrayToCGI( $this->powerSearchOptions(), array( 'search' => $term ) ),
- max( $titleMatchesNum, $textMatchesNum ) < $this->limit
- );
- //$wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
- wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
- } else {
- wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
- }
-
- if( $titleMatches ) {
- if( $numTitleMatches > 0 ) {
- $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' );
- $wgOut->addHTML( $this->showMatches( $titleMatches ) );
- }
- $titleMatches->free();
- }
- if( $textMatches ) {
- // output appropriate heading
- if( $numTextMatches > 0 && $numTitleMatches > 0 ) {
- // if no title matches the heading is redundant
- $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' );
- } elseif( $totalRes == 0 ) {
- # Don't show the 'no text matches' if we received title matches
- # $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' );
- }
- // show interwiki results if any
- if( $textMatches->hasInterwikiResults() ) {
- $wgOut->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
+ $this->searchEngineType = $request->getVal( 'srbackend' );
+ if (
+ !$request->getVal( 'fulltext' ) &&
+ $request->getVal( 'offset' ) === null
+ ) {
+ $url = $this->goResult( $term );
+ if ( $url !== null ) {
+ // successful 'go'
+ $out->redirect( $url );
+ return;
}
- // show results
- if( $numTextMatches > 0 ) {
- $wgOut->addHTML( $this->showMatches( $textMatches ) );
+ // No match. If it could plausibly be a title
+ // run the No go match hook.
+ $title = Title::newFromText( $term );
+ if ( !is_null( $title ) ) {
+ Hooks::run( 'SpecialSearchNogomatch', [ &$title ] );
}
-
- $textMatches->free();
- }
- if( $num === 0 ) {
- $wgOut->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
- $this->showCreateLink( $t );
}
- $wgOut->addHtml( "</div>" );
- if( $num || $this->offset ) {
- $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
- }
- wfProfileOut( __METHOD__ );
- }
-
- protected function showCreateLink( $t ) {
- global $wgOut;
-
- // show direct page/create link if applicable
- $messageName = null;
- if( !is_null($t) ) {
- if( $t->isKnown() ) {
- $messageName = 'searchmenu-exists';
- } elseif( $t->userCan( 'create' ) ) {
- $messageName = 'searchmenu-new';
+ $this->setupPage( $term );
+
+ if ( $this->getConfig()->get( 'DisableTextSearch' ) ) {
+ $searchForwardUrl = $this->getConfig()->get( 'SearchForwardUrl' );
+ if ( $searchForwardUrl ) {
+ $url = str_replace( '$1', urlencode( $term ), $searchForwardUrl );
+ $out->redirect( $url );
} else {
- $messageName = 'searchmenu-new-nocreate';
+ $out->addHTML(
+ "<fieldset>" .
+ "<legend>" .
+ $this->msg( 'search-external' )->escaped() .
+ "</legend>" .
+ "<p class='mw-searchdisabled'>" .
+ $this->msg( 'searchdisabled' )->escaped() .
+ "</p>" .
+ $this->msg( 'googlesearch' )->rawParams(
+ htmlspecialchars( $term ),
+ 'UTF-8',
+ $this->msg( 'searchbutton' )->escaped()
+ )->text() .
+ "</fieldset>"
+ );
}
- }
- if( $messageName ) {
- $wgOut->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ) );
- } else {
- // preserve the paragraph for margins etc...
- $wgOut->addHtml( '<p></p>' );
+
+ return;
}
+
+ $this->showResults( $term );
}
/**
+ * Set up basic search parameters from the request and user settings.
*
+ * @see tests/phpunit/includes/specials/SpecialSearchTest.php
*/
- protected function setupPage( $term ) {
- global $wgOut;
- // Figure out the active search profile header
- if( $this->searchAdvanced ) {
- $this->active = 'advanced';
- } else {
- $profiles = $this->getSearchProfiles();
-
- foreach( $profiles as $key => $data ) {
- if ( $this->namespaces == $data['namespaces'] && $key != 'advanced')
- $this->active = $key;
- }
-
+ public function load() {
+ $request = $this->getRequest();
+ list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, '' );
+ $this->mPrefix = $request->getVal( 'prefix', '' );
+
+ $user = $this->getUser();
+
+ # Extract manually requested namespaces
+ $nslist = $this->powerSearch( $request );
+ if ( !count( $nslist ) ) {
+ # Fallback to user preference
+ $nslist = $this->searchConfig->userNamespaces( $user );
}
- # Should advanced UI be used?
- $this->searchAdvanced = ($this->active === 'advanced');
- if( !empty( $term ) ) {
- $wgOut->setPageTitle( wfMsg( 'searchresults') );
- $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term ) ) );
+
+ $profile = null;
+ if ( !count( $nslist ) ) {
+ $profile = 'default';
}
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- // add javascript specific to special:search
- $wgOut->addModules( 'mediawiki.legacy.search' );
- $wgOut->addModules( 'mediawiki.special.search' );
- }
- /**
- * Extract "power search" namespace settings from the request object,
- * returning a list of index numbers to search.
- *
- * @param $request WebRequest
- * @return Array
- */
- protected function powerSearch( &$request ) {
- $arr = array();
- foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
- if( $request->getCheck( 'ns' . $ns ) ) {
- $arr[] = $ns;
+ $profile = $request->getVal( 'profile', $profile );
+ $profiles = $this->getSearchProfiles();
+ if ( $profile === null ) {
+ // BC with old request format
+ $profile = 'advanced';
+ foreach ( $profiles as $key => $data ) {
+ if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) {
+ $profile = $key;
+ }
+ }
+ $this->namespaces = $nslist;
+ } elseif ( $profile === 'advanced' ) {
+ $this->namespaces = $nslist;
+ } else {
+ if ( isset( $profiles[$profile]['namespaces'] ) ) {
+ $this->namespaces = $profiles[$profile]['namespaces'];
+ } else {
+ // Unknown profile requested
+ $profile = 'default';
+ $this->namespaces = $profiles['default']['namespaces'];
}
}
- return $arr;
+
+ $this->fulltext = $request->getVal( 'fulltext' );
+ $this->runSuggestion = (bool)$request->getVal( 'runsuggestion', true );
+ $this->profile = $profile;
}
/**
- * Reconstruct the 'power search' options for links
+ * If an exact title match can be found, jump straight ahead to it.
*
- * @return Array
+ * @param string $term
+ * @return string|null The url to redirect to, or null if no redirect.
*/
- protected function powerSearchOptions() {
- $opt = array();
- foreach( $this->namespaces as $n ) {
- $opt['ns' . $n] = 1;
+ public function goResult( $term ) {
+ # If the string cannot be used to create a title
+ if ( is_null( Title::newFromText( $term ) ) ) {
+ return null;
}
- $opt['redirs'] = $this->searchRedirects ? 1 : 0;
- if( $this->searchAdvanced ) {
- $opt['advanced'] = $this->searchAdvanced;
+ # If there's an exact or very near match, jump right there.
+ $title = $this->getSearchEngine()
+ ->getNearMatcher( $this->getConfig() )->getNearMatch( $term );
+ if ( is_null( $title ) ) {
+ return null;
+ }
+ $url = null;
+ if ( !Hooks::run( 'SpecialSearchGoResult', [ $term, $title, &$url ] ) ) {
+ return null;
}
- return $opt;
+
+ return $url === null ? $title->getFullUrlForRedirect() : $url;
}
/**
- * Show whole set of results
- *
- * @param $matches SearchResultSet
+ * @param string $term
*/
- protected function showMatches( &$matches ) {
+ public function showResults( $term ) {
global $wgContLang;
- wfProfileIn( __METHOD__ );
-
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $out = "";
- $infoLine = $matches->getInfo();
- if( !is_null($infoLine) ) {
- $out .= "\n<!-- {$infoLine} -->\n";
+ if ( $this->searchEngineType !== null ) {
+ $this->setExtraParam( 'srbackend', $this->searchEngineType );
}
- $out .= "<ul class='mw-search-results'>\n";
- while( $result = $matches->next() ) {
- $out .= $this->showHit( $result, $terms );
- }
- $out .= "</ul>\n";
- // convert the whole thing to desired language variant
- $out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
- return $out;
- }
+ $out = $this->getOutput();
+ $formWidget = new MediaWiki\Widget\Search\SearchFormWidget(
+ $this,
+ $this->searchConfig,
+ $this->getSearchProfiles()
+ );
+ $filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':';
+ if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
+ // Empty query -- straight view of search form
+ if ( !Hooks::run( 'SpecialSearchResultsPrepend', [ $this, $out, $term ] ) ) {
+ # Hook requested termination
+ return;
+ }
+ $out->enableOOUI();
+ // The form also contains the 'Showing results 0 - 20 of 1234' so we can
+ // only do the form render here for the empty $term case. Rendering
+ // the form when a search is provided is repeated below.
+ $out->addHTML( $formWidget->render(
+ $this->profile, $term, 0, 0, $this->offset, $this->isPowerSearch()
+ ) );
+ return;
+ }
- /**
- * Format a single hit result
- *
- * @param $result SearchResult
- * @param $terms Array: terms to highlight
- */
- protected function showHit( $result, $terms ) {
- global $wgLang, $wgUser;
- wfProfileIn( __METHOD__ );
+ $search = $this->getSearchEngine();
+ $search->setFeatureData( 'rewrite', $this->runSuggestion );
+ $search->setLimitOffset( $this->limit, $this->offset );
+ $search->setNamespaces( $this->namespaces );
+ $search->prefix = $this->mPrefix;
+ $term = $search->transformSearchTerm( $term );
- if( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
+ Hooks::run( 'SpecialSearchSetupEngine', [ $this, $this->profile, $search ] );
+ if ( !Hooks::run( 'SpecialSearchResultsPrepend', [ $this, $out, $term ] ) ) {
+ # Hook requested termination
+ return;
}
- $sk = $wgUser->getSkin();
- $t = $result->getTitle();
+ $title = Title::newFromText( $term );
+ $showSuggestion = $title === null || !$title->isKnown();
+ $search->setShowSuggestion( $showSuggestion );
- $titleSnippet = $result->getTitleSnippet($terms);
+ // fetch search results
+ $rewritten = $search->replacePrefixes( $term );
- if( $titleSnippet == '' )
- $titleSnippet = null;
-
- $link_t = clone $t;
-
- wfRunHooks( 'ShowSearchHitTitle',
- array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
+ $titleMatches = $search->searchTitle( $rewritten );
+ $textMatches = $search->searchText( $rewritten );
- $link = $this->sk->linkKnown(
- $link_t,
- $titleSnippet
- );
+ $textStatus = null;
+ if ( $textMatches instanceof Status ) {
+ $textStatus = $textMatches;
+ $textMatches = $textStatus->getValue();
+ }
- //If page content is not readable, just return the title.
- //This is not quite safe, but better than showing excerpts from non-readable pages
- //Note that hiding the entry entirely would screw up paging.
- if( !$t->userCanRead() ) {
- wfProfileOut( __METHOD__ );
- return "<li>{$link}</li>\n";
+ // Get number of results
+ $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0;
+ if ( $titleMatches ) {
+ $titleMatchesNum = $titleMatches->numRows();
+ $numTitleMatches = $titleMatches->getTotalHits();
+ }
+ if ( $textMatches ) {
+ $textMatchesNum = $textMatches->numRows();
+ $numTextMatches = $textMatches->getTotalHits();
+ if ( $textMatchesNum > 0 ) {
+ $search->augmentSearchResults( $textMatches );
+ }
}
+ $num = $titleMatchesNum + $textMatchesNum;
+ $totalRes = $numTitleMatches + $numTextMatches;
- // If the page doesn't *exist*... our search index is out of date.
- // The least confusing at this point is to drop the result.
- // You may get less results, but... oh well. :P
- if( $result->isMissingRevision() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
+ // start rendering the page
+ $out->enableOOUI();
+ $out->addHTML( $formWidget->render(
+ $this->profile, $term, $num, $totalRes, $this->offset, $this->isPowerSearch()
+ ) );
+
+ // did you mean... suggestions
+ if ( $textMatches ) {
+ $dymWidget = new MediaWiki\Widget\Search\DidYouMeanWidget( $this );
+ $out->addHTML( $dymWidget->render( $term, $textMatches ) );
}
- // format redirects / relevant sections
- $redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet($terms);
- $sectionTitle = $result->getSectionTitle();
- $sectionText = $result->getSectionSnippet($terms);
- $redirect = '';
-
- if( !is_null($redirectTitle) ) {
- if( $redirectText == '' )
- $redirectText = null;
-
- $redirect = "<span class='searchalttitle'>" .
- wfMsg(
- 'search-redirect',
- $this->sk->linkKnown(
- $redirectTitle,
- $redirectText
- )
- ) .
- "</span>";
+ $hasErrors = $textStatus && $textStatus->getErrors() !== [];
+ $hasOtherResults = $textMatches &&
+ $textMatches->hasInterwikiResults( SearchResultSet::INLINE_RESULTS );
+
+ if ( $textMatches && $textMatches->hasInterwikiResults( SearchResultSet::SECONDARY_RESULTS ) ) {
+ $out->addHTML( '<div class="searchresults mw-searchresults-has-iw">' );
+ } else {
+ $out->addHTML( '<div class="searchresults">' );
}
- $section = '';
+ if ( $hasErrors ) {
+ list( $error, $warning ) = $textStatus->splitByErrorType();
+ if ( $error->getErrors() ) {
+ $out->addHTML( Html::rawElement(
+ 'div',
+ [ 'class' => 'errorbox' ],
+ $error->getHTML( 'search-error' )
+ ) );
+ }
+ if ( $warning->getErrors() ) {
+ $out->addHTML( Html::rawElement(
+ 'div',
+ [ 'class' => 'warningbox' ],
+ $warning->getHTML( 'search-warning' )
+ ) );
+ }
+ }
+ // Show the create link ahead
+ $this->showCreateLink( $title, $num, $titleMatches, $textMatches );
- if( !is_null($sectionTitle) ) {
- if( $sectionText == '' )
- $sectionText = null;
+ Hooks::run( 'SpecialSearchResults', [ $term, &$titleMatches, &$textMatches ] );
- $section = "<span class='searchalttitle'>" .
- wfMsg(
- 'search-section', $this->sk->linkKnown(
- $sectionTitle,
- $sectionText
- )
- ) .
- "</span>";
+ // If we have no results and have not already displayed an error message
+ if ( $num === 0 && !$hasErrors ) {
+ $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", [
+ $hasOtherResults ? 'search-nonefound-thiswiki' : 'search-nonefound',
+ wfEscapeWikiText( $term )
+ ] );
}
- // format text extract
- $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
+ // Although $num might be 0 there can still be secondary or inline
+ // results to display.
+ $linkRenderer = $this->getLinkRenderer();
+ $mainResultWidget = new FullSearchResultWidget( $this, $linkRenderer );
- // format score
- if( is_null( $result->getScore() ) ) {
- // Search engine doesn't report scoring info
- $score = '';
+ if ( $search->getFeatureData( 'enable-new-crossproject-page' ) ) {
+ $sidebarResultWidget = new InterwikiSearchResultWidget( $this, $linkRenderer );
+ $sidebarResultsWidget = new InterwikiSearchResultSetWidget(
+ $this,
+ $sidebarResultWidget,
+ $linkRenderer,
+ MediaWikiServices::getInstance()->getInterwikiLookup(),
+ $search->getFeatureData( 'show-multimedia-search-results' )
+ );
} else {
- $percent = sprintf( '%2.1f', $result->getScore() * 100 );
- $score = wfMsg( 'search-result-score', $wgLang->formatNum( $percent ) )
- . ' - ';
+ $sidebarResultWidget = new SimpleSearchResultWidget( $this, $linkRenderer );
+ $sidebarResultsWidget = new SimpleSearchResultSetWidget(
+ $this,
+ $sidebarResultWidget,
+ $linkRenderer,
+ MediaWikiServices::getInstance()->getInterwikiLookup()
+ );
}
- // format description
- $byteSize = $result->getByteSize();
- $wordCount = $result->getWordCount();
- $timestamp = $result->getTimestamp();
- $size = wfMsgExt(
- 'search-result-size',
- array( 'parsemag', 'escape' ),
- $this->sk->formatSize( $byteSize ),
- $wgLang->formatNum( $wordCount )
- );
+ $widget = new BasicSearchResultSetWidget( $this, $mainResultWidget, $sidebarResultsWidget );
- if( $t->getNamespace() == NS_CATEGORY ) {
- $cat = Category::newFromTitle( $t );
- $size = wfMsgExt(
- 'search-result-category-size',
- array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $cat->getPageCount() ),
- $wgLang->formatNum( $cat->getSubcatCount() ),
- $wgLang->formatNum( $cat->getFileCount() )
- );
- }
+ $out->addHTML( $widget->render(
+ $term, $this->offset, $titleMatches, $textMatches
+ ) );
- $date = $wgLang->timeanddate( $timestamp );
-
- // link to related articles if supported
- $related = '';
- if( $result->hasRelated() ) {
- $st = SpecialPage::getTitleFor( 'Search' );
- $stParams = array_merge(
- $this->powerSearchOptions(),
- array(
- 'search' => wfMsgForContent( 'searchrelated' ) . ':' . $t->getPrefixedText(),
- 'fulltext' => wfMsg( 'search' )
- )
- );
+ if ( $titleMatches ) {
+ $titleMatches->free();
+ }
- $related = ' -- ' . $sk->linkKnown(
- $st,
- wfMsg('search-relatedarticle'),
- array(),
- $stParams
- );
+ if ( $textMatches ) {
+ $textMatches->free();
}
- // Include a thumbnail for media files...
- if( $t->getNamespace() == NS_FILE ) {
- $img = wfFindFile( $t );
- if( $img ) {
- $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
- if( $thumb ) {
- $desc = wfMsg( 'parentheses', $img->getShortDesc() );
- wfProfileOut( __METHOD__ );
- // Float doesn't seem to interact well with the bullets.
- // Table messes up vertical alignment of the bullets.
- // Bullets are therefore disabled (didn't look great anyway).
- return "<li>" .
- '<table class="searchResultImage">' .
- '<tr>' .
- '<td width="120" align="center" valign="top">' .
- $thumb->toHtml( array( 'desc-link' => true ) ) .
- '</td>' .
- '<td valign="top">' .
- $link .
- $extract .
- "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" .
- '</td>' .
- '</tr>' .
- '</table>' .
- "</li>\n";
- }
+ $out->addHTML( '<div class="mw-search-visualclear"></div>' );
+
+ // prev/next links
+ if ( $totalRes > $this->limit || $this->offset ) {
+ // Allow matches to define the correct offset, as interleaved
+ // AB testing may require a different next page offset.
+ if ( $textMatches && $textMatches->getOffset() !== null ) {
+ $offset = $textMatches->getOffset();
+ } else {
+ $offset = $this->offset;
}
+
+ $prevnext = $this->getLanguage()->viewPrevNext(
+ $this->getPageTitle(),
+ $offset,
+ $this->limit,
+ $this->powerSearchOptions() + [ 'search' => $term ],
+ $this->limit + $this->offset >= $totalRes
+ );
+ $out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
- wfProfileOut( __METHOD__ );
- return "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
- "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
- "</li>\n";
+ // Close <div class='searchresults'>
+ $out->addHTML( "</div>" );
+ Hooks::run( 'SpecialSearchResultsAppend', [ $this, $out, $term ] );
}
/**
- * Show results from other wikis
- *
- * @param $matches SearchResultSet
- * @param $query String
+ * @param Title $title
+ * @param int $num The number of search results found
+ * @param null|SearchResultSet $titleMatches Results from title search
+ * @param null|SearchResultSet $textMatches Results from text search
*/
- protected function showInterwiki( &$matches, $query ) {
- global $wgContLang;
- wfProfileIn( __METHOD__ );
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
-
- $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
- wfMsg('search-interwiki-caption')."</div>\n";
- $out .= "<ul class='mw-search-iwresults'>\n";
-
- // work out custom project captions
- $customCaptions = array();
- $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line <iwprefix>:<caption>
- foreach($customLines as $line) {
- $parts = explode(":",$line,2);
- if(count($parts) == 2) // validate line
- $customCaptions[$parts[0]] = $parts[1];
+ protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) {
+ // show direct page/create link if applicable
+
+ // Check DBkey !== '' in case of fragment link only.
+ if ( is_null( $title ) || $title->getDBkey() === ''
+ || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() )
+ || ( $textMatches !== null && $textMatches->searchContainedSyntax() )
+ ) {
+ // invalid title
+ // preserve the paragraph for margins etc...
+ $this->getOutput()->addHTML( '<p></p>' );
+
+ return;
}
- $prev = null;
- while( $result = $matches->next() ) {
- $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
- $prev = $result->getInterwikiPrefix();
+ $messageName = 'searchmenu-new-nocreate';
+ $linkClass = 'mw-search-createlink';
+
+ if ( !$title->isExternal() ) {
+ if ( $title->isKnown() ) {
+ $messageName = 'searchmenu-exists';
+ $linkClass = 'mw-search-exists';
+ } elseif ( ContentHandler::getForTitle( $title )->supportsDirectEditing()
+ && $title->quickUserCan( 'create', $this->getUser() )
+ ) {
+ $messageName = 'searchmenu-new';
+ }
}
- // TODO: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
- $out .= "</ul></div>\n";
- // convert the whole thing to desired language variant
- $out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
- return $out;
+ $params = [
+ $messageName,
+ wfEscapeWikiText( $title->getPrefixedText() ),
+ Message::numParam( $num )
+ ];
+ Hooks::run( 'SpecialSearchCreateLink', [ $title, &$params ] );
+
+ // Extensions using the hook might still return an empty $messageName
+ if ( $messageName ) {
+ $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params );
+ } else {
+ // preserve the paragraph for margins etc...
+ $this->getOutput()->addHTML( '<p></p>' );
+ }
}
/**
- * Show single interwiki link
+ * Sets up everything for the HTML output page including styles, javascript,
+ * page title, etc.
*
- * @param $result SearchResult
- * @param $lastInterwiki String
- * @param $terms Array
- * @param $query String
- * @param $customCaptions Array: iw prefix -> caption
+ * @param string $term
*/
- protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
- wfProfileIn( __METHOD__ );
-
- if( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
+ protected function setupPage( $term ) {
+ $out = $this->getOutput();
+
+ $this->setHeaders();
+ $this->outputHeader();
+ // TODO: Is this true? The namespace remember uses a user token
+ // on save.
+ $out->allowClickjacking();
+ $this->addHelpLink( 'Help:Searching' );
+
+ if ( strval( $term ) !== '' ) {
+ $out->setPageTitle( $this->msg( 'searchresults' ) );
+ $out->setHTMLTitle( $this->msg( 'pagetitle' )
+ ->rawParams( $this->msg( 'searchresults-title' )->rawParams( $term )->text() )
+ ->inContentLanguage()->text()
+ );
}
- $t = $result->getTitle();
-
- $titleSnippet = $result->getTitleSnippet($terms);
-
- if( $titleSnippet == '' )
- $titleSnippet = null;
+ $out->addJsConfigVars( [ 'searchTerm' => $term ] );
+ $out->addModules( 'mediawiki.special.search' );
+ $out->addModuleStyles( [
+ 'mediawiki.special', 'mediawiki.special.search.styles', 'mediawiki.ui', 'mediawiki.ui.button',
+ 'mediawiki.ui.input', 'mediawiki.widgets.SearchInputWidget.styles',
+ ] );
+ }
- $link = $this->sk->linkKnown(
- $t,
- $titleSnippet
- );
+ /**
+ * Return true if current search is a power (advanced) search
+ *
+ * @return bool
+ */
+ protected function isPowerSearch() {
+ return $this->profile === 'advanced';
+ }
- // format redirect if any
- $redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet($terms);
- $redirect = '';
- if( !is_null($redirectTitle) ) {
- if( $redirectText == '' )
- $redirectText = null;
-
- $redirect = "<span class='searchalttitle'>" .
- wfMsg(
- 'search-redirect',
- $this->sk->linkKnown(
- $redirectTitle,
- $redirectText
- )
- ) .
- "</span>";
+ /**
+ * Extract "power search" namespace settings from the request object,
+ * returning a list of index numbers to search.
+ *
+ * @param WebRequest &$request
+ * @return array
+ */
+ protected function powerSearch( &$request ) {
+ $arr = [];
+ foreach ( $this->searchConfig->searchableNamespaces() as $ns => $name ) {
+ if ( $request->getCheck( 'ns' . $ns ) ) {
+ $arr[] = $ns;
+ }
}
- $out = "";
- // display project name
- if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) {
- if( key_exists($t->getInterwiki(),$customCaptions) )
- // captions from 'search-interwiki-custom'
- $caption = $customCaptions[$t->getInterwiki()];
- else{
- // default is to show the hostname of the other wiki which might suck
- // if there are many wikis on one hostname
- $parsed = parse_url($t->getFullURL());
- $caption = wfMsg('search-interwiki-default', $parsed['host']);
+ return $arr;
+ }
+
+ /**
+ * Reconstruct the 'power search' options for links
+ * TODO: Instead of exposing this publicly, could we instead expose
+ * a function for creating search links?
+ *
+ * @return array
+ */
+ public function powerSearchOptions() {
+ $opt = [];
+ if ( $this->isPowerSearch() ) {
+ foreach ( $this->namespaces as $n ) {
+ $opt['ns' . $n] = 1;
}
- // "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
- $searchLink = $this->sk->linkKnown(
- $searchTitle,
- wfMsg('search-interwiki-more'),
- array(),
- array(
- 'search' => $query,
- 'fulltext' => 'Search'
- )
- );
- $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'>
- {$searchLink}</span>{$caption}</div>\n<ul>";
+ } else {
+ $opt['profile'] = $this->profile;
}
- $out .= "<li>{$link} {$redirect}</li>\n";
- wfProfileOut( __METHOD__ );
- return $out;
+ return $opt + $this->extraParams;
}
-
/**
- * Generates the power search box at bottom of [[Special:Search]]
+ * Save namespace preferences when we're supposed to
*
- * @param $term String: search term
- * @return String: HTML form
+ * @return bool Whether we wrote something
*/
- protected function powerSearchBox( $term ) {
- // Groups namespaces into rows according to subject
- $rows = array();
- foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
- $subject = MWNamespace::getSubject( $namespace );
- if( !array_key_exists( $subject, $rows ) ) {
- $rows[$subject] = "";
+ protected function saveNamespaces() {
+ $user = $this->getUser();
+ $request = $this->getRequest();
+
+ if ( $user->isLoggedIn() &&
+ $user->matchEditToken(
+ $request->getVal( 'nsRemember' ),
+ 'searchnamespace',
+ $request
+ ) && !wfReadOnly()
+ ) {
+ // Reset namespace preferences: namespaces are not searched
+ // when they're not mentioned in the URL parameters.
+ foreach ( MWNamespace::getValidNamespaces() as $n ) {
+ $user->setOption( 'searchNs' . $n, false );
}
- $name = str_replace( '_', ' ', $name );
- if( $name == '' ) {
- $name = wfMsg( 'blanknamespace' );
- }
- $rows[$subject] .=
- Xml::openElement(
- 'td', array( 'style' => 'white-space: nowrap' )
- ) .
- Xml::checkLabel(
- $name,
- "ns{$namespace}",
- "mw-search-ns{$namespace}",
- in_array( $namespace, $this->namespaces )
- ) .
- Xml::closeElement( 'td' );
- }
- $rows = array_values( $rows );
- $numRows = count( $rows );
-
- // Lays out namespaces in multiple floating two-column tables so they'll
- // be arranged nicely while still accommodating different screen widths
- $namespaceTables = '';
- for( $i = 0; $i < $numRows; $i += 4 ) {
- $namespaceTables .= Xml::openElement(
- 'table',
- array( 'cellpadding' => 0, 'cellspacing' => 0, 'border' => 0 )
- );
- for( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
- $namespaceTables .= Xml::tags( 'tr', null, $rows[$j] );
+ // The request parameters include all the namespaces to be searched.
+ // Even if they're the same as an existing profile, they're not eaten.
+ foreach ( $this->namespaces as $n ) {
+ $user->setOption( 'searchNs' . $n, true );
}
- $namespaceTables .= Xml::closeElement( 'table' );
- }
- // Show redirects check only if backend supports it
- $redirects = '';
- if( $this->searchEngine->acceptListRedirects() ) {
- $redirects =
- Xml::check(
- 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' )
- ) .
- ' ' .
- Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' );
+
+ DeferredUpdates::addCallableUpdate( function () use ( $user ) {
+ $user->saveSettings();
+ } );
+
+ return true;
}
- // Return final output
- return
- Xml::openElement(
- 'fieldset',
- array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
- ) .
- Xml::element( 'legend', null, wfMsg('powersearch-legend') ) .
- Xml::tags( 'h4', null, wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) ) .
- Xml::tags(
- 'div',
- array( 'id' => 'mw-search-togglebox' ),
- Xml::label( wfMsg( 'powersearch-togglelabel' ), 'mw-search-togglelabel' ) .
- Xml::element(
- 'input',
- array(
- 'type'=>'button',
- 'id' => 'mw-search-toggleall',
- 'onclick' => 'mwToggleSearchCheckboxes("all");',
- 'value' => wfMsg( 'powersearch-toggleall' )
- )
- ) .
- Xml::element(
- 'input',
- array(
- 'type'=>'button',
- 'id' => 'mw-search-togglenone',
- 'onclick' => 'mwToggleSearchCheckboxes("none");',
- 'value' => wfMsg( 'powersearch-togglenone' )
- )
- )
- ) .
- Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
- $namespaceTables .
- Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
- $redirects .
- Html::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) .
- Html::hidden( 'advanced', $this->searchAdvanced ) .
- Html::hidden( 'fulltext', 'Advanced search' ) .
- Xml::closeElement( 'fieldset' );
+
+ return false;
}
-
+
+ /**
+ * @return array
+ */
protected function getSearchProfiles() {
// Builds list of Search Types (profiles)
- $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
-
- $profiles = array(
- 'default' => array(
+ $nsAllSet = array_keys( $this->searchConfig->searchableNamespaces() );
+ $defaultNs = $this->searchConfig->defaultNamespaces();
+ $profiles = [
+ 'default' => [
'message' => 'searchprofile-articles',
'tooltip' => 'searchprofile-articles-tooltip',
- 'namespaces' => SearchEngine::defaultNamespaces(),
- 'namespace-messages' => SearchEngine::namespacesAsText(
- SearchEngine::defaultNamespaces()
+ 'namespaces' => $defaultNs,
+ 'namespace-messages' => $this->searchConfig->namespacesAsText(
+ $defaultNs
),
- ),
- 'images' => array(
+ ],
+ 'images' => [
'message' => 'searchprofile-images',
'tooltip' => 'searchprofile-images-tooltip',
- 'namespaces' => array( NS_FILE ),
- ),
- 'help' => array(
- 'message' => 'searchprofile-project',
- 'tooltip' => 'searchprofile-project-tooltip',
- 'namespaces' => SearchEngine::helpNamespaces(),
- 'namespace-messages' => SearchEngine::namespacesAsText(
- SearchEngine::helpNamespaces()
- ),
- ),
- 'all' => array(
+ 'namespaces' => [ NS_FILE ],
+ ],
+ 'all' => [
'message' => 'searchprofile-everything',
'tooltip' => 'searchprofile-everything-tooltip',
'namespaces' => $nsAllSet,
- ),
- 'advanced' => array(
+ ],
+ 'advanced' => [
'message' => 'searchprofile-advanced',
'tooltip' => 'searchprofile-advanced-tooltip',
- 'namespaces' => $this->namespaces,
- 'parameters' => array( 'advanced' => 1 ),
- )
- );
-
- wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
+ 'namespaces' => self::NAMESPACES_CURRENT,
+ ]
+ ];
- foreach( $profiles as &$data ) {
- sort($data['namespaces']);
- }
-
- return $profiles;
- }
-
- protected function formHeader( $term, $resultsShown, $totalNum ) {
- global $wgLang;
-
- $out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) );
-
- $bareterm = $term;
- if( $this->startsWithImage( $term ) ) {
- // Deletes prefixes
- $bareterm = substr( $term, strpos( $term, ':' ) + 1 );
- }
+ Hooks::run( 'SpecialSearchProfiles', [ &$profiles ] );
- $profiles = $this->getSearchProfiles();
-
- // Outputs XML for Search Types
- $out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) );
- $out .= Xml::openElement( 'ul' );
- foreach ( $profiles as $id => $profile ) {
- $tooltipParam = isset( $profile['namespace-messages'] ) ?
- $wgLang->commaList( $profile['namespace-messages'] ) : null;
- $out .= Xml::tags(
- 'li',
- array(
- 'class' => $this->active == $id ? 'current' : 'normal'
- ),
- $this->makeSearchLink(
- $bareterm,
- $profile['namespaces'],
- wfMsg( $profile['message'] ),
- wfMsg( $profile['tooltip'], $tooltipParam ),
- isset( $profile['parameters'] ) ? $profile['parameters'] : array()
- )
- );
- }
- $out .= Xml::closeElement( 'ul' );
- $out .= Xml::closeElement('div') ;
-
- // Results-info
- if ( $resultsShown > 0 ) {
- if ( $totalNum > 0 ){
- $top = wfMsgExt( 'showingresultsheader', array( 'parseinline' ),
- $wgLang->formatNum( $this->offset + 1 ),
- $wgLang->formatNum( $this->offset + $resultsShown ),
- $wgLang->formatNum( $totalNum ),
- wfEscapeWikiText( $term ),
- $wgLang->formatNum( $resultsShown )
- );
- } elseif ( $resultsShown >= $this->limit ) {
- $top = wfShowingResults( $this->offset, $this->limit );
- } else {
- $top = wfShowingResultsNum( $this->offset, $this->limit, $resultsShown );
- }
- $out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
- Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) )
- );
- }
-
- $out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
- $out .= Xml::closeElement('div');
-
- // Adds hidden namespace fields
- if ( !$this->searchAdvanced ) {
- foreach( $this->namespaces as $ns ) {
- $out .= Html::hidden( "ns{$ns}", '1' );
+ foreach ( $profiles as &$data ) {
+ if ( !is_array( $data['namespaces'] ) ) {
+ continue;
}
+ sort( $data['namespaces'] );
}
-
- return $out;
- }
- protected function shortDialog( $term ) {
- $searchTitle = SpecialPage::getTitleFor( 'Search' );
- $out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
- // Keep redirect setting
- $out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n";
- // Term box
- $out .= Html::input( 'search', $term, 'search', array(
- 'id' => $this->searchAdvanced ? 'powerSearchText' : 'searchText',
- 'size' => '50',
- 'autofocus'
- ) ) . "\n";
- $out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
- $out .= Xml::submitButton( wfMsg( 'searchbutton' ) ) . "\n";
- return $out . $this->didYouMeanHtml;
+ return $profiles;
}
/**
- * Make a search link with some target namespaces
+ * @since 1.18
*
- * @param $term String
- * @param $namespaces Array
- * @param $label String: link's text
- * @param $tooltip String: link's tooltip
- * @param $params Array: query string parameters
- * @return String: HTML fragment
+ * @return SearchEngine
*/
- protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) {
- $opt = $params;
- foreach( $namespaces as $n ) {
- $opt['ns' . $n] = 1;
+ public function getSearchEngine() {
+ if ( $this->searchEngine === null ) {
+ $this->searchEngine = $this->searchEngineType ?
+ MediaWikiServices::getInstance()->getSearchEngineFactory()->create( $this->searchEngineType ) :
+ MediaWikiServices::getInstance()->newSearchEngine();
}
- $opt['redirs'] = $this->searchRedirects ? 1 : 0;
-
- $st = SpecialPage::getTitleFor( 'Search' );
- $stParams = array_merge(
- array(
- 'search' => $term,
- 'fulltext' => wfMsg( 'search' )
- ),
- $opt
- );
- return Xml::element(
- 'a',
- array(
- 'href' => $st->getLocalURL( $stParams ),
- 'title' => $tooltip,
- 'onmousedown' => 'mwSearchHeaderClick(this);',
- 'onkeydown' => 'mwSearchHeaderClick(this);'),
- $label
- );
+ return $this->searchEngine;
}
/**
- * Check if query starts with image: prefix
- *
- * @param $term String: the string to check
- * @return Boolean
+ * Current search profile.
+ * @return null|string
*/
- protected function startsWithImage( $term ) {
- global $wgContLang;
+ function getProfile() {
+ return $this->profile;
+ }
- $p = explode( ':', $term );
- if( count( $p ) > 1 ) {
- return $wgContLang->getNsIndex( $p[0] ) == NS_FILE;
- }
- return false;
+ /**
+ * Current namespaces.
+ * @return array
+ */
+ function getNamespaces() {
+ return $this->namespaces;
}
-
+
/**
- * Check if query starts with all: prefix
+ * Users of hook SpecialSearchSetupEngine can use this to
+ * add more params to links to not lose selection when
+ * user navigates search results.
+ * @since 1.18
*
- * @param $term String: the string to check
- * @return Boolean
+ * @param string $key
+ * @param mixed $value
*/
- protected function startsWithAll( $term ) {
+ public function setExtraParam( $key, $value ) {
+ $this->extraParams[$key] = $value;
+ }
- $allkeyword = wfMsgForContent('searchall');
-
- $p = explode( ':', $term );
- if( count( $p ) > 1 ) {
- return $p[0] == $allkeyword;
- }
- return false;
+ protected function getGroupName() {
+ return 'pages';
}
}
-