MediaWiki  REL1_19
SpecialSearch.php
Go to the documentation of this file.
00001 <?php
00030 class SpecialSearch extends SpecialPage {
00039         protected $profile;
00040         function getProfile() { return $this->profile; }
00041 
00043         protected $searchEngine;
00044 
00046         protected $extraParams = array();
00047 
00049         protected $mPrefix;
00050 
00054         protected $limit, $offset;
00055 
00059         protected $namespaces;
00060         function getNamespaces() { return $this->namespaces; }
00061 
00065         protected $searchRedirects;
00066 
00070         protected $didYouMeanHtml, $fulltext;
00071 
00072         const NAMESPACES_CURRENT = 'sense';
00073 
00074         public function __construct() {
00075                 parent::__construct( 'Search' );
00076         }
00077 
00083         public function execute( $par ) {
00084                 $this->setHeaders();
00085                 $this->outputHeader();
00086                 $out = $this->getOutput();
00087                 $out->allowClickjacking();
00088                 $out->addModuleStyles( 'mediawiki.special' );
00089 
00090                 // Strip underscores from title parameter; most of the time we'll want
00091                 // text form here. But don't strip underscores from actual text params!
00092                 $titleParam = str_replace( '_', ' ', $par );
00093 
00094                 $request = $this->getRequest();
00095 
00096                 // Fetch the search term
00097                 $search = str_replace( "\n", " ", $request->getText( 'search', $titleParam ) );
00098 
00099                 $this->load();
00100 
00101                 if ( $request->getVal( 'fulltext' )
00102                         || !is_null( $request->getVal( 'offset' ) )
00103                         || !is_null( $request->getVal( 'searchx' ) ) )
00104                 {
00105                         $this->showResults( $search );
00106                 } else {
00107                         $this->goResult( $search );
00108                 }
00109         }
00110 
00116         public function load() {
00117                 $request = $this->getRequest();
00118                 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
00119                 $this->mPrefix = $request->getVal( 'prefix', '' );
00120 
00121                 $user = $this->getUser();
00122 
00123                 # Extract manually requested namespaces
00124                 $nslist = $this->powerSearch( $request );
00125                 if ( !count( $nslist ) ) {
00126                         # Fallback to user preference
00127                         $nslist = SearchEngine::userNamespaces( $user );
00128                 }
00129 
00130                 $profile = null;
00131                 if ( !count( $nslist ) ) {
00132                         $profile = 'default';
00133                 }
00134 
00135                 $profile = $request->getVal( 'profile', $profile );
00136                 $profiles = $this->getSearchProfiles();
00137                 if ( $profile === null ) {
00138                         // BC with old request format
00139                         $profile = 'advanced';
00140                         foreach( $profiles as $key => $data ) {
00141                                 if ( $nslist === $data['namespaces'] && $key !== 'advanced') {
00142                                         $profile = $key;
00143                                 }
00144                         }
00145                         $this->namespaces = $nslist;
00146                 } elseif ( $profile === 'advanced' ) {
00147                         $this->namespaces = $nslist;
00148                 } else {
00149                         if ( isset( $profiles[$profile]['namespaces'] ) ) {
00150                                 $this->namespaces = $profiles[$profile]['namespaces'];
00151                         } else {
00152                                 // Unknown profile requested
00153                                 $profile = 'default';
00154                                 $this->namespaces = $profiles['default']['namespaces'];
00155                         }
00156                 }
00157 
00158                 // Redirects defaults to true, but we don't know whether it was ticked of or just missing
00159                 $default = $request->getBool( 'profile' ) ? 0 : 1;
00160                 $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
00161                 $this->didYouMeanHtml = ''; # html of did you mean... link
00162                 $this->fulltext = $request->getVal('fulltext');
00163                 $this->profile = $profile;
00164         }
00165 
00171         public function goResult( $term ) {
00172                 $this->setupPage( $term );
00173                 # Try to go to page as entered.
00174                 $t = Title::newFromText( $term );
00175                 # If the string cannot be used to create a title
00176                 if( is_null( $t ) ) {
00177                         return $this->showResults( $term );
00178                 }
00179                 # If there's an exact or very near match, jump right there.
00180                 $t = SearchEngine::getNearMatch( $term );
00181 
00182                 if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) {
00183                         # Hook requested termination
00184                         return;
00185                 }
00186 
00187                 if( !is_null( $t ) ) {
00188                         $this->getOutput()->redirect( $t->getFullURL() );
00189                         return;
00190                 }
00191                 # No match, generate an edit URL
00192                 $t = Title::newFromText( $term );
00193                 if( !is_null( $t ) ) {
00194                         global $wgGoToEdit;
00195                         wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
00196                         wfDebugLog( 'nogomatch', $t->getText(), false );
00197 
00198                         # If the feature is enabled, go straight to the edit page
00199                         if( $wgGoToEdit ) {
00200                                 $this->getOutput()->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
00201                                 return;
00202                         }
00203                 }
00204                 return $this->showResults( $term );
00205         }
00206 
00210         public function showResults( $term ) {
00211                 global $wgDisableTextSearch, $wgSearchForwardUrl, $wgContLang, $wgScript;
00212                 wfProfileIn( __METHOD__ );
00213 
00214                 $search = $this->getSearchEngine();
00215                 $search->setLimitOffset( $this->limit, $this->offset );
00216                 $search->setNamespaces( $this->namespaces );
00217                 $search->showRedirects = $this->searchRedirects; // BC
00218                 $search->setFeatureData( 'list-redirects', $this->searchRedirects );
00219                 $search->prefix = $this->mPrefix;
00220                 $term = $search->transformSearchTerm($term);
00221 
00222                 wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
00223 
00224                 $this->setupPage( $term );
00225 
00226                 $out = $this->getOutput();
00227 
00228                 if ( $wgDisableTextSearch ) {
00229                         if ( $wgSearchForwardUrl ) {
00230                                 $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
00231                                 $out->redirect( $url );
00232                         } else {
00233                                 $out->addHTML(
00234                                         Xml::openElement( 'fieldset' ) .
00235                                         Xml::element( 'legend', null, wfMsg( 'search-external' ) ) .
00236                                         Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), wfMsg( 'searchdisabled' ) ) .
00237                                         wfMsg( 'googlesearch',
00238                                                 htmlspecialchars( $term ),
00239                                                 htmlspecialchars( 'UTF-8' ),
00240                                                 htmlspecialchars( wfMsg( 'searchbutton' ) )
00241                                         ) .
00242                                         Xml::closeElement( 'fieldset' )
00243                                 );
00244                         }
00245                         wfProfileOut( __METHOD__ );
00246                         return;
00247                 }
00248 
00249                 $t = Title::newFromText( $term );
00250 
00251                 // fetch search results
00252                 $rewritten = $search->replacePrefixes($term);
00253 
00254                 $titleMatches = $search->searchTitle( $rewritten );
00255                 if( !( $titleMatches instanceof SearchResultTooMany ) ) {
00256                         $textMatches = $search->searchText( $rewritten );
00257                 }
00258 
00259                 // did you mean... suggestions
00260                 if( $textMatches && $textMatches->hasSuggestion() ) {
00261                         $st = SpecialPage::getTitleFor( 'Search' );
00262 
00263                         # mirror Go/Search behaviour of original request ..
00264                         $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
00265 
00266                         if( $this->fulltext != null ) {
00267                                 $didYouMeanParams['fulltext'] = $this->fulltext;
00268                         }
00269 
00270                         $stParams = array_merge(
00271                                 $didYouMeanParams,
00272                                 $this->powerSearchOptions()
00273                         );
00274 
00275                         $suggestionSnippet = $textMatches->getSuggestionSnippet();
00276 
00277                         if( $suggestionSnippet == '' ) {
00278                                 $suggestionSnippet = null;
00279                         }
00280 
00281                         $suggestLink = Linker::linkKnown(
00282                                 $st,
00283                                 $suggestionSnippet,
00284                                 array(),
00285                                 $stParams
00286                         );
00287 
00288                         $this->didYouMeanHtml = '<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>';
00289                 }
00290                 // start rendering the page
00291                 $out->addHtml(
00292                         Xml::openElement(
00293                                 'form',
00294                                 array(
00295                                         'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ),
00296                                         'method' => 'get',
00297                                         'action' => $wgScript
00298                                 )
00299                         )
00300                 );
00301                 $out->addHtml(
00302                         Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
00303                         Xml::openElement( 'tr' ) .
00304                         Xml::openElement( 'td' ) . "\n" .
00305                         $this->shortDialog( $term ) .
00306                         Xml::closeElement('td') .
00307                         Xml::closeElement('tr') .
00308                         Xml::closeElement('table')
00309                 );
00310 
00311                 // Sometimes the search engine knows there are too many hits
00312                 if( $titleMatches instanceof SearchResultTooMany ) {
00313                         $out->wrapWikiMsg( "==$1==\n", 'toomanymatches' );
00314                         wfProfileOut( __METHOD__ );
00315                         return;
00316                 }
00317 
00318                 $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
00319                 if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
00320                         $out->addHTML( $this->formHeader( $term, 0, 0 ) );
00321                         $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
00322                         $out->addHTML( '</form>' );
00323                         // Empty query -- straight view of search form
00324                         wfProfileOut( __METHOD__ );
00325                         return;
00326                 }
00327 
00328                 // Get number of results
00329                 $titleMatchesNum = $titleMatches ? $titleMatches->numRows() : 0;
00330                 $textMatchesNum = $textMatches ? $textMatches->numRows() : 0;
00331                 // Total initial query matches (possible false positives)
00332                 $num = $titleMatchesNum + $textMatchesNum;
00333 
00334                 // Get total actual results (after second filtering, if any)
00335                 $numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ?
00336                         $titleMatches->getTotalHits() : $titleMatchesNum;
00337                 $numTextMatches = $textMatches && !is_null( $textMatches->getTotalHits() ) ?
00338                         $textMatches->getTotalHits() : $textMatchesNum;
00339 
00340                 // get total number of results if backend can calculate it
00341                 $totalRes = 0;
00342                 if($titleMatches && !is_null( $titleMatches->getTotalHits() ) )
00343                         $totalRes += $titleMatches->getTotalHits();
00344                 if($textMatches && !is_null( $textMatches->getTotalHits() ))
00345                         $totalRes += $textMatches->getTotalHits();
00346 
00347                 // show number of results and current offset
00348                 $out->addHTML( $this->formHeader( $term, $num, $totalRes ) );
00349                 $out->addHtml( $this->getProfileForm( $this->profile, $term ) );
00350 
00351 
00352                 $out->addHtml( Xml::closeElement( 'form' ) );
00353                 $out->addHtml( "<div class='searchresults'>" );
00354 
00355                 // prev/next links
00356                 if( $num || $this->offset ) {
00357                         // Show the create link ahead
00358                         $this->showCreateLink( $t );
00359                         $prevnext = $this->getLanguage()->viewPrevNext( $this->getTitle(), $this->offset, $this->limit,
00360                                 $this->powerSearchOptions() + array( 'search' => $term ),
00361                                 max( $titleMatchesNum, $textMatchesNum ) < $this->limit
00362                         );
00363                         //$out->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
00364                         wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
00365                 } else {
00366                         wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
00367                 }
00368 
00369                 $out->parserOptions()->setEditSection( false );
00370                 if( $titleMatches ) {
00371                         if( $numTitleMatches > 0 ) {
00372                                 $out->wrapWikiMsg( "==$1==\n", 'titlematches' );
00373                                 $out->addHTML( $this->showMatches( $titleMatches ) );
00374                         }
00375                         $titleMatches->free();
00376                 }
00377                 if( $textMatches ) {
00378                         // output appropriate heading
00379                         if( $numTextMatches > 0 && $numTitleMatches > 0 ) {
00380                                 // if no title matches the heading is redundant
00381                                 $out->wrapWikiMsg( "==$1==\n", 'textmatches' );
00382                         } elseif( $totalRes == 0 ) {
00383                                 # Don't show the 'no text matches' if we received title matches
00384                                 # $out->wrapWikiMsg( "==$1==\n", 'notextmatches' );
00385                         }
00386                         // show interwiki results if any
00387                         if( $textMatches->hasInterwikiResults() ) {
00388                                 $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) );
00389                         }
00390                         // show results
00391                         if( $numTextMatches > 0 ) {
00392                                 $out->addHTML( $this->showMatches( $textMatches ) );
00393                         }
00394 
00395                         $textMatches->free();
00396                 }
00397                 if( $num === 0 ) {
00398                         $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) );
00399                         $this->showCreateLink( $t );
00400                 }
00401                 $out->addHtml( "</div>" );
00402 
00403                 if( $num || $this->offset ) {
00404                         $out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
00405                 }
00406                 wfProfileOut( __METHOD__ );
00407         }
00408 
00412         protected function showCreateLink( $t ) {
00413                 // show direct page/create link if applicable
00414 
00415                 // Check DBkey !== '' in case of fragment link only.
00416                 if( is_null( $t ) || $t->getDBkey() === '' ) {
00417                         // invalid title
00418                         // preserve the paragraph for margins etc...
00419                         $this->getOutput()->addHtml( '<p></p>' );
00420                         return;
00421                 }
00422 
00423                 if( $t->isKnown() ) {
00424                         $messageName = 'searchmenu-exists';
00425                 } elseif( $t->userCan( 'create' ) ) {
00426                         $messageName = 'searchmenu-new';
00427                 } else {
00428                         $messageName = 'searchmenu-new-nocreate';
00429                 }
00430                 $params = array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) );
00431                 wfRunHooks( 'SpecialSearchCreateLink', array( $t, &$params ) );
00432 
00433                 // Extensions using the hook might still return an empty $messageName
00434                 if( $messageName ) {
00435                         $this->getOutput()->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", $params );
00436                 } else {
00437                         // preserve the paragraph for margins etc...
00438                         $this->getOutput()->addHtml( '<p></p>' );
00439                 }
00440         }
00441 
00445         protected function setupPage( $term ) {
00446                 # Should advanced UI be used?
00447                 $this->searchAdvanced = ($this->profile === 'advanced');
00448                 $out = $this->getOutput();
00449                 if( strval( $term ) !== ''  ) {
00450                         $out->setPageTitle( $this->msg( 'searchresults' ) );
00451                         $out->setHTMLTitle( $this->msg( 'pagetitle', $this->msg( 'searchresults-title', $term )->plain() ) );
00452                 }
00453                 // add javascript specific to special:search
00454                 $out->addModules( 'mediawiki.special.search' );
00455         }
00456 
00464         protected function powerSearch( &$request ) {
00465                 $arr = array();
00466                 foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
00467                         if( $request->getCheck( 'ns' . $ns ) ) {
00468                                 $arr[] = $ns;
00469                         }
00470                 }
00471 
00472                 return $arr;
00473         }
00474 
00480         protected function powerSearchOptions() {
00481                 $opt = array();
00482                 $opt['redirs'] = $this->searchRedirects ? 1 : 0;
00483                 if( $this->profile !== 'advanced' ) {
00484                         $opt['profile'] = $this->profile;
00485                 } else {
00486                         foreach( $this->namespaces as $n ) {
00487                                 $opt['ns' . $n] = 1;
00488                         }
00489                 }
00490                 return $opt + $this->extraParams;
00491         }
00492 
00500         protected function showMatches( &$matches ) {
00501                 global $wgContLang;
00502                 wfProfileIn( __METHOD__ );
00503 
00504                 $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
00505 
00506                 $out = "";
00507                 $infoLine = $matches->getInfo();
00508                 if( !is_null($infoLine) ) {
00509                         $out .= "\n<!-- {$infoLine} -->\n";
00510                 }
00511                 $out .= "<ul class='mw-search-results'>\n";
00512                 $result = $matches->next();
00513                 while( $result ) {
00514                         $out .= $this->showHit( $result, $terms );
00515                         $result = $matches->next();
00516                 }
00517                 $out .= "</ul>\n";
00518 
00519                 // convert the whole thing to desired language variant
00520                 $out = $wgContLang->convert( $out );
00521                 wfProfileOut( __METHOD__ );
00522                 return $out;
00523         }
00524 
00533         protected function showHit( $result, $terms ) {
00534                 wfProfileIn( __METHOD__ );
00535 
00536                 if( $result->isBrokenTitle() ) {
00537                         wfProfileOut( __METHOD__ );
00538                         return "<!-- Broken link in search result -->\n";
00539                 }
00540 
00541                 $t = $result->getTitle();
00542 
00543                 $titleSnippet = $result->getTitleSnippet($terms);
00544 
00545                 if( $titleSnippet == '' )
00546                         $titleSnippet = null;
00547 
00548                 $link_t = clone $t;
00549 
00550                 wfRunHooks( 'ShowSearchHitTitle',
00551                                         array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
00552 
00553                 $link = Linker::linkKnown(
00554                         $link_t,
00555                         $titleSnippet
00556                 );
00557 
00558                 //If page content is not readable, just return the title.
00559                 //This is not quite safe, but better than showing excerpts from non-readable pages
00560                 //Note that hiding the entry entirely would screw up paging.
00561                 if( !$t->userCan( 'read' ) ) {
00562                         wfProfileOut( __METHOD__ );
00563                         return "<li>{$link}</li>\n";
00564                 }
00565 
00566                 // If the page doesn't *exist*... our search index is out of date.
00567                 // The least confusing at this point is to drop the result.
00568                 // You may get less results, but... oh well. :P
00569                 if( $result->isMissingRevision() ) {
00570                         wfProfileOut( __METHOD__ );
00571                         return "<!-- missing page " . htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
00572                 }
00573 
00574                 // format redirects / relevant sections
00575                 $redirectTitle = $result->getRedirectTitle();
00576                 $redirectText = $result->getRedirectSnippet($terms);
00577                 $sectionTitle = $result->getSectionTitle();
00578                 $sectionText = $result->getSectionSnippet($terms);
00579                 $redirect = '';
00580 
00581                 if( !is_null($redirectTitle) ) {
00582                         if( $redirectText == '' )
00583                                 $redirectText = null;
00584 
00585                         $redirect = "<span class='searchalttitle'>" .
00586                                 wfMsg(
00587                                         'search-redirect',
00588                                         Linker::linkKnown(
00589                                                 $redirectTitle,
00590                                                 $redirectText
00591                                         )
00592                                 ) .
00593                                 "</span>";
00594                 }
00595 
00596                 $section = '';
00597 
00598                 if( !is_null($sectionTitle) ) {
00599                         if( $sectionText == '' )
00600                                 $sectionText = null;
00601 
00602                         $section = "<span class='searchalttitle'>" .
00603                                 wfMsg(
00604                                         'search-section', Linker::linkKnown(
00605                                                 $sectionTitle,
00606                                                 $sectionText
00607                                         )
00608                                 ) .
00609                                 "</span>";
00610                 }
00611 
00612                 // format text extract
00613                 $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
00614 
00615                 $lang = $this->getLanguage();
00616 
00617                 // format score
00618                 if( is_null( $result->getScore() ) ) {
00619                         // Search engine doesn't report scoring info
00620                         $score = '';
00621                 } else {
00622                         $percent = sprintf( '%2.1f', $result->getScore() * 100 );
00623                         $score = wfMsg( 'search-result-score', $lang->formatNum( $percent ) )
00624                                 . ' - ';
00625                 }
00626 
00627                 // format description
00628                 $byteSize = $result->getByteSize();
00629                 $wordCount = $result->getWordCount();
00630                 $timestamp = $result->getTimestamp();
00631                 $size = wfMsgExt(
00632                         'search-result-size',
00633                         array( 'parsemag', 'escape' ),
00634                         $lang->formatSize( $byteSize ),
00635                         $lang->formatNum( $wordCount )
00636                 );
00637 
00638                 if( $t->getNamespace() == NS_CATEGORY ) {
00639                         $cat = Category::newFromTitle( $t );
00640                         $size = wfMsgExt(
00641                                 'search-result-category-size',
00642                                 array( 'parsemag', 'escape' ),
00643                                 $lang->formatNum( $cat->getPageCount() ),
00644                                 $lang->formatNum( $cat->getSubcatCount() ),
00645                                 $lang->formatNum( $cat->getFileCount() )
00646                         );
00647                 }
00648 
00649                 $date = $lang->timeanddate( $timestamp );
00650 
00651                 // link to related articles if supported
00652                 $related = '';
00653                 if( $result->hasRelated() ) {
00654                         $st = SpecialPage::getTitleFor( 'Search' );
00655                         $stParams = array_merge(
00656                                 $this->powerSearchOptions(),
00657                                 array(
00658                                         'search' => wfMsgForContent( 'searchrelated' ) . ':' . $t->getPrefixedText(),
00659                                         'fulltext' => wfMsg( 'search' )
00660                                 )
00661                         );
00662 
00663                         $related = ' -- ' . Linker::linkKnown(
00664                                 $st,
00665                                 wfMsg('search-relatedarticle'),
00666                                 array(),
00667                                 $stParams
00668                         );
00669                 }
00670 
00671                 // Include a thumbnail for media files...
00672                 if( $t->getNamespace() == NS_FILE ) {
00673                         $img = wfFindFile( $t );
00674                         if( $img ) {
00675                                 $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
00676                                 if( $thumb ) {
00677                                         $desc = wfMsg( 'parentheses', $img->getShortDesc() );
00678                                         wfProfileOut( __METHOD__ );
00679                                         // Float doesn't seem to interact well with the bullets.
00680                                         // Table messes up vertical alignment of the bullets.
00681                                         // Bullets are therefore disabled (didn't look great anyway).
00682                                         return "<li>" .
00683                                                 '<table class="searchResultImage">' .
00684                                                 '<tr>' .
00685                                                 '<td width="120" align="center" valign="top">' .
00686                                                 $thumb->toHtml( array( 'desc-link' => true ) ) .
00687                                                 '</td>' .
00688                                                 '<td valign="top">' .
00689                                                 $link .
00690                                                 $extract .
00691                                                 "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" .
00692                                                 '</td>' .
00693                                                 '</tr>' .
00694                                                 '</table>' .
00695                                                 "</li>\n";
00696                                 }
00697                         }
00698                 }
00699 
00700                 wfProfileOut( __METHOD__ );
00701                 return "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" .
00702                         "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
00703                         "</li>\n";
00704 
00705         }
00706 
00715         protected function showInterwiki( &$matches, $query ) {
00716                 global $wgContLang;
00717                 wfProfileIn( __METHOD__ );
00718                 $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
00719 
00720                 $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
00721                         wfMsg('search-interwiki-caption')."</div>\n";
00722                 $out .= "<ul class='mw-search-iwresults'>\n";
00723 
00724                 // work out custom project captions
00725                 $customCaptions = array();
00726                 $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line <iwprefix>:<caption>
00727                 foreach($customLines as $line) {
00728                         $parts = explode(":",$line,2);
00729                         if(count($parts) == 2) // validate line
00730                                 $customCaptions[$parts[0]] = $parts[1];
00731                 }
00732 
00733                 $prev = null;
00734                 $result = $matches->next();
00735                 while( $result ) {
00736                         $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
00737                         $prev = $result->getInterwikiPrefix();
00738                         $result = $matches->next();
00739                 }
00740                 // TODO: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
00741                 $out .= "</ul></div>\n";
00742 
00743                 // convert the whole thing to desired language variant
00744                 $out = $wgContLang->convert( $out );
00745                 wfProfileOut( __METHOD__ );
00746                 return $out;
00747         }
00748 
00760         protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
00761                 wfProfileIn( __METHOD__ );
00762 
00763                 if( $result->isBrokenTitle() ) {
00764                         wfProfileOut( __METHOD__ );
00765                         return "<!-- Broken link in search result -->\n";
00766                 }
00767 
00768                 $t = $result->getTitle();
00769 
00770                 $titleSnippet = $result->getTitleSnippet($terms);
00771 
00772                 if( $titleSnippet == '' )
00773                         $titleSnippet = null;
00774 
00775                 $link = Linker::linkKnown(
00776                         $t,
00777                         $titleSnippet
00778                 );
00779 
00780                 // format redirect if any
00781                 $redirectTitle = $result->getRedirectTitle();
00782                 $redirectText = $result->getRedirectSnippet($terms);
00783                 $redirect = '';
00784                 if( !is_null($redirectTitle) ) {
00785                         if( $redirectText == '' )
00786                                 $redirectText = null;
00787 
00788                         $redirect = "<span class='searchalttitle'>" .
00789                                 wfMsg(
00790                                         'search-redirect',
00791                                         Linker::linkKnown(
00792                                                 $redirectTitle,
00793                                                 $redirectText
00794                                         )
00795                                 ) .
00796                                 "</span>";
00797                 }
00798 
00799                 $out = "";
00800                 // display project name
00801                 if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) {
00802                         if( array_key_exists($t->getInterwiki(),$customCaptions) ) {
00803                                 // captions from 'search-interwiki-custom'
00804                                 $caption = $customCaptions[$t->getInterwiki()];
00805                         } else {
00806                                 // default is to show the hostname of the other wiki which might suck
00807                                 // if there are many wikis on one hostname
00808                                 $parsed = wfParseUrl( $t->getFullURL() );
00809                                 $caption = wfMsg('search-interwiki-default', $parsed['host']);
00810                         }
00811                         // "more results" link (special page stuff could be localized, but we might not know target lang)
00812                         $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
00813                         $searchLink = Linker::linkKnown(
00814                                 $searchTitle,
00815                                 wfMsg('search-interwiki-more'),
00816                                 array(),
00817                                 array(
00818                                         'search' => $query,
00819                                         'fulltext' => 'Search'
00820                                 )
00821                         );
00822                         $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'>
00823                                 {$searchLink}</span>{$caption}</div>\n<ul>";
00824                 }
00825 
00826                 $out .= "<li>{$link} {$redirect}</li>\n";
00827                 wfProfileOut( __METHOD__ );
00828                 return $out;
00829         }
00830 
00836         protected function getProfileForm( $profile, $term ) {
00837                 // Hidden stuff
00838                 $opts = array();
00839                 $opts['redirs'] = $this->searchRedirects;
00840                 $opts['profile'] = $this->profile;
00841 
00842                 if ( $profile === 'advanced' ) {
00843                         return $this->powerSearchBox( $term, $opts );
00844                 } else {
00845                         $form = '';
00846                         wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $profile, $term, $opts ) );
00847                         return $form;
00848                 }
00849         }
00850 
00858         protected function powerSearchBox( $term, $opts ) {
00859                 // Groups namespaces into rows according to subject
00860                 $rows = array();
00861                 foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
00862                         $subject = MWNamespace::getSubject( $namespace );
00863                         if( !array_key_exists( $subject, $rows ) ) {
00864                                 $rows[$subject] = "";
00865                         }
00866                         $name = str_replace( '_', ' ', $name );
00867                         if( $name == '' ) {
00868                                 $name = wfMsg( 'blanknamespace' );
00869                         }
00870                         $rows[$subject] .=
00871                                 Xml::openElement(
00872                                         'td', array( 'style' => 'white-space: nowrap' )
00873                                 ) .
00874                                 Xml::checkLabel(
00875                                         $name,
00876                                         "ns{$namespace}",
00877                                         "mw-search-ns{$namespace}",
00878                                         in_array( $namespace, $this->namespaces )
00879                                 ) .
00880                                 Xml::closeElement( 'td' );
00881                 }
00882                 $rows = array_values( $rows );
00883                 $numRows = count( $rows );
00884 
00885                 // Lays out namespaces in multiple floating two-column tables so they'll
00886                 // be arranged nicely while still accommodating different screen widths
00887                 $namespaceTables = '';
00888                 for( $i = 0; $i < $numRows; $i += 4 ) {
00889                         $namespaceTables .= Xml::openElement(
00890                                 'table',
00891                                 array( 'cellpadding' => 0, 'cellspacing' => 0, 'border' => 0 )
00892                         );
00893                         for( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
00894                                 $namespaceTables .= Xml::tags( 'tr', null, $rows[$j] );
00895                         }
00896                         $namespaceTables .= Xml::closeElement( 'table' );
00897                 }
00898 
00899                 $showSections = array( 'namespaceTables' => $namespaceTables );
00900 
00901                 // Show redirects check only if backend supports it
00902                 if( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
00903                         $showSections['redirects'] =
00904                                 Xml::checkLabel( wfMsg( 'powersearch-redir' ), 'redirs', 'redirs', $this->searchRedirects );
00905                 }
00906 
00907                 wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) );
00908 
00909                 $hidden = '';
00910                 unset( $opts['redirs'] );
00911                 foreach( $opts as $key => $value ) {
00912                         $hidden .= Html::hidden( $key, $value );
00913                 }
00914                 // Return final output
00915                 return
00916                         Xml::openElement(
00917                                 'fieldset',
00918                                 array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
00919                         ) .
00920                         Xml::element( 'legend', null, wfMsg('powersearch-legend') ) .
00921                         Xml::tags( 'h4', null, wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) ) .
00922                         Xml::tags(
00923                                 'div',
00924                                 array( 'id' => 'mw-search-togglebox' ),
00925                                 Xml::label( wfMsg( 'powersearch-togglelabel' ), 'mw-search-togglelabel' ) .
00926                                         Xml::element(
00927                                                 'input',
00928                                                 array(
00929                                                         'type'=>'button',
00930                                                         'id' => 'mw-search-toggleall',
00931                                                         'value' => wfMsg( 'powersearch-toggleall' )
00932                                                 )
00933                                         ) .
00934                                         Xml::element(
00935                                                 'input',
00936                                                 array(
00937                                                         'type'=>'button',
00938                                                         'id' => 'mw-search-togglenone',
00939                                                         'value' => wfMsg( 'powersearch-togglenone' )
00940                                                 )
00941                                         )
00942                         ) .
00943                         Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
00944                         implode( Xml::element( 'div', array( 'class' => 'divider' ), '', false ), $showSections ) .
00945                         $hidden .
00946                         Xml::closeElement( 'fieldset' );
00947         }
00948 
00952         protected function getSearchProfiles() {
00953                 // Builds list of Search Types (profiles)
00954                 $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
00955 
00956                 $profiles = array(
00957                         'default' => array(
00958                                 'message' => 'searchprofile-articles',
00959                                 'tooltip' => 'searchprofile-articles-tooltip',
00960                                 'namespaces' => SearchEngine::defaultNamespaces(),
00961                                 'namespace-messages' => SearchEngine::namespacesAsText(
00962                                         SearchEngine::defaultNamespaces()
00963                                 ),
00964                         ),
00965                         'images' => array(
00966                                 'message' => 'searchprofile-images',
00967                                 'tooltip' => 'searchprofile-images-tooltip',
00968                                 'namespaces' => array( NS_FILE ),
00969                         ),
00970                         'help' => array(
00971                                 'message' => 'searchprofile-project',
00972                                 'tooltip' => 'searchprofile-project-tooltip',
00973                                 'namespaces' => SearchEngine::helpNamespaces(),
00974                                 'namespace-messages' => SearchEngine::namespacesAsText(
00975                                         SearchEngine::helpNamespaces()
00976                                 ),
00977                         ),
00978                         'all' => array(
00979                                 'message' => 'searchprofile-everything',
00980                                 'tooltip' => 'searchprofile-everything-tooltip',
00981                                 'namespaces' => $nsAllSet,
00982                         ),
00983                         'advanced' => array(
00984                                 'message' => 'searchprofile-advanced',
00985                                 'tooltip' => 'searchprofile-advanced-tooltip',
00986                                 'namespaces' => self::NAMESPACES_CURRENT,
00987                         )
00988                 );
00989 
00990                 wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
00991 
00992                 foreach( $profiles as &$data ) {
00993                         if ( !is_array( $data['namespaces'] ) ) continue;
00994                         sort( $data['namespaces'] );
00995                 }
00996 
00997                 return $profiles;
00998         }
00999 
01006         protected function formHeader( $term, $resultsShown, $totalNum ) {
01007                 $out = Xml::openElement('div', array( 'class' =>  'mw-search-formheader' ) );
01008 
01009                 $bareterm = $term;
01010                 if( $this->startsWithImage( $term ) ) {
01011                         // Deletes prefixes
01012                         $bareterm = substr( $term, strpos( $term, ':' ) + 1 );
01013                 }
01014 
01015                 $profiles = $this->getSearchProfiles();
01016                 $lang = $this->getLanguage();
01017 
01018                 // Outputs XML for Search Types
01019                 $out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) );
01020                 $out .= Xml::openElement( 'ul' );
01021                 foreach ( $profiles as $id => $profile ) {
01022                         if ( !isset( $profile['parameters'] ) ) {
01023                                 $profile['parameters'] = array();
01024                         }
01025                         $profile['parameters']['profile'] = $id;
01026 
01027                         $tooltipParam = isset( $profile['namespace-messages'] ) ?
01028                                 $lang->commaList( $profile['namespace-messages'] ) : null;
01029                         $out .= Xml::tags(
01030                                 'li',
01031                                 array(
01032                                         'class' => $this->profile === $id ? 'current' : 'normal'
01033                                 ),
01034                                 $this->makeSearchLink(
01035                                         $bareterm,
01036                                         array(),
01037                                         wfMsg( $profile['message'] ),
01038                                         wfMsg( $profile['tooltip'], $tooltipParam ),
01039                                         $profile['parameters']
01040                                 )
01041                         );
01042                 }
01043                 $out .= Xml::closeElement( 'ul' );
01044                 $out .= Xml::closeElement('div') ;
01045 
01046                 // Results-info
01047                 if ( $resultsShown > 0 ) {
01048                         if ( $totalNum > 0 ){
01049                                 $top = wfMsgExt( 'showingresultsheader', array( 'parseinline' ),
01050                                         $lang->formatNum( $this->offset + 1 ),
01051                                         $lang->formatNum( $this->offset + $resultsShown ),
01052                                         $lang->formatNum( $totalNum ),
01053                                         wfEscapeWikiText( $term ),
01054                                         $lang->formatNum( $resultsShown )
01055                                 );
01056                         } elseif ( $resultsShown >= $this->limit ) {
01057                                 $top = wfMsgExt( 'showingresults', array( 'parseinline' ),
01058                                         $lang->formatNum( $this->limit ),
01059                                         $lang->formatNum( $this->offset + 1 )
01060                                 );
01061                         } else {
01062                                 $top =  wfMsgExt( 'showingresultsnum', array( 'parseinline' ),
01063                                         $lang->formatNum( $this->limit ),
01064                                         $lang->formatNum( $this->offset + 1 ),
01065                                         $lang->formatNum( $resultsShown )
01066                                 );
01067                         }
01068                         $out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
01069                                 Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) )
01070                         );
01071                 }
01072 
01073                 $out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
01074                 $out .= Xml::closeElement('div');
01075 
01076                 return $out;
01077         }
01078 
01083         protected function shortDialog( $term ) {
01084                 $out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
01085                 $out .= Html::hidden( 'profile', $this->profile ) . "\n";
01086                 // Term box
01087                 $out .= Html::input( 'search', $term, 'search', array(
01088                         'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText',
01089                         'size' => '50',
01090                         'autofocus'
01091                 ) ) . "\n";
01092                 $out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
01093                 $out .= Xml::submitButton( wfMsg( 'searchbutton' ) ) . "\n";
01094                 return $out . $this->didYouMeanHtml;
01095         }
01096 
01107         protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
01108                 $opt = $params;
01109                 foreach( $namespaces as $n ) {
01110                         $opt['ns' . $n] = 1;
01111                 }
01112                 $opt['redirs'] = $this->searchRedirects;
01113 
01114                 $stParams = array_merge(
01115                         array(
01116                                 'search' => $term,
01117                                 'fulltext' => wfMsg( 'search' )
01118                         ),
01119                         $opt
01120                 );
01121 
01122                 return Xml::element(
01123                         'a',
01124                         array(
01125                                 'href' => $this->getTitle()->getLocalURL( $stParams ),
01126                                 'title' => $tooltip),
01127                         $label
01128                 );
01129         }
01130 
01137         protected function startsWithImage( $term ) {
01138                 global $wgContLang;
01139 
01140                 $p = explode( ':', $term );
01141                 if( count( $p ) > 1 ) {
01142                         return $wgContLang->getNsIndex( $p[0] ) == NS_FILE;
01143                 }
01144                 return false;
01145         }
01146 
01153         protected function startsWithAll( $term ) {
01154 
01155                 $allkeyword = wfMsgForContent('searchall');
01156 
01157                 $p = explode( ':', $term );
01158                 if( count( $p ) > 1 ) {
01159                         return $p[0]  == $allkeyword;
01160                 }
01161                 return false;
01162         }
01163 
01169         public function getSearchEngine() {
01170                 if ( $this->searchEngine === null ) {
01171                         $this->searchEngine = SearchEngine::create();
01172                 }
01173                 return $this->searchEngine;
01174         }
01175 
01185         public function setExtraParam( $key, $value ) {
01186                 $this->extraParams[$key] = $value;
01187         }
01188 
01189 }