X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FSpecialSearch.php;h=5b642bfaca5faa08d0550af53a9d981c410f0848;hb=98c66071a35d14a1e396e248796295f91bacf7e9;hp=d4e5e64808df5acfda2ea4db8513336f912774c1;hpb=64ac6b8e775dd8582ea9cc5f24235488f076c5b3;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/SpecialSearch.php b/includes/SpecialSearch.php index d4e5e64808..5b642bfaca 100644 --- a/includes/SpecialSearch.php +++ b/includes/SpecialSearch.php @@ -1,34 +1,36 @@ # http://www.mediawiki.org/ -# +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # http://www.gnu.org/copyleft/gpl.html /** * Run text & title search and display the output - * @package MediaWiki - * @subpackage SpecialPage + * @addtogroup SpecialPage */ -require_once( 'SearchEngine.php' ); - -function wfSpecialSearch( $par='' ) { +/** + * Entry point + * + * @param $par String: (default '') + */ +function wfSpecialSearch( $par = '' ) { global $wgRequest, $wgUser; - - $search = $wgRequest->getText( 'search', $par ); + + $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $par ) ); $searchPage = new SpecialSearch( $wgRequest, $wgUser ); if( $wgRequest->getVal( 'fulltext' ) || !is_null( $wgRequest->getVal( 'offset' ) ) || @@ -39,45 +41,47 @@ function wfSpecialSearch( $par='' ) { } } - +/** + * implements Special:Search - Run text & title search and display the output + * @addtogroup SpecialPage + */ class SpecialSearch { + /** * Set up basic search parameters from the request and user settings. * Typically you'll pass $wgRequest and $wgUser. * * @param WebRequest $request * @param User $user - * @access public + * @public */ function SpecialSearch( &$request, &$user ) { list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); - - if( $request->getCheck( 'searchx' ) ) { - $this->namespaces = $this->powerSearch( $request ); - } else { + + $this->namespaces = $this->powerSearch( $request ); + if( empty( $this->namespaces ) ) { $this->namespaces = $this->userNamespaces( $user ); } - - $this->searchRedirects = false; + + $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; } - + /** - * If an exact title match can be found, jump straight ahead to + * If an exact title match can be found, jump straight ahead to it. * @param string $term - * @access public + * @public */ function goResult( $term ) { global $wgOut; global $wgGoToEdit; - + $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 ) ){ + if( is_null( $t ) ){ return $this->showResults( $term ); } @@ -87,104 +91,160 @@ class SpecialSearch { $wgOut->redirect( $t->getFullURL() ); return; } - + # No match, generate an edit URL $t = Title::newFromText( $term ); - if( is_null( $t ) ) { - $editurl = ''; # hrm... - } else { + if( ! is_null( $t ) ) { + wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); # If the feature is enabled, go straight to the edit page if ( $wgGoToEdit ) { $wgOut->redirect( $t->getFullURL( 'action=edit' ) ); return; - } else { - $editurl = $t->escapeLocalURL( 'action=edit' ); - } + } + } + if( $t->quickUserCan( 'create' ) && $t->quickUserCan( 'edit' ) ) { + $wgOut->addWikiMsg( 'noexactmatch', wfEscapeWikiText( $term ) ); + } else { + $wgOut->addWikiMsg( 'noexactmatch-nocreate', wfEscapeWikiText( $term ) ); } - # FIXME: HTML in wiki message - $wgOut->addHTML( '

' . wfMsg('nogomatch', $editurl, htmlspecialchars( $term ) ) . "

\n" ); return $this->showResults( $term ); } - + /** * @param string $term - * @access public + * @public */ function showResults( $term ) { $fname = 'SpecialSearch::showResults'; wfProfileIn( $fname ); - + $this->setupPage( $term ); - - global $wgUser, $wgOut; - $sk = $wgUser->getSkin(); - $wgOut->addWikiText( wfMsg( 'searchresulttext' ) ); - - #if ( !$this->parseQuery() ) { + + global $wgOut; + $wgOut->addWikiMsg( 'searchresulttext' ); + if( '' === trim( $term ) ) { - $wgOut->addWikiText( - '==' . wfMsg( 'badquery' ) . "==\n" . - wfMsg( 'badquerytext' ) ); + // Empty query -- straight view of search form + $wgOut->setSubtitle( '' ); + $wgOut->addHTML( $this->powerSearchBox( $term ) ); + $wgOut->addHTML( $this->powerSearchFocus() ); wfProfileOut( $fname ); return; } - + global $wgDisableTextSearch; if ( $wgDisableTextSearch ) { + global $wgForwardSearchUrl; + if( $wgForwardSearchUrl ) { + $url = str_replace( '$1', urlencode( $term ), $wgForwardSearchUrl ); + $wgOut->redirect( $url ); + return; + } global $wgInputEncoding; - $wgOut->addHTML( wfMsg( 'searchdisabled' ) ); - $wgOut->addHTML( wfMsg( 'googlesearch', - htmlspecialchars( $term ), - htmlspecialchars( $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( $fname ); return; } + + $wgOut->addHTML( $this->shortDialog( $term ) ); - $search =& SearchEngine::create(); + $search = SearchEngine::create(); $search->setLimitOffset( $this->limit, $this->offset ); $search->setNamespaces( $this->namespaces ); - $titleMatches = $search->searchTitle( $term ); - $textMatches = $search->searchText( $term ); + $search->showRedirects = $this->searchRedirects; + $rewritten = $search->replacePrefixes($term); - $num = $titleMatches->numRows() + $textMatches->numRows(); - if ( $num >= $this->limit ) { - $top = wfShowingResults( $this->offset, $this->limit ); - } else { - $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); + $titleMatches = $search->searchTitle( $rewritten ); + + // Sometimes the search engine knows there are too many hits + if ($titleMatches instanceof SearchResultTooMany) { + $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" ); + $wgOut->addHTML( $this->powerSearchBox( $term ) ); + $wgOut->addHTML( $this->powerSearchFocus() ); + wfProfileOut( $fname ); + return; + } + $textMatches = $search->searchText( $rewritten ); + + // did you mean... + if($textMatches && $textMatches->hasSuggestion()){ + global $wgScript; + $fulltext = htmlspecialchars(wfMsg('search')); + $suggestLink = '' + .$textMatches->getSuggestionSnippet().''; + $wgOut->addHTML('
'.wfMsg('search-suggest',$suggestLink).'
'); + } + + + $num = ( $titleMatches ? $titleMatches->numRows() : 0 ) + + ( $textMatches ? $textMatches->numRows() : 0); + $totalNum = 0; + if($titleMatches && !is_null($titleMatches->getTotalHits())) + $totalNum += $titleMatches->getTotalHits(); + if($textMatches && !is_null($textMatches->getTotalHits())) + $totalNum += $textMatches->getTotalHits(); + if ( $num > 0 ) { + if ( $totalNum > 0 ){ + $top = wfMsgExt('showingresultstotal',array( 'parseinline' ), $this->offset+1, $this->offset+$num, $totalNum); + } elseif ( $num >= $this->limit ) { + $top = wfShowingResults( $this->offset, $this->limit ); + } else { + $top = wfShowingResultsNum( $this->offset, $this->limit, $num ); + } + $wgOut->addHTML( "

{$top}

\n" ); } - $wgOut->addHTML( "

{$top}

\n" ); if( $num || $this->offset ) { $prevnext = wfViewPrevNext( $this->offset, $this->limit, - 'Special:Search', + SpecialPage::getTitleFor( 'Search' ), wfArrayToCGI( $this->powerSearchOptions(), - array( 'search' => $term ) ) ); - $wgOut->addHTML( "
{$prevnext}\n" ); + array( 'search' => $term ) ), + ($num < $this->limit) ); + $wgOut->addHTML( "

{$prevnext}

\n" ); + wfRunHooks( 'SpecialSearchResults', array( $term, $titleMatches, $textMatches ) ); + } else { + wfRunHooks( 'SpecialSearchNoResults', array( $term ) ); } - global $wgContLang; - $tm = $wgContLang->convertForSearchResult( $search->termMatches() ); - $terms = implode( '|', $tm ); - - if( $titleMatches->numRows() ) { - $wgOut->addWikiText( '==' . wfMsg( 'titlematches' ) . "==\n" ); - $wgOut->addHTML( $this->showMatches( $titleMatches, $terms ) ); - } else { - $wgOut->addWikiText( '==' . wfMsg( 'notitlematches' ) . "==\n" ); + if( $titleMatches ) { + if( $titleMatches->numRows() ) { + $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' ); + $wgOut->addHTML( $this->showMatches( $titleMatches ) ); + } else { + $wgOut->wrapWikiMsg( "==$1==\n", 'notitlematches' ); + } + $titleMatches->free(); } - - if( $textMatches->numRows() ) { - $wgOut->addWikiText( '==' . wfMsg( 'textmatches' ) . "==\n" ); - $wgOut->addHTML( $this->showMatches( $textMatches, $terms ) ); - } elseif( $num == 0 ) { - # Don't show the 'no text matches' if we received title matches - $wgOut->addWikiText( '==' . wfMsg( 'notextmatches' ) . "==\n" ); + + if( $textMatches ) { + 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 ) ); + } elseif( $num == 0 ) { + # Don't show the 'no text matches' if we received title matches + $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' ); + } + $textMatches->free(); } - + if ( $num == 0 ) { - $wgOut->addWikiText( wfMsg( 'nonefound' ) ); + $wgOut->addWikiMsg( 'nonefound' ); } if( $num || $this->offset ) { $wgOut->addHTML( "

{$prevnext}

\n" ); @@ -192,28 +252,30 @@ class SpecialSearch { $wgOut->addHTML( $this->powerSearchBox( $term ) ); wfProfileOut( $fname ); } - + #------------------------------------------------------------------ # Private methods below this line - + /** - * + * */ function setupPage( $term ) { global $wgOut; - $wgOut->setPageTitle( wfMsg( 'searchresults' ) ); - $wgOut->setSubtitle( wfMsg( 'searchquery', htmlspecialchars( $term ) ) ); + if( !empty( $term ) ) + $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 - * @access private + * @private */ function userNamespaces( &$user ) { $arr = array(); @@ -224,14 +286,14 @@ class SpecialSearch { } return $arr; } - + /** * Extract "power search" namespace settings from the request object, * returning a list of index numbers to search. * * @param WebRequest $request * @return array - * @access private + * @private */ function powerSearch( &$request ) { $arr = array(); @@ -242,11 +304,11 @@ class SpecialSearch { } return $arr; } - + /** * Reconstruct the 'power search' options for links * @return array - * @access private + * @private */ function powerSearchOptions() { $opt = array(); @@ -254,26 +316,30 @@ class SpecialSearch { $opt['ns' . $n] = 1; } $opt['redirs'] = $this->searchRedirects ? 1 : 0; - $opt['searchx'] = 1; return $opt; } + + /** - * @param ResultWrapper $matches + * @param SearchResultSet $matches * @param string $terms partial regexp for highlighting terms */ - function showMatches( &$matches, $terms ) { + function showMatches( &$matches ) { $fname = 'SpecialSearch::showMatches'; wfProfileIn( $fname ); - - global $wgOut; + + global $wgContLang; + $tm = $wgContLang->convertForSearchResult( $matches->termMatches() ); + $terms = implode( '|', $tm ); + $off = $this->offset + 1; - $out = "
    \n"; + $out = "
\n"; + $out .= "\n"; // convert the whole thing to desired language variant global $wgContLang; @@ -281,57 +347,166 @@ class SpecialSearch { wfProfileOut( $fname ); return $out; } - + /** * Format a single hit result - * @param object $row + * @param SearchResult $result * @param string $terms partial regexp for highlighting terms */ - function showHit( $row, $terms ) { + function showHit( $result, $terms ) { $fname = 'SpecialSearch::showHit'; wfProfileIn( $fname ); - global $wgUser, $wgContLang; + global $wgUser, $wgContLang, $wgLang; - $t = Title::makeTitle( $row->page_namespace, $row->page_title ); + $t = $result->getTitle(); if( is_null( $t ) ) { wfProfileOut( $fname ); return "\n"; } - $sk =& $wgUser->getSkin(); + $sk = $wgUser->getSkin(); + + //$contextlines = $wgUser->getOption( 'contextlines', 5 ); + $contextlines = 2; // Hardcode this. Old defaults sucked. :) + $contextchars = $wgUser->getOption( 'contextchars', 50 ); + + $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet()); + + //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()) { + return "
  • {$link}
  • \n"; + } + + $revision = Revision::newFromTitle( $t ); + // 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( !$revision ) { + return "\n"; + } + + if( is_null( $result->getScore() ) ) { + // Search engine doesn't report scoring info + $score = ''; + } else { + $percent = sprintf( '%2.1f', $result->getScore() * 100 ); + $score = wfMsg( 'search-result-score', $wgLang->formatNum( $percent ) ) + . ' - '; + } + + // try to fetch everything from the search engine backend + // then fill-in what couldn't be fetched + $extract = $result->getTextSnippet(); + $byteSize = $result->getByteSize(); + $wordCount = $result->getWordCount(); + $timestamp = $result->getTimestamp(); + $redirectTitle = $result->getRedirectTitle(); + $redirectText = $result->getRedirectSnippet(); + $sectionTitle = $result->getSectionTitle(); + $sectionText = $result->getSectionSnippet(); - $contextlines = $wgUser->getOption( 'contextlines' ); - if ( '' == $contextlines ) { $contextlines = 5; } - $contextchars = $wgUser->getOption( 'contextchars' ); - if ( '' == $contextchars ) { $contextchars = 50; } + // fallback + if( is_null($extract) || is_null($wordCount) || is_null($byteSize) ){ + $text = $revision->getText(); + if( is_null($extract) ) + $extract = $this->extractText( $text, $terms, $contextlines, $contextchars ); + if( is_null($byteSize) ) + $byteSize = strlen( $text ); + if( is_null($wordCount) ) + $wordCount = str_word_count( $text ); + } + if( is_null($timestamp) ){ + $timestamp = $revision->getTimestamp(); + } + + // format description + $size = wfMsgExt( 'search-result-size', array( 'parsemag', 'escape' ), + $sk->formatSize( $byteSize ), + $wordCount ); + $date = $wgLang->timeanddate( $timestamp ); + + // format redirects / sections + $redirect = ''; + if( !is_null($redirectTitle) ) + $redirect = "" + .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText)) + .""; + $section = ''; + if( !is_null($sectionTitle) ) + $section = "" + .wfMsg('search-section', $sk->makeKnownLinkObj( $sectionTitle, $sectionText)) + .""; + // wrap extract + $extract = "
    ".$extract."
    "; + + // Include a thumbnail for media files... + if( $t->getNamespace() == NS_IMAGE ) { + $img = wfFindFile( $t ); + if( $img ) { + $thumb = $img->getThumbnail( 120, 120 ); + if( $thumb ) { + $desc = $img->getShortDesc(); + wfProfileOut( $fname ); + // Ugly table. :D + // Float doesn't seem to interact well with the bullets. + // Table messes up vertical alignment of the bullet, but I'm + // not sure what more I can do about that. :( + return "
  • " . + '' . + '' . + '' . + '' . + '' . + '
    ' . + $thumb->toHtml( array( 'desc-link' => true ) ) . + '' . + $link . + $extract . + "
    {$score}{$desc} - {$date}
    " . + '
    ' . + "
  • \n"; + } + } + } - $link = $sk->makeKnownLinkObj( $t, '' ); - $text = Article::getRevisionText( $row ); - $size = wfMsg( 'nbytes', strlen( $text ) ); + wfProfileOut( $fname ); + return "
  • {$link} {$redirect} {$section} {$extract}\n" . + "
    {$score}{$size} - {$date}
    " . + "
  • \n"; + } + + private function extractText( $text, $terms, $contextlines, $contextchars ) { + global $wgLang, $wgContLang; + $fname = __METHOD__; + $lines = explode( "\n", $text ); - $max = IntVal( $contextchars ) + 1; + $max = intval( $contextchars ) + 1; $pat1 = "/(.*)($terms)(.{0,$max})/i"; $lineno = 0; - - $extract = ''; + + $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, '...' ); + $pre = $wgContLang->truncate( $m[1], -$contextchars, ' ... ' ); if ( count( $m ) < 3 ) { $post = ''; } else { - $post = $wgContLang->truncate( $m[3], $contextchars, '...' ); + $post = $wgContLang->truncate( $m[3], $contextchars, ' ... ' ); } $found = $m[2]; @@ -341,48 +516,74 @@ class SpecialSearch { $line = preg_replace( $pat2, "\\1", $line ); - $extract .= "
    {$lineno}: {$line}\n"; + $extract .= "${line}\n"; } wfProfileOut( "$fname-extract" ); - wfProfileOut( $fname ); - return "
  • {$link} ({$size}){$extract}
  • \n"; + + return $extract; } - + + /** + * Generates the power search box at bottom of [[Special:Search]] + * @param $term string: search term + * @return $out string: HTML form + */ function powerSearchBox( $term ) { + global $wgScript; + $namespaces = ''; foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { - $checked = in_array( $ns, $this->namespaces ) - ? ' checked="checked"' - : ''; $name = str_replace( '_', ' ', $name ); if( '' == $name ) { $name = wfMsg( 'blanknamespace' ); } - $namespaces .= " \n"; + $namespaces .= Xml::openElement( 'span', array( 'style' => 'white-space: nowrap' ) ) . + Xml::checkLabel( $name, "ns{$ns}", $name, in_array( $ns, $this->namespaces ) ) . + Xml::closeElement( 'span' ) . "\n"; } + + $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1' ) ); + $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) ); + $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' ) ) . "\n"; + + $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) . + Xml::openElement( 'fieldset' ) . + Xml::element( 'legend', array( ), wfMsg( 'powersearch-legend' ) ) . + Xml::hidden( 'title', 'Special:Search' ) . + wfMsgExt( 'powersearchtext', array( 'parse', 'replaceafter' ), + $namespaces, $redirect, $searchField, + '', '', '', '', '', # Dummy placeholders + $searchButton ) . + Xml::closeElement( 'fieldset' ) . + Xml::closeElement( 'form' ); + + return $out; + } + + function powerSearchFocus() { + return ""; + } + + function shortDialog($term) { + global $wgScript; - $checked = $this->searchRedirects - ? ' checked="checked"' - : ''; - $redirect = "\n"; - - $searchField = "\n"; - - $searchButton = '\n"; - - $ret = wfMsg( 'powersearchtext', - $namespaces, $redirect, $searchField, - '', '', '', '', '', # Dummy placeholders - $searchButton ); + $out = Xml::openElement( 'form', array( + 'id' => 'search', + 'method' => 'get', + 'action' => $wgScript + )); + $out .= Xml::hidden( 'title', 'Special:Search' ); + $out .= Xml::input( 'search', 50, $term ) . ' '; + foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { + if( in_array( $ns, $this->namespaces ) ) { + $out .= Xml::hidden( "ns{$ns}", '1' ); + } + } + $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) ); + $out .= Xml::closeElement( 'form' ); - $title = Title::makeTitle( NS_SPECIAL, 'Search' ); - $action = $title->escapeLocalURL(); - return "

    \n
    \n{$ret}\n
    \n"; + return $out; } } - -?>