• Main Page
  • Related Pages
  • Modules
  • Classes
  • Files

includes/specials/SpecialSearch.php (r43595)

Go to the documentation of this file.
00001 <?php
00002 # Copyright (C) 2004 Brion Vibber <brion@pobox.com>
00003 # http://www.mediawiki.org/
00004 #
00005 # This program is free software; you can redistribute it and/or modify
00006 # it under the terms of the GNU General Public License as published by
00007 # the Free Software Foundation; either version 2 of the License, or
00008 # (at your option) any later version.
00009 #
00010 # This program is distributed in the hope that it will be useful,
00011 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00012 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00013 # GNU General Public License for more details.
00014 #
00015 # You should have received a copy of the GNU General Public License along
00016 # with this program; if not, write to the Free Software Foundation, Inc.,
00017 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00018 # http://www.gnu.org/copyleft/gpl.html
00019 
00031 function wfSpecialSearch( $par = '' ) {
00032         global $wgRequest, $wgUser, $wgUseOldSearchUI;
00033 
00034         // Strip underscores from title parameter; most of the time we'll want
00035         // text form here. But don't strip underscores from actual text params!
00036         $titleParam = str_replace( '_', ' ', $par );
00037         
00038         $search = str_replace( "\n", " ", $wgRequest->getText( 'search', $titleParam ) );
00039         $class = $wgUseOldSearchUI ? 'SpecialSearchOld' : 'SpecialSearch';
00040         $searchPage = new $class( $wgRequest, $wgUser );
00041         if( $wgRequest->getVal( 'fulltext' ) 
00042                 || !is_null( $wgRequest->getVal( 'offset' )) 
00043                 || !is_null( $wgRequest->getVal( 'searchx' )) )
00044         {
00045                 $searchPage->showResults( $search, 'search' );
00046         } else {
00047                 $searchPage->goResult( $search );
00048         }
00049 }
00050 
00055 class SpecialSearch {
00056 
00065         function __construct( &$request, &$user ) {
00066                 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
00067 
00068                 $this->namespaces = $this->powerSearch( $request );
00069                 if( empty( $this->namespaces ) ) {
00070                         $this->namespaces = SearchEngine::userNamespaces( $user );
00071                 }
00072 
00073                 $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false;
00074                 $this->searchAdvanced = $request->getVal('advanced');
00075         }
00076 
00082         function goResult( $term ) {
00083                 global $wgOut, $wgGoToEdit;
00084 
00085                 $this->setupPage( $term );
00086 
00087                 # Try to go to page as entered.
00088                 $t = Title::newFromText( $term );
00089 
00090                 # If the string cannot be used to create a title
00091                 if( is_null( $t ) ) {
00092                         return $this->showResults( $term );
00093                 }
00094 
00095                 # If there's an exact or very near match, jump right there.
00096                 $t = SearchEngine::getNearMatch( $term );
00097                 if( !is_null( $t ) ) {
00098                         $wgOut->redirect( $t->getFullURL() );
00099                         return;
00100                 }
00101 
00102                 # No match, generate an edit URL
00103                 $t = Title::newFromText( $term );
00104                 if( ! is_null( $t ) ) {
00105                         wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
00106                         # If the feature is enabled, go straight to the edit page
00107                         if( $wgGoToEdit ) {
00108                                 $wgOut->redirect( $t->getFullURL( 'action=edit' ) );
00109                                 return;
00110                         }
00111                 }
00112 
00113                 return $this->showResults( $term );
00114         }
00115 
00120         function showResults( $term ) {
00121                 wfProfileIn( __METHOD__ );
00122                 global $wgOut, $wgUser;
00123                 $sk = $wgUser->getSkin();
00124 
00125                 $this->setupPage( $term );
00126                 $this->searchEngine = SearchEngine::create();
00127                 
00128                 $t = Title::newFromText( $term );
00129 
00130                 $wgOut->addHtml( 
00131                         Xml::openElement( 'table', array( 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
00132                         Xml::openElement( 'tr' ) .
00133                         Xml::openElement( 'td' ) . "\n" 
00134                 );
00135                 if( $this->searchAdvanced ) {
00136                         $wgOut->addHTML( $this->powerSearchBox( $term ) );
00137                         $showMenu = false;
00138                 } else {                        
00139                         $wgOut->addHTML( $this->shortDialog( $term ) );
00140                         $showMenu = true;
00141                 }
00142                 $wgOut->addHtml(
00143                         Xml::closeElement('td') .
00144                         Xml::closeElement('tr') .
00145                         Xml::closeElement('table')
00146                 );
00147 
00148                 if( '' === trim( $term ) ) {
00149                         // Empty query -- straight view of search form
00150                         wfProfileOut( __METHOD__ );
00151                         return;
00152                 }
00153 
00154                 global $wgDisableTextSearch;
00155                 if( $wgDisableTextSearch ) {
00156                         global $wgSearchForwardUrl;
00157                         if( $wgSearchForwardUrl ) {
00158                                 $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
00159                                 $wgOut->redirect( $url );
00160                                 return;
00161                         }
00162                         global $wgInputEncoding;
00163                         $wgOut->addHTML(
00164                                 Xml::openElement( 'fieldset' ) .
00165                                 Xml::element( 'legend', null, wfMsg( 'search-external' ) ) .
00166                                 Xml::element( 'p', array( 'class' => 'mw-searchdisabled' ), wfMsg( 'searchdisabled' ) ) .
00167                                 wfMsg( 'googlesearch',
00168                                         htmlspecialchars( $term ),
00169                                         htmlspecialchars( $wgInputEncoding ),
00170                                         htmlspecialchars( wfMsg( 'searchbutton' ) )
00171                                 ) .
00172                                 Xml::closeElement( 'fieldset' )
00173                         );
00174                         wfProfileOut( __METHOD__ );
00175                         return;
00176                 }
00177 
00178                 $search =& $this->searchEngine;
00179                 $search->setLimitOffset( $this->limit, $this->offset );
00180                 $search->setNamespaces( $this->namespaces );
00181                 $search->showRedirects = $this->searchRedirects;
00182                 $rewritten = $search->replacePrefixes($term);
00183 
00184                 $titleMatches = $search->searchTitle( $rewritten );
00185 
00186                 // Sometimes the search engine knows there are too many hits
00187                 if( $titleMatches instanceof SearchResultTooMany ) {
00188                         $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" );
00189                         wfProfileOut( __METHOD__ );
00190                         return;
00191                 }
00192                 
00193                 $textMatches = $search->searchText( $rewritten );
00194                 
00195                 // did you mean... suggestions
00196                 if( $textMatches && $textMatches->hasSuggestion() ) {
00197                         $st = SpecialPage::getTitleFor( 'Search' );                     
00198                         $stParams = wfArrayToCGI( 
00199                                 array( 'search' => $textMatches->getSuggestionQuery(), 'fulltext'       => wfMsg('search') ),
00200                                 $this->powerSearchOptions()
00201                         );
00202                         $suggestLink = '<a href="'.$st->escapeLocalURL($stParams).'">'.
00203                                 $textMatches->getSuggestionSnippet().'</a>';
00204 
00205                         $wgOut->addHTML('<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>');
00206                 }
00207                 
00208                 // show direct page/create link
00209                 if( !is_null($t) ) {
00210                         if( !$t->exists() ) {
00211                                 $wgOut->addWikiMsg( 'searchmenu-new', wfEscapeWikiText( $t->getPrefixedText() ) );
00212                         } else {
00213                                 $wgOut->addWikiMsg( 'searchmenu-exists', wfEscapeWikiText( $t->getPrefixedText() ) );
00214                         }
00215                 }
00216         
00217                 // show number of results
00218                 $numTitleMatches = $titleMatches ? $titleMatches->numRows() : 0;
00219                 $numTextMatches = $textMatches ? $textMatches->numRows() : 0;
00220                 $highestNum = max( $numTitleMatches, $numTextMatches );
00221                 // Total query matches (possible false positives)
00222                 $num = $numTitleMatches + $numTextMatches;
00223                 // Get total actual results
00224                 $totalNum = 0;
00225                 if( $titleMatches && !is_null($titleMatches->getTotalHits()) )
00226                         $totalNum += $titleMatches->getTotalHits();
00227                 if( $textMatches && !is_null($textMatches->getTotalHits()) )
00228                         $totalNum += $textMatches->getTotalHits();
00229                 if( $num > 0 ) {
00230                         if( $totalNum > 0 ) {
00231                                 $top = wfMsgExt('showingresultstotal', array( 'parseinline' ), 
00232                                         $this->offset+1, $this->offset+$num, $totalNum, $num );
00233                         } elseif( $num >= $this->limit ) {
00234                                 $top = wfShowingResults( $this->offset, $this->limit );
00235                         } else {
00236                                 $top = wfShowingResultsNum( $this->offset, $this->limit, $num );
00237                         }
00238                         $wgOut->addHTML( "<p class='mw-search-numberresults'>{$top}</p>\n" );
00239                 }
00240 
00241                 // prev/next links
00242                 if( $num || $this->offset ) {
00243                         $prevnext = wfViewPrevNext( $this->offset, $this->limit,
00244                                 SpecialPage::getTitleFor( 'Search' ),
00245                                 wfArrayToCGI( $this->powerSearchOptions(), array( 'search' => $term ) ),
00246                                 ($highestNum < $this->limit)
00247                         );
00248                         $wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
00249                         wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
00250                 } else {
00251                         wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
00252                 }
00253 
00254                 $wgOut->addHtml( "<div class='searchresults'>" );
00255 
00256                 if( $titleMatches ) {
00257                         if( $titleMatches->numRows() ) {
00258                                 $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' );
00259                                 $wgOut->addHTML( $this->showMatches( $titleMatches ) );
00260                         }
00261                         $titleMatches->free();
00262                 }
00263 
00264                 if( $textMatches ) {
00265                         // output appropriate heading
00266                         if( $textMatches->numRows() ) {
00267                                 if($titleMatches)
00268                                         $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' );
00269                                 else // if no title matches the heading is redundant
00270                                         $wgOut->addHTML("<hr/>");                                                               
00271                         } elseif( $num == 0 ) {
00272                                 # Don't show the 'no text matches' if we received title matches
00273                                 $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' );
00274                         }
00275                         // show interwiki results if any
00276                         if( $textMatches->hasInterwikiResults() )
00277                                 $wgOut->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ));
00278                         // show results
00279                         if( $textMatches->numRows() )
00280                                 $wgOut->addHTML( $this->showMatches( $textMatches ) );
00281 
00282                         $textMatches->free();
00283                 }
00284 
00285                 if( $num == 0 ) {
00286                         $wgOut->addWikiMsg( 'search-nonefound' );
00287                 }
00288 
00289                 $wgOut->addHtml( "</div>" );
00290 
00291                 if( $num || $this->offset ) {
00292                         $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
00293                 }
00294                 wfProfileOut( __METHOD__ );
00295         }
00296         
00300         protected function setupPage( $term ) {
00301                 global $wgOut;
00302                 if( !empty( $term ) ) {
00303                         $wgOut->setPageTitle( wfMsg( 'searchresults') );
00304                         $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term) ) );
00305                 }                       
00306                 $wgOut->setArticleRelated( false );
00307                 $wgOut->setRobotPolicy( 'noindex,nofollow' );
00308         }
00309 
00317         protected function powerSearch( &$request ) {
00318                 $arr = array();
00319                 foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
00320                         if( $request->getCheck( 'ns' . $ns ) ) {
00321                                 $arr[] = $ns;
00322                         }
00323                 }
00324                 return $arr;
00325         }
00326 
00331         protected function powerSearchOptions() {
00332                 $opt = array();
00333                 foreach( $this->namespaces as $n ) {
00334                         $opt['ns' . $n] = 1;
00335                 }
00336                 $opt['redirs'] = $this->searchRedirects ? 1 : 0;
00337                 if( $this->searchAdvanced )
00338                         $opt['advanced'] = $this->searchAdvanced;
00339                 return $opt;
00340         }
00341 
00347         protected function showMatches( &$matches ) {
00348                 global $wgContLang;
00349                 wfProfileIn( __METHOD__ );
00350 
00351                 $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
00352 
00353                 $out = "";
00354                 
00355                 $infoLine = $matches->getInfo();
00356                 if( !is_null($infoLine) )
00357                         $out .= "\n<!-- {$infoLine} -->\n";
00358                         
00359                 
00360                 $off = $this->offset + 1;
00361                 $out .= "<ul class='mw-search-results'>\n";
00362                 while( $result = $matches->next() ) {
00363                         $out .= $this->showHit( $result, $terms );
00364                 }
00365                 $out .= "</ul>\n";
00366 
00367                 // convert the whole thing to desired language variant
00368                 $out = $wgContLang->convert( $out );
00369                 wfProfileOut( __METHOD__ );
00370                 return $out;
00371         }
00372 
00378         protected function showHit( $result, $terms ) {
00379                 wfProfileIn( __METHOD__ );
00380                 global $wgUser, $wgContLang, $wgLang;
00381                 
00382                 if( $result->isBrokenTitle() ) {
00383                         wfProfileOut( __METHOD__ );
00384                         return "<!-- Broken link in search result -->\n";
00385                 }
00386                 
00387                 $t = $result->getTitle();
00388                 $sk = $wgUser->getSkin();
00389 
00390                 $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms));
00391 
00392                 //If page content is not readable, just return the title.
00393                 //This is not quite safe, but better than showing excerpts from non-readable pages
00394                 //Note that hiding the entry entirely would screw up paging.
00395                 if(!$t->userCanRead()) {
00396                         wfProfileOut( __METHOD__ );
00397                         return "<li>{$link}</li>\n";
00398                 }
00399 
00400                 // If the page doesn't *exist*... our search index is out of date.
00401                 // The least confusing at this point is to drop the result.
00402                 // You may get less results, but... oh well. :P
00403                 if( $result->isMissingRevision() ) {
00404                         wfProfileOut( __METHOD__ );
00405                         return "<!-- missing page " .
00406                                 htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
00407                 }
00408 
00409                 // format redirects / relevant sections
00410                 $redirectTitle = $result->getRedirectTitle();
00411                 $redirectText = $result->getRedirectSnippet($terms);
00412                 $sectionTitle = $result->getSectionTitle();
00413                 $sectionText = $result->getSectionSnippet($terms);
00414                 $redirect = '';
00415                 if( !is_null($redirectTitle) )
00416                         $redirect = "<span class='searchalttitle'>"
00417                                 .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText))
00418                                 ."</span>";
00419                 $section = '';
00420                 if( !is_null($sectionTitle) )
00421                         $section = "<span class='searchalttitle'>" 
00422                                 .wfMsg('search-section', $sk->makeKnownLinkObj( $sectionTitle, $sectionText))
00423                                 ."</span>";
00424 
00425                 // format text extract
00426                 $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
00427                 
00428                 // format score
00429                 if( is_null( $result->getScore() ) ) {
00430                         // Search engine doesn't report scoring info
00431                         $score = '';
00432                 } else {
00433                         $percent = sprintf( '%2.1f', $result->getScore() * 100 );
00434                         $score = wfMsg( 'search-result-score', $wgLang->formatNum( $percent ) )
00435                                 . ' - ';
00436                 }
00437 
00438                 // format description
00439                 $byteSize = $result->getByteSize();
00440                 $wordCount = $result->getWordCount();
00441                 $timestamp = $result->getTimestamp();
00442                 $size = wfMsgExt( 'search-result-size', array( 'parsemag', 'escape' ),
00443                         $sk->formatSize( $byteSize ),
00444                         $wordCount );
00445                 $date = $wgLang->timeanddate( $timestamp );
00446 
00447                 // link to related articles if supported
00448                 $related = '';
00449                 if( $result->hasRelated() ) {
00450                         $st = SpecialPage::getTitleFor( 'Search' );
00451                         $stParams = wfArrayToCGI( $this->powerSearchOptions(),
00452                                 array('search'    => wfMsgForContent('searchrelated').':'.$t->getPrefixedText(),
00453                                       'fulltext'  => wfMsg('search') ));
00454                         
00455                         $related = ' -- <a href="'.$st->escapeLocalURL($stParams).'">'. 
00456                                 wfMsg('search-relatedarticle').'</a>';
00457                 }
00458                                 
00459                 // Include a thumbnail for media files...
00460                 if( $t->getNamespace() == NS_IMAGE ) {
00461                         $img = wfFindFile( $t );
00462                         if( $img ) {
00463                                 $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
00464                                 if( $thumb ) {
00465                                         $desc = $img->getShortDesc();
00466                                         wfProfileOut( __METHOD__ );
00467                                         // Float doesn't seem to interact well with the bullets.
00468                                         // Table messes up vertical alignment of the bullets.
00469                                         // Bullets are therefore disabled (didn't look great anyway).
00470                                         return "<li>" .
00471                                                 '<table class="searchResultImage">' .
00472                                                 '<tr>' .
00473                                                 '<td width="120" align="center" valign="top">' .
00474                                                 $thumb->toHtml( array( 'desc-link' => true ) ) .
00475                                                 '</td>' .
00476                                                 '<td valign="top">' .
00477                                                 $link .
00478                                                 $extract .
00479                                                 "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" .
00480                                                 '</td>' .
00481                                                 '</tr>' .
00482                                                 '</table>' .
00483                                                 "</li>\n";
00484                                 }
00485                         }
00486                 }
00487 
00488                 wfProfileOut( __METHOD__ );
00489                 return "<li>{$link} {$redirect} {$section} {$extract}\n" .
00490                         "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
00491                         "</li>\n";
00492 
00493         }
00494 
00500         protected function showInterwiki( &$matches, $query ) {
00501                 wfProfileIn( __METHOD__ );
00502 
00503                 global $wgContLang;
00504                 $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
00505 
00506                 $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
00507                         wfMsg('search-interwiki-caption')."</div>\n";           
00508                 $off = $this->offset + 1;
00509                 $out .= "<ul start='{$off}' class='mw-search-iwresults'>\n";
00510 
00511                 // work out custom project captions
00512                 $customCaptions = array();
00513                 $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line <iwprefix>:<caption>
00514                 foreach($customLines as $line) {
00515                         $parts = explode(":",$line,2);
00516                         if(count($parts) == 2) // validate line
00517                                 $customCaptions[$parts[0]] = $parts[1]; 
00518                 }
00519                 
00520                 
00521                 $prev = null;
00522                 while( $result = $matches->next() ) {
00523                         $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
00524                         $prev = $result->getInterwikiPrefix();
00525                 }
00526                 // FIXME: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
00527                 $out .= "</ul></div>\n";
00528 
00529                 // convert the whole thing to desired language variant
00530                 global $wgContLang;
00531                 $out = $wgContLang->convert( $out );
00532                 wfProfileOut( __METHOD__ );
00533                 return $out;
00534         }
00535         
00545         protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
00546                 wfProfileIn( __METHOD__ );
00547                 global $wgUser, $wgContLang, $wgLang;
00548                 
00549                 if( $result->isBrokenTitle() ) {
00550                         wfProfileOut( __METHOD__ );
00551                         return "<!-- Broken link in search result -->\n";
00552                 }
00553                 
00554                 $t = $result->getTitle();
00555                 $sk = $wgUser->getSkin();
00556                 
00557                 $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms));
00558                                 
00559                 // format redirect if any
00560                 $redirectTitle = $result->getRedirectTitle();
00561                 $redirectText = $result->getRedirectSnippet($terms);
00562                 $redirect = '';
00563                 if( !is_null($redirectTitle) )
00564                         $redirect = "<span class='searchalttitle'>"
00565                                 .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText))
00566                                 ."</span>";
00567 
00568                 $out = "";
00569                 // display project name 
00570                 if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) {
00571                         if( key_exists($t->getInterwiki(),$customCaptions) )
00572                                 // captions from 'search-interwiki-custom'
00573                                 $caption = $customCaptions[$t->getInterwiki()];
00574                         else{
00575                                 // default is to show the hostname of the other wiki which might suck 
00576                                 // if there are many wikis on one hostname
00577                                 $parsed = parse_url($t->getFullURL());
00578                                 $caption = wfMsg('search-interwiki-default', $parsed['host']); 
00579                         }               
00580                         // "more results" link (special page stuff could be localized, but we might not know target lang)
00581                         $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");                        
00582                         $searchLink = $sk->makeKnownLinkObj( $searchTitle, wfMsg('search-interwiki-more'),
00583                                 wfArrayToCGI(array('search' => $query, 'fulltext' => 'Search'))); 
00584                         $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'>
00585                                 {$searchLink}</span>{$caption}</div>\n<ul>";
00586                 }
00587 
00588                 $out .= "<li>{$link} {$redirect}</li>\n"; 
00589                 wfProfileOut( __METHOD__ );
00590                 return $out;
00591         }
00592         
00593 
00599         protected function powerSearchBox( $term ) {
00600                 global $wgScript, $wgContLang;
00601 
00602                 $namespaces = SearchEngine::searchableNamespaces();
00603 
00604                 // group namespaces into rows according to subject; try not to make too
00605                 // many assumptions about namespace numbering
00606                 $rows = array();
00607                 foreach( $namespaces as $ns => $name ) {
00608                         $subj = Namespace::getSubject( $ns );
00609                         if( !array_key_exists( $subj, $rows ) ) {
00610                                 $rows[$subj] = "";
00611                         }
00612                         $name = str_replace( '_', ' ', $name );
00613                         if( '' == $name ) {
00614                                 $name = wfMsg( 'blanknamespace' );
00615                         }
00616                         $rows[$subj] .= Xml::openElement( 'td', array( 'style' => 'white-space: nowrap' ) ) .
00617                                         Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) .
00618                                         Xml::closeElement( 'td' ) . "\n";
00619                 }
00620                 $rows = array_values( $rows );
00621                 $numRows = count( $rows );
00622 
00623                 // lay out namespaces in multiple floating two-column tables so they'll
00624                 // be arranged nicely while still accommodating different screen widths
00625                 $rowsPerTable = 3;  // seems to look nice
00626 
00627                 // float to the right on RTL wikis
00628                 $tableStyle = ( $wgContLang->isRTL() ?
00629                                 'float: right; margin: 0 0 1em 1em' :
00630                                 'float: left; margin: 0 1em 1em 0' );
00631 
00632                 $tables = "";
00633                 for( $i = 0; $i < $numRows; $i += $rowsPerTable ) {
00634                         $tables .= Xml::openElement( 'table', array( 'style' => $tableStyle ) );
00635                         for( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) {
00636                                 $tables .= "<tr>\n" . $rows[$j] . "</tr>";
00637                         }
00638                         $tables .= Xml::closeElement( 'table' ) . "\n";
00639                 }
00640 
00641                 $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) );
00642                 $redirectLabel = Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' );
00643                 $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) );
00644                 $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' ) ) . "\n";
00645                 $searchTitle = SpecialPage::getTitleFor( 'Search' );
00646                 
00647                 $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) .
00648                         Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n" .
00649                         "<p>" .
00650                         wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) .
00651                         "</p>\n" .
00652                         $tables .
00653                         "<hr style=\"clear: both\" />\n" .
00654                         "<p>" .
00655                         $redirect . " " . $redirectLabel .
00656                         "</p>\n" .
00657                         wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) .
00658                         "&nbsp;" .
00659                         $searchField .
00660                         "&nbsp;" .
00661                         $searchButton .
00662                         "</form>";
00663                 $t = Title::newFromText( $term );
00664                 if( $t != null )
00665                         $out .= wfMsgExt( 'searchmenu-prefix', array('parseinline'), $term );
00666 
00667                 return Xml::openElement( 'fieldset', array('id' => 'mw-searchoptions','style' => 'margin:0em;') ) .
00668                         Xml::element( 'legend', null, wfMsg('powersearch-legend') ) .
00669                         $this->formHeader($term) . $out . 
00670                         Xml::closeElement( 'fieldset' );
00671         }
00672 
00673         protected function powerSearchFocus() {
00674                 global $wgJsMimeType;
00675                 return "<script type=\"$wgJsMimeType\">" .
00676                         "hookEvent(\"load\", function() {" .
00677                                 "document.getElementById('powerSearchText').focus();" .
00678                         "});" .
00679                         "</script>";
00680         }
00681         
00683         protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) {
00684                 $opt = $params;
00685                 foreach( $namespaces as $n ) {
00686                         $opt['ns' . $n] = 1;
00687                 }
00688                 $opt['redirs'] = $this->searchRedirects ? 1 : 0;
00689 
00690                 $st = SpecialPage::getTitleFor( 'Search' );             
00691                 $stParams = wfArrayToCGI( array( 'search' => $term, 'fulltext' => wfMsg( 'search' ) ), $opt );
00692 
00693                 return Xml::element( 'a', 
00694                         array( 'href'=> $st->getLocalURL( $stParams ), 'title' => $tooltip ), 
00695                         $label );       
00696         }
00697         
00699         protected function startsWithImage( $term ) {
00700                 global $wgContLang;
00701                 
00702                 $p = explode( ':', $term );
00703                 if( count( $p ) > 1 ) {
00704                         return $wgContLang->getNsIndex( $p[0] ) == NS_IMAGE;
00705                 }
00706                 return false;
00707         }
00708 
00709         protected function formHeader( $term ) {
00710                 global $wgContLang, $wgCanonicalNamespaceNames;
00711                 
00712                 $sep = '&nbsp;&nbsp;&nbsp;';
00713                 $out = Xml::openElement('div', array( 'style' => 'padding-bottom:0.5em;' ) );
00714                 
00715                 $bareterm = $term;
00716                 if( $this->startsWithImage( $term ) )
00717                         $bareterm = substr( $term, strpos( $term, ':' ) + 1 ); // delete all/image prefix
00718                         
00719                 $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
00720                 // figure out the active search profile header
00721                 if( $this->searchAdvanced )
00722                         $active = 'advanced';
00723                 else if( $this->namespaces === NS_IMAGE || $this->startsWithImage( $term ) )
00724                         $active = 'images';
00725                 elseif( $this->namespaces === $nsAllSet )
00726                         $active = 'all';
00727                 elseif( $this->namespaces === SearchEngine::defaultNamespaces() )
00728                         $active = 'default';
00729                 elseif( $this->namespaces === SearchEngine::defaultAndProjectNamespaces() )
00730                         $active = 'withproject';
00731                 elseif( $this->namespaces === SearchEngine::projectNamespaces() )
00732                         $active = 'project';
00733                 else
00734                         $active = 'advanced';
00735                 
00736                 
00737                 // search profiles headers
00738                 $m = wfMsg( 'searchprofile-articles' );
00739                 $tt = wfMsg( 'searchprofile-articles-tooltip', 
00740                         implode( ', ', SearchEngine::namespacesAsText( SearchEngine::defaultNamespaces() ) ) );
00741                 if( $active == 'default' ) {
00742                         $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );    
00743                 } else {
00744                         $out .= $this->makeSearchLink( $bareterm, SearchEngine::defaultNamespaces(), $m, $tt );
00745                 }
00746                 $out .= $sep;
00747                 
00748                 $m = wfMsg( 'searchprofile-images' );
00749                 $tt = wfMsg( 'searchprofile-images-tooltip' );
00750                 if( $active == 'images' ) {
00751                         $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );    
00752                 } else {
00753                         $imageTextForm = $wgContLang->getFormattedNsText(NS_IMAGE).':'.$bareterm;
00754                         $out .= $this->makeSearchLink( $imageTextForm, array( NS_IMAGE ) , $m, $tt );
00755                 }
00756                 $out .= $sep;
00757                         
00758                 $m = wfMsg( 'searchprofile-articles-and-proj' );
00759                 $tt = wfMsg( 'searchprofile-project-tooltip', 
00760                         implode( ', ', SearchEngine::namespacesAsText( SearchEngine::defaultAndProjectNamespaces() ) ) );
00761                 if( $active == 'withproject' ) {
00762                         $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );    
00763                 } else {
00764                         $out .= $this->makeSearchLink( $bareterm, SearchEngine::defaultAndProjectNamespaces(), $m, $tt );
00765                 }
00766                 $out .= $sep;
00767                 
00768                 $m = wfMsg( 'searchprofile-project' );
00769                 $tt = wfMsg( 'searchprofile-project-tooltip', 
00770                         implode( ', ', SearchEngine::namespacesAsText( SearchEngine::projectNamespaces() ) ) );
00771                 if( $active == 'project' ) {
00772                         $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );    
00773                 } else {
00774                         $out .= $this->makeSearchLink( $bareterm, SearchEngine::projectNamespaces(), $m, $tt );
00775                 }
00776                 $out .= $sep;
00777                         
00778                 $m = wfMsg( 'searchprofile-everything' );
00779                 $tt = wfMsg( 'searchprofile-everything-tooltip' );
00780                 if( $active == 'all' ) {
00781                         $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );    
00782                 } else {
00783                         $out .= $this->makeSearchLink( $bareterm, $nsAllSet, $m, $tt );
00784                 }
00785                 $out .= $sep;
00786                 
00787                 $m = wfMsg( 'searchprofile-advanced' );
00788                 $tt = wfMsg( 'searchprofile-advanced-tooltip' );
00789                 if( $active == 'advanced' ) {
00790                         $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );    
00791                 } else {
00792                         $out .= $this->makeSearchLink( $bareterm, $this->namespaces, $m, $tt, array( 'advanced' => '1' ) );
00793                 }
00794                 $out .= Xml::closeElement('div') ;
00795                 
00796                 return $out;
00797         }
00798         
00799         protected function shortDialog( $term ) {
00800                 global $wgScript;
00801                 $out = Xml::openElement( 'form', array( 'id' => 'search', 'method' => 'get', 'action' => $wgScript ) );
00802                 $searchTitle = SpecialPage::getTitleFor( 'Search' );
00803                 $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
00804                 $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . "\n";
00805                 foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
00806                         if( in_array( $ns, $this->namespaces ) ) {
00807                                 $out .= Xml::hidden( "ns{$ns}", '1' );
00808                         }
00809                 }
00810                 $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) );
00811                 $out .= ' (' . wfMsgExt('searchmenu-help',array('parseinline') ) . ')';
00812                 $out .= Xml::closeElement( 'form' );
00813                 $t = Title::newFromText( $term );
00814                 if( $t != null )
00815                         $out .= wfMsgExt( 'searchmenu-prefix', array('parseinline'), $term );
00816                 return Xml::openElement( 'fieldset', array('id' => 'mw-searchoptions','style' => 'margin:0em;') ) .
00817                         Xml::element( 'legend', null, wfMsg('searchmenu-legend') ) .
00818                         $this->formHeader($term) . $out .
00819                         Xml::closeElement( 'fieldset' );
00820         }
00821 }
00822 
00827 class SpecialSearchOld {
00828 
00837         function __construct( &$request, &$user ) {
00838                 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
00839 
00840                 $this->namespaces = $this->powerSearch( $request );
00841                 if( empty( $this->namespaces ) ) {
00842                         $this->namespaces = SearchEngine::userNamespaces( $user );
00843                 }
00844 
00845                 $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false;
00846         }
00847 
00853         function goResult( $term ) {
00854                 global $wgOut;
00855                 global $wgGoToEdit;
00856 
00857                 $this->setupPage( $term );
00858 
00859                 # Try to go to page as entered.
00860                 $t = Title::newFromText( $term );
00861 
00862                 # If the string cannot be used to create a title
00863                 if( is_null( $t ) ){
00864                         return $this->showResults( $term );
00865                 }
00866 
00867                 # If there's an exact or very near match, jump right there.
00868                 $t = SearchEngine::getNearMatch( $term );
00869                 if( !is_null( $t ) ) {
00870                         $wgOut->redirect( $t->getFullURL() );
00871                         return;
00872                 }
00873 
00874                 # No match, generate an edit URL
00875                 $t =