From d6fd8e7c1345052cfba0f396bbb65b4c93d11366 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Robert=20Stojni=C4=87?= Date: Tue, 15 Apr 2008 23:06:28 +0000 Subject: [PATCH] Ajax suggestions: * check in a new ajax suggestion engine (mwsuggest.js) which uses OpenSearch to fetch results (by default via API), this should deprecated the old ajaxsearch thingy * extend PrefixSearchBackend hook to accept multiple namespaces for future lucene use (default implementation however can still process only one) * Added to preferences, also a feature to turn it on/off for every input (disabled atm until I work out browser issues completely) * WMF wikis probably won't be using API to fetch results, but a custom php wrapper that just forwards the request to appropriate lucene daemon, added support for that SpecialSearch: * moved stuff out of SpecialSearch to SearchEngine, like snippet highlighting and such * support for additional interwiki results, e.g. title matches from other projects shown in a separate box on the right * todo: interwiki box doesn't have standard prev/next links to avoid clutter and unintuitive interface * support for related articles --- docs/hooks.txt | 2 +- includes/DefaultSettings.php | 26 + includes/OutputPage.php | 7 +- includes/PrefixSearch.php | 67 ++- includes/SearchEngine.php | 240 +++++++++- includes/Skin.php | 8 + includes/SpecialPreferences.php | 13 +- includes/SpecialSearch.php | 310 +++++++----- includes/api/ApiOpenSearch.php | 15 +- languages/messages/MessagesEn.php | 9 + maintenance/language/messages.inc | 9 + opensearch_desc.php | 2 +- skins/common/mwsuggest.js | 762 ++++++++++++++++++++++++++++++ skins/common/shared.css | 80 ++++ skins/monobook/main.css | 42 +- 15 files changed, 1402 insertions(+), 190 deletions(-) create mode 100644 skins/common/mwsuggest.js diff --git a/docs/hooks.txt b/docs/hooks.txt index f6a9edc9ca..06de42193e 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -886,7 +886,7 @@ $form : PreferencesForm object 'PrefixSearchBackend': Override the title prefix search used for OpenSearch and AJAX search suggestions. Put results into &$results outparam and return false. -$ns : int namespace key to search in +$ns : array of int namespace keys to search in $search : search term (not guaranteed to be conveniently normalized) $limit : maximum number of results to return &$results : out param: array of page names (strings) diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index cf5206f6cc..07391f4f1e 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -1597,6 +1597,32 @@ $wgDisableCounters = false; $wgDisableTextSearch = false; $wgDisableSearchContext = false; + +/** + * Template for OpenSearch suggestions, defaults to API action=opensearch + * + * Sites with heavy load would tipically have these point to a custom + * PHP wrapper to avoid firing up mediawiki for every keystroke + * + * Placeholders: {searchTerms} + * + */ +$wgOpenSearchTemplate = false; + +/** + * Enable suggestions while typing in search boxes + * (results are passed around in OpenSearch format) + */ +$wgEnableMWSuggest = false; + +/** + * Template for internal MediaWiki suggestion engine, defaults to API action=opensearch + * + * Placeholders: {searchTerms}, {namespaces}, {dbname} + * + */ +$wgMWSuggestTemplate = false; + /** * If you've disabled search semi-permanently, this also disables updates to the * table. If you ever re-enable, be sure to rebuild the search table. diff --git a/includes/OutputPage.php b/includes/OutputPage.php index c8c3babd65..e915b7a126 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -670,7 +670,7 @@ class OutputPage { global $wgUser, $wgOutputEncoding, $wgRequest; global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType; global $wgJsMimeType, $wgStylePath, $wgUseAjax, $wgAjaxSearch, $wgAjaxWatch; - global $wgServer, $wgStyleVersion; + global $wgServer, $wgStyleVersion, $wgEnableMWSuggest; if( $this->mDoNothing ){ return; @@ -772,10 +772,13 @@ class OutputPage { if( $wgAjaxWatch && $wgUser->isLoggedIn() ) { $this->addScript( "\n" ); } + + if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ){ + $this->addScript( "\n" ); + } } - # Buffer output; final headers may depend on later processing ob_start(); diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index 1a43afe7f6..a3ff05e25c 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -5,19 +5,23 @@ class PrefixSearch { * Do a prefix search of titles and return a list of matching page names. * @param string $search * @param int $limit + * @param array $namespaces - used if query is not explicitely prefixed * @return array of strings */ - public static function titleSearch( $search, $limit ) { + public static function titleSearch( $search, $limit, $namespaces=array() ) { $search = trim( $search ); if( $search == '' ) { return array(); // Return empty result } - + $namespaces = self::validateNamespaces( $namespaces ); + $title = Title::newFromText( $search ); if( $title && $title->getInterwiki() == '' ) { - $ns = $title->getNamespace(); + $ns = array($title->getNamespace()); + if($ns[0] == NS_MAIN) + $ns = $namespaces; // no explicit prefix, use default namespaces return self::searchBackend( - $title->getNamespace(), $title->getText(), $limit ); + $ns, $title->getText(), $limit ); } // Is this a namespace prefix? @@ -26,29 +30,32 @@ class PrefixSearch { && $title->getNamespace() != NS_MAIN && $title->getInterwiki() == '' ) { return self::searchBackend( - $title->getNamespace(), '', $limit ); + array($title->getNamespace()), '', $limit ); } - - return self::searchBackend( 0, $search, $limit ); + + return self::searchBackend( $namespaces, $search, $limit ); } /** * Do a prefix search of titles and return a list of matching page names. + * @param array $namespaces * @param string $search * @param int $limit * @return array of strings */ - protected static function searchBackend( $ns, $search, $limit ) { - if( $ns == NS_MEDIA ) { - $ns = NS_IMAGE; - } elseif( $ns == NS_SPECIAL ) { - return self::specialSearch( $search, $limit ); + protected static function searchBackend( $namespaces, $search, $limit ) { + if( count($namespaces) == 1 ){ + $ns = $namespaces[0]; + if( $ns == NS_MEDIA ) { + $namespaces = array(NS_IMAGE); + } elseif( $ns == NS_SPECIAL ) { + return self::specialSearch( $search, $limit ); + } } - $srchres = array(); - if( wfRunHooks( 'PrefixSearchBackend', array( $ns, $search, $limit, &$srchres ) ) ) { - return self::defaultSearchBackend( $ns, $search, $limit ); + if( wfRunHooks( 'PrefixSearchBackend', array( $namespaces, $search, $limit, &$srchres ) ) ) { + return self::defaultSearchBackend( $namespaces, $search, $limit ); } return $srchres; } @@ -91,18 +98,22 @@ class PrefixSearch { * Unless overridden by PrefixSearchBackend hook... * This is case-sensitive except the first letter (per $wgCapitalLinks) * - * @param int $ns Namespace to search in + * @param array $namespaces Namespaces to search in * @param string $search term * @param int $limit max number of items to return * @return array of title strings */ - protected static function defaultSearchBackend( $ns, $search, $limit ) { + protected static function defaultSearchBackend( $namespaces, $search, $limit ) { global $wgCapitalLinks, $wgContLang; if( $wgCapitalLinks ) { $search = $wgContLang->ucfirst( $search ); } + $ns = array_shift($namespaces); // support only one namespace + if( in_array(NS_MAIN,$namespaces)) + $ns = NS_MAIN; // if searching on many always default to main + // Prepare nested request $req = new FauxRequest(array ( 'action' => 'query', @@ -129,5 +140,25 @@ class PrefixSearch { return $srchres; } - + + /** + * Validate an array of numerical namespace indexes + * + * @param array $namespaces + */ + protected static function validateNamespaces($namespaces){ + global $wgContLang; + $validNamespaces = $wgContLang->getNamespaces(); + if( is_array($namespaces) && count($namespaces)>0 ){ + $valid = array(); + foreach ($namespaces as $ns){ + if( is_numeric($ns) && array_key_exists($ns, $validNamespaces) ) + $valid[] = $ns; + } + if( count($valid) > 0 ) + return $valid; + } + + return array( NS_MAIN ); + } } diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php index 4d8909c456..00cb185f36 100644 --- a/includes/SearchEngine.php +++ b/includes/SearchEngine.php @@ -35,7 +35,7 @@ class SearchEngine { function searchTitle( $term ) { return null; } - + /** * If an exact title match can be find, or a very slightly close match, * return the title. If no match, returns NULL. @@ -222,6 +222,50 @@ class SearchEngine { } return $arr; } + + /** + * Extract default namespaces to search from the given user's + * settings, returning a list of index numbers. + * + * @param User $user + * @return array + * @static + */ + public static function userNamespaces( &$user ) { + $arr = array(); + foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { + if( $user->getOption( 'searchNs' . $ns ) ) { + $arr[] = $ns; + } + } + return $arr; + } + + /** + * Find snippet highlight settings for a given user + * + * @param User $user + * @return array contextlines, contextchars + * @static + */ + public static function userHighlightPrefs( &$user ){ + //$contextlines = $user->getOption( 'contextlines', 5 ); + $contextlines = 2; // Hardcode this. Old defaults sucked. :) + $contextchars = $user->getOption( 'contextchars', 50 ); + return array($contextlines, $contextchars); + } + + /** + * An array of namespaces indexes to be searched by default + * + * @return array + * @static + */ + public static function defaultNamespaces(){ + global $wgNamespacesToBeSearchedDefault; + + return array_keys($wgNamespacesToBeSearchedDefault, true); + } /** * Return a 'cleaned up' search string @@ -281,6 +325,37 @@ class SearchEngine { function updateTitle( $id, $title ) { // no-op } + + /** + * Get OpenSearch suggestion template + * + * @return string + * @static + */ + public static function getOpenSearchTemplate() { + global $wgOpenSearchTemplate, $wgServer, $wgScriptPath; + if($wgOpenSearchTemplate) + return $wgOpenSearchTemplate; + else{ + $ns = implode(',',SearchEngine::defaultNamespaces()); + if(!$ns) $ns = "0"; + return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace='.$ns; + } + } + + /** + * Get internal MediaWiki Suggest template + * + * @return string + * @static + */ + public static function getMWSuggestTemplate() { + global $wgMWSuggestTemplate, $wgServer, $wgScriptPath; + if($wgMWSuggestTemplate) + return $wgMWSuggestTemplate; + else + return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}'; + } } @@ -352,6 +427,35 @@ class SearchResultSet { function getSuggestionSnippet(){ return ''; } + + /** + * Return information about how and from where the results were fetched, + * should be useful for diagnostics and debugging + * + * @return string + */ + function getInfo() { + return null; + } + + /** + * Return a result set of hits on other (multiple) wikis associated with this one + * + * @return SearchResultSet + */ + function getInterwikiResults() { + return null; + } + + /** + * Check if there are results on other wikis + * + * @return boolean + */ + function hasInterwikiResults() { + return $this->getInterwikiResults() != null; + } + /** * Fetches next search result, or false. @@ -388,6 +492,32 @@ class SearchResult { function SearchResult( $row ) { $this->mTitle = Title::makeTitle( $row->page_namespace, $row->page_title ); + if( !is_null($this->mTitle) ) + $this->mRevision = Revision::newFromTitle( $this->mTitle ); + } + + /** + * Check if this is result points to an invalid title + * + * @return boolean + * @access public + */ + function isBrokenTitle(){ + if( is_null($this->mTitle) ) + return true; + return false; + } + + /** + * Check if target page is missing, happens when index is out of date + * + * @return boolean + * @access public + */ + function isMissingRevision(){ + if( !$this->mRevision ) + return true; + return false; } /** @@ -406,23 +536,93 @@ class SearchResult { } /** - * @return string highlighted text snippet, null if not supported + * Lazy initialization of article text from DB */ - function getTextSnippet(){ - return null; + protected function initText(){ + if( !isset($this->mText) ){ + $this->mText = $this->mRevision->getText(); + } } /** + * @param array $terms terms to highlight + * @return string highlighted text snippet, null (and not '') if not supported + */ + function getTextSnippet($terms){ + global $wgUser; + $this->initText(); + list($contextlines,$contextchars) = SearchEngine::userHighlightPrefs($wgUser); + return $this->extractText( $this->mText, $terms, $contextlines, $contextchars); + } + + /** + * Default implementation of snippet extraction + * + * @param string $text + * @param array $terms + * @param int $contextlines + * @param int $contextchars + * @return string + */ + protected function extractText( $text, $terms, $contextlines, $contextchars ) { + global $wgLang, $wgContLang; + $fname = __METHOD__; + + $lines = explode( "\n", $text ); + + $terms = implode( '|', $terms ); + $max = intval( $contextchars ) + 1; + $pat1 = "/(.*)($terms)(.{0,$max})/i"; + + $lineno = 0; + + $extract = ""; + wfProfileIn( "$fname-extract" ); + foreach ( $lines as $line ) { + if ( 0 == $contextlines ) { + break; + } + ++$lineno; + $m = array(); + if ( ! preg_match( $pat1, $line, $m ) ) { + continue; + } + --$contextlines; + $pre = $wgContLang->truncate( $m[1], -$contextchars, ' ... ' ); + + if ( count( $m ) < 3 ) { + $post = ''; + } else { + $post = $wgContLang->truncate( $m[3], $contextchars, ' ... ' ); + } + + $found = $m[2]; + + $line = htmlspecialchars( $pre . $found . $post ); + $pat2 = '/(' . $terms . ")/i"; + $line = preg_replace( $pat2, + "\\1", $line ); + + $extract .= "${line}\n"; + } + wfProfileOut( "$fname-extract" ); + + return $extract; + } + + /** + * @param array $terms terms to highlight * @return string highlighted title, '' if not supported */ - function getTitleSnippet(){ + function getTitleSnippet($terms){ return ''; } /** + * @param array $terms terms to highlight * @return string highlighted redirect name (redirect to this page), '' if none or not supported */ - function getRedirectSnippet(){ + function getRedirectSnippet($terms){ return ''; } @@ -448,24 +648,40 @@ class SearchResult { } /** - * @return string timestamp, null if not supported + * @return string timestamp */ function getTimestamp(){ - return null; + return $this->mRevision->getTimestamp(); } /** - * @return int number of words, null if not supported + * @return int number of words */ function getWordCount(){ - return null; + $this->initText(); + return str_word_count( $this->mText ); } /** - * @return int size in bytes, null if not supported + * @return int size in bytes */ function getByteSize(){ - return null; + $this->initText(); + return strlen( $this->mText ); + } + + /** + * @return boolean if hit has related articles + */ + function hasRelated(){ + return false; + } + + /** + * @return interwiki prefix of the title (return iw even if title is broken) + */ + function getInterwikiPrefix(){ + return ''; } } diff --git a/includes/Skin.php b/includes/Skin.php index 1cc1c3f731..5e324bba4e 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -300,6 +300,7 @@ class Skin extends Linker { global $wgUseAjax, $wgAjaxWatch; global $wgVersion, $wgEnableAPI, $wgEnableWriteAPI; global $wgRestrictionTypes, $wgLivePreview; + global $wgMWSuggestTemplate, $wgDBname, $wgEnableMWSuggest; $ns = $wgTitle->getNamespace(); $nsname = isset( $wgCanonicalNamespaceNames[ $ns ] ) ? $wgCanonicalNamespaceNames[ $ns ] : $wgTitle->getNsText(); @@ -331,6 +332,13 @@ class Skin extends Linker { 'wgEnableAPI' => $wgEnableAPI, 'wgEnableWriteAPI' => $wgEnableWriteAPI, ); + + if( $wgUseAjax && $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false )){ + $vars['wgMWSuggestTemplate'] = SearchEngine::getMWSuggestTemplate(); + $vars['wgDBname'] = $wgDBname; + $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $wgUser ); + $vars['wgMWSuggestMessages'] = array( wfMsg('search-mwsuggest-enabled'), wfMsg('search-mwsuggest-disabled')); + } foreach( $wgRestrictionTypes as $type ) $vars['wgRestriction' . ucfirst( $type )] = $wgTitle->getRestrictions( $type ); diff --git a/includes/SpecialPreferences.php b/includes/SpecialPreferences.php index 4b0f3d0576..293db0efb0 100644 --- a/includes/SpecialPreferences.php +++ b/includes/SpecialPreferences.php @@ -66,6 +66,7 @@ class PreferencesForm { $this->mWatchlistDays = $request->getVal( 'wpWatchlistDays' ); $this->mWatchlistEdits = $request->getVal( 'wpWatchlistEdits' ); $this->mUseAjaxSearch = $request->getCheck( 'wpUseAjaxSearch' ); + $this->mDisableMWSuggest = $request->getCheck( 'wpDisableMWSuggest' ); $this->mSaveprefs = $request->getCheck( 'wpSaveprefs' ) && $this->mPosted && @@ -288,6 +289,7 @@ class PreferencesForm { $wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) ); $wgUser->setOption( 'watchlistdays', $this->validateFloat( $this->mWatchlistDays, 0, 7 ) ); $wgUser->setOption( 'ajaxsearch', $this->mUseAjaxSearch ); + $wgUser->setOption( 'disablesuggest', $this->mDisableMWSuggest ); # Set search namespace options foreach( $this->mSearchNs as $i => $value ) { @@ -400,6 +402,7 @@ class PreferencesForm { $this->mUnderline = $wgUser->getOption( 'underline' ); $this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' ); $this->mUseAjaxSearch = $wgUser->getBoolOption( 'ajaxsearch' ); + $this->mDisableMWSuggest = $wgUser->getBoolOption( 'disablesuggest' ); $togs = User::getToggles(); foreach ( $togs as $tname ) { @@ -517,7 +520,7 @@ class PreferencesForm { global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress; global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication; global $wgContLanguageCode, $wgDefaultSkin, $wgSkipSkins, $wgAuth; - global $wgEmailConfirmToEdit, $wgAjaxSearch; + global $wgEmailConfirmToEdit, $wgAjaxSearch, $wgEnableMWSuggest; $wgOut->setPageTitle( wfMsg( 'preferences' ) ); $wgOut->setArticleRelated( false ); @@ -980,8 +983,13 @@ class PreferencesForm { wfLabel( wfMsg( 'useajaxsearch' ), 'wpUseAjaxSearch' ), wfCheck( 'wpUseAjaxSearch', $this->mUseAjaxSearch, array( 'id' => 'wpUseAjaxSearch' ) ) ) : ''; + $mwsuggest = $wgEnableMWSuggest ? + $this->addRow( + wfLabel( wfMsg( 'mwsuggest-disable' ), 'wpDisableMWSuggest' ), + wfCheck( 'wpDisableMWSuggest', $this->mDisableMWSuggest, array( 'id' => 'wpDisableMWSuggest' ) ) + ) : ''; $wgOut->addHTML( '
' . wfMsg( 'searchresultshead' ) . '' . - $ajaxsearch . + $ajaxsearch . $this->addRow( wfLabel( wfMsg( 'resultsperpage' ), 'wpSearch' ), wfInput( 'wpSearch', 4, $this->mSearch, array( 'id' => 'wpSearch' ) ) @@ -994,6 +1002,7 @@ class PreferencesForm { wfLabel( wfMsg( 'contextchars' ), 'wpSearchChars' ), wfInput( 'wpSearchChars', 4, $this->mSearchChars, array( 'id' => 'wpSearchChars' ) ) ) . + $mwsuggest. "
" . wfMsg( 'defaultns' ) . "$ps
" ); # Misc diff --git a/includes/SpecialSearch.php b/includes/SpecialSearch.php index 85726ce227..dd9c9c5264 100644 --- a/includes/SpecialSearch.php +++ b/includes/SpecialSearch.php @@ -32,10 +32,10 @@ function wfSpecialSearch( $par = '' ) { $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $par ) ); $searchPage = new SpecialSearch( $wgRequest, $wgUser ); - if( $wgRequest->getVal( 'fulltext' ) || - !is_null( $wgRequest->getVal( 'offset' ) ) || - !is_null ($wgRequest->getVal( 'searchx' ) ) ) { - $searchPage->showResults( $search ); + if( $wgRequest->getVal( 'fulltext' ) + || !is_null( $wgRequest->getVal( 'offset' )) + || !is_null( $wgRequest->getVal( 'searchx' ))) { + $searchPage->showResults( $search, 'search' ); } else { $searchPage->goResult( $search ); } @@ -60,7 +60,7 @@ class SpecialSearch { $this->namespaces = $this->powerSearch( $request ); if( empty( $this->namespaces ) ) { - $this->namespaces = $this->userNamespaces( $user ); + $this->namespaces = SearchEngine::userNamespaces( $user ); } $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; @@ -118,10 +118,11 @@ class SpecialSearch { function showResults( $term ) { $fname = 'SpecialSearch::showResults'; wfProfileIn( $fname ); + global $wgOut, $wgUser; + $sk = $wgUser->getSkin(); $this->setupPage( $term ); - global $wgOut; $wgOut->addWikiMsg( 'searchresulttext' ); if( '' === trim( $term ) ) { @@ -175,19 +176,24 @@ class SpecialSearch { wfProfileOut( $fname ); return; } + $textMatches = $search->searchText( $rewritten ); - // did you mean... + // did you mean... suggestions if($textMatches && $textMatches->hasSuggestion()){ - global $wgScript; - $fulltext = htmlspecialchars(wfMsg('search')); - $suggestLink = '' - .$textMatches->getSuggestionSnippet().''; + $st = SpecialPage::getTitleFor( 'Search' ); + $stParams = wfArrayToCGI( array( + 'search' => $textMatches->getSuggestionQuery(), + 'fulltext' => wfMsg('search')), + $this->powerSearchOptions()); + + $suggestLink = ''. + $textMatches->getSuggestionSnippet().''; + $wgOut->addHTML('
'.wfMsg('search-suggest',$suggestLink).'
'); } - + // show number of results $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) + ( $textMatches ? $textMatches->numRows() : 0); $totalNum = 0; @@ -197,7 +203,8 @@ class SpecialSearch { $totalNum += $textMatches->getTotalHits(); if ( $num > 0 ) { if ( $totalNum > 0 ){ - $top = wfMsgExt('showingresultstotal',array( 'parseinline' ), $this->offset+1, $this->offset+$num, $totalNum); + $top = wfMsgExt('showingresultstotal', array( 'parseinline' ), + $this->offset+1, $this->offset+$num, $totalNum ); } elseif ( $num >= $this->limit ) { $top = wfShowingResults( $this->offset, $this->limit ); } else { @@ -206,6 +213,7 @@ class SpecialSearch { $wgOut->addHTML( "

{$top}

\n" ); } + // prev/next links if( $num || $this->offset ) { $prevnext = wfViewPrevNext( $this->offset, $this->limit, SpecialPage::getTitleFor( 'Search' ), @@ -230,16 +238,23 @@ class SpecialSearch { } if( $textMatches ) { + // output appropriate heading if( $textMatches->numRows() ) { if($titleMatches) $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' ); else // if no title matches the heading is redundant - $wgOut->addHTML("
"); - $wgOut->addHTML( $this->showMatches( $textMatches ) ); + $wgOut->addHTML("
"); } elseif( $num == 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 )); + // show results + if( $textMatches->numRows() ) + $wgOut->addHTML( $this->showMatches( $textMatches ) ); + $textMatches->free(); } @@ -255,38 +270,20 @@ class SpecialSearch { #------------------------------------------------------------------ # Private methods below this line - + /** * */ function setupPage( $term ) { global $wgOut; if( !empty( $term ) ) - $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); + $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); $subtitlemsg = ( Title::newFromText( $term ) ? 'searchsubtitle' : 'searchsubtitleinvalid' ); $wgOut->setSubtitle( $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ) ); $wgOut->setArticleRelated( false ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); } - /** - * Extract default namespaces to search from the given user's - * settings, returning a list of index numbers. - * - * @param User $user - * @return array - * @private - */ - function userNamespaces( &$user ) { - $arr = array(); - foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - if( $user->getOption( 'searchNs' . $ns ) ) { - $arr[] = $ns; - } - } - return $arr; - } - /** * Extract "power search" namespace settings from the request object, * returning a list of index numbers to search. @@ -319,22 +316,27 @@ class SpecialSearch { return $opt; } - - /** + * Show whole set of results + * * @param SearchResultSet $matches - * @param string $terms partial regexp for highlighting terms */ function showMatches( &$matches ) { $fname = 'SpecialSearch::showMatches'; wfProfileIn( $fname ); global $wgContLang; - $tm = $wgContLang->convertForSearchResult( $matches->termMatches() ); - $terms = implode( '|', $tm ); - + $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); + + $out = ""; + + $infoLine = $matches->getInfo(); + if( !is_null($infoLine) ) + $out .= "\n\n"; + + $off = $this->offset + 1; - $out = "