MediaWiki
REL1_24
|
00001 <?php 00030 class SpecialSearch extends SpecialPage { 00039 protected $profile; 00040 00042 protected $searchEngine; 00043 00045 protected $searchEngineType; 00046 00048 protected $extraParams = array(); 00049 00051 protected $mPrefix; 00052 00056 protected $limit, $offset; 00057 00061 protected $namespaces; 00062 00066 protected $didYouMeanHtml, $fulltext; 00067 00068 const NAMESPACES_CURRENT = 'sense'; 00069 00070 public function __construct() { 00071 parent::__construct( 'Search' ); 00072 } 00073 00079 public function execute( $par ) { 00080 $this->setHeaders(); 00081 $this->outputHeader(); 00082 $out = $this->getOutput(); 00083 $out->allowClickjacking(); 00084 $out->addModuleStyles( array( 00085 'mediawiki.special', 'mediawiki.special.search', 'mediawiki.ui', 'mediawiki.ui.button', 00086 'mediawiki.ui.input', 00087 ) ); 00088 00089 // Strip underscores from title parameter; most of the time we'll want 00090 // text form here. But don't strip underscores from actual text params! 00091 $titleParam = str_replace( '_', ' ', $par ); 00092 00093 $request = $this->getRequest(); 00094 00095 // Fetch the search term 00096 $search = str_replace( "\n", " ", $request->getText( 'search', $titleParam ) ); 00097 00098 $this->load(); 00099 if ( !is_null( $request->getVal( 'nsRemember' ) ) ) { 00100 $this->saveNamespaces(); 00101 // Remove the token from the URL to prevent the user from inadvertently 00102 // exposing it (e.g. by pasting it into a public wiki page) or undoing 00103 // later settings changes (e.g. by reloading the page). 00104 $query = $request->getValues(); 00105 unset( $query['title'], $query['nsRemember'] ); 00106 $out->redirect( $this->getPageTitle()->getFullURL( $query ) ); 00107 return; 00108 } 00109 00110 $this->searchEngineType = $request->getVal( 'srbackend' ); 00111 00112 if ( $request->getVal( 'fulltext' ) 00113 || !is_null( $request->getVal( 'offset' ) ) 00114 ) { 00115 $this->showResults( $search ); 00116 } else { 00117 $this->goResult( $search ); 00118 } 00119 } 00120 00126 public function load() { 00127 $request = $this->getRequest(); 00128 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, '' ); 00129 $this->mPrefix = $request->getVal( 'prefix', '' ); 00130 00131 $user = $this->getUser(); 00132 00133 # Extract manually requested namespaces 00134 $nslist = $this->powerSearch( $request ); 00135 if ( !count( $nslist ) ) { 00136 # Fallback to user preference 00137 $nslist = SearchEngine::userNamespaces( $user ); 00138 } 00139 00140 $profile = null; 00141 if ( !count( $nslist ) ) { 00142 $profile = 'default'; 00143 } 00144 00145 $profile = $request->getVal( 'profile', $profile ); 00146 $profiles = $this->getSearchProfiles(); 00147 if ( $profile === null ) { 00148 // BC with old request format 00149 $profile = 'advanced'; 00150 foreach ( $profiles as $key => $data ) { 00151 if ( $nslist === $data['namespaces'] && $key !== 'advanced' ) { 00152 $profile = $key; 00153 } 00154 } 00155 $this->namespaces = $nslist; 00156 } elseif ( $profile === 'advanced' ) { 00157 $this->namespaces = $nslist; 00158 } else { 00159 if ( isset( $profiles[$profile]['namespaces'] ) ) { 00160 $this->namespaces = $profiles[$profile]['namespaces']; 00161 } else { 00162 // Unknown profile requested 00163 $profile = 'default'; 00164 $this->namespaces = $profiles['default']['namespaces']; 00165 } 00166 } 00167 00168 $this->didYouMeanHtml = ''; # html of did you mean... link 00169 $this->fulltext = $request->getVal( 'fulltext' ); 00170 $this->profile = $profile; 00171 } 00172 00178 public function goResult( $term ) { 00179 $this->setupPage( $term ); 00180 # Try to go to page as entered. 00181 $title = Title::newFromText( $term ); 00182 # If the string cannot be used to create a title 00183 if ( is_null( $title ) ) { 00184 $this->showResults( $term ); 00185 00186 return; 00187 } 00188 # If there's an exact or very near match, jump right there. 00189 $title = SearchEngine::getNearMatch( $term ); 00190 00191 if ( !is_null( $title ) ) { 00192 $this->getOutput()->redirect( $title->getFullURL() ); 00193 00194 return; 00195 } 00196 # No match, generate an edit URL 00197 $title = Title::newFromText( $term ); 00198 if ( !is_null( $title ) ) { 00199 wfRunHooks( 'SpecialSearchNogomatch', array( &$title ) ); 00200 wfDebugLog( 'nogomatch', $title->getFullText(), 'private' ); 00201 00202 # If the feature is enabled, go straight to the edit page 00203 if ( $this->getConfig()->get( 'GoToEdit' ) ) { 00204 $this->getOutput()->redirect( $title->getFullURL( array( 'action' => 'edit' ) ) ); 00205 00206 return; 00207 } 00208 } 00209 $this->showResults( $term ); 00210 } 00211 00215 public function showResults( $term ) { 00216 global $wgContLang; 00217 00218 $profile = new ProfileSection( __METHOD__ ); 00219 $search = $this->getSearchEngine(); 00220 $search->setLimitOffset( $this->limit, $this->offset ); 00221 $search->setNamespaces( $this->namespaces ); 00222 $search->prefix = $this->mPrefix; 00223 $term = $search->transformSearchTerm( $term ); 00224 00225 wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) ); 00226 00227 $this->setupPage( $term ); 00228 00229 $out = $this->getOutput(); 00230 00231 if ( $this->getConfig()->get( 'DisableTextSearch' ) ) { 00232 $searchFowardUrl = $this->getConfig()->get( 'SearchForwardUrl' ); 00233 if ( $searchFowardUrl ) { 00234 $url = str_replace( '$1', urlencode( $term ), $searchFowardUrl ); 00235 $out->redirect( $url ); 00236 } else { 00237 $out->addHTML( 00238 Xml::openElement( 'fieldset' ) . 00239 Xml::element( 'legend', null, $this->msg( 'search-external' )->text() ) . 00240 Xml::element( 00241 'p', 00242 array( 'class' => 'mw-searchdisabled' ), 00243 $this->msg( 'searchdisabled' )->text() 00244 ) . 00245 $this->msg( 'googlesearch' )->rawParams( 00246 htmlspecialchars( $term ), 00247 'UTF-8', 00248 $this->msg( 'searchbutton' )->escaped() 00249 )->text() . 00250 Xml::closeElement( 'fieldset' ) 00251 ); 00252 } 00253 00254 return; 00255 } 00256 00257 $title = Title::newFromText( $term ); 00258 $showSuggestion = $title === null || !$title->isKnown(); 00259 $search->setShowSuggestion( $showSuggestion ); 00260 00261 // fetch search results 00262 $rewritten = $search->replacePrefixes( $term ); 00263 00264 $titleMatches = $search->searchTitle( $rewritten ); 00265 $textMatches = $search->searchText( $rewritten ); 00266 00267 $textStatus = null; 00268 if ( $textMatches instanceof Status ) { 00269 $textStatus = $textMatches; 00270 $textMatches = null; 00271 } 00272 00273 // did you mean... suggestions 00274 if ( $showSuggestion && $textMatches && !$textStatus && $textMatches->hasSuggestion() ) { 00275 # mirror Go/Search behavior of original request .. 00276 $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() ); 00277 00278 if ( $this->fulltext != null ) { 00279 $didYouMeanParams['fulltext'] = $this->fulltext; 00280 } 00281 00282 $stParams = array_merge( 00283 $didYouMeanParams, 00284 $this->powerSearchOptions() 00285 ); 00286 00287 $suggestionSnippet = $textMatches->getSuggestionSnippet(); 00288 00289 if ( $suggestionSnippet == '' ) { 00290 $suggestionSnippet = null; 00291 } 00292 00293 $suggestLink = Linker::linkKnown( 00294 $this->getPageTitle(), 00295 $suggestionSnippet, 00296 array(), 00297 $stParams 00298 ); 00299 00300 $this->didYouMeanHtml = '<div class="searchdidyoumean">' 00301 . $this->msg( 'search-suggest' )->rawParams( $suggestLink )->text() . '</div>'; 00302 } 00303 00304 if ( !wfRunHooks( 'SpecialSearchResultsPrepend', array( $this, $out, $term ) ) ) { 00305 # Hook requested termination 00306 return; 00307 } 00308 00309 // start rendering the page 00310 $out->addHtml( 00311 Xml::openElement( 00312 'form', 00313 array( 00314 'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ), 00315 'method' => 'get', 00316 'action' => wfScript(), 00317 ) 00318 ) 00319 ); 00320 00321 // Get number of results 00322 $titleMatchesNum = $textMatchesNum = $numTitleMatches = $numTextMatches = 0; 00323 if ( $titleMatches ) { 00324 $titleMatchesNum = $titleMatches->numRows(); 00325 $numTitleMatches = $titleMatches->getTotalHits(); 00326 } 00327 if ( $textMatches ) { 00328 $textMatchesNum = $textMatches->numRows(); 00329 $numTextMatches = $textMatches->getTotalHits(); 00330 } 00331 $num = $titleMatchesNum + $textMatchesNum; 00332 $totalRes = $numTitleMatches + $numTextMatches; 00333 00334 $out->addHtml( 00335 # This is an awful awful ID name. It's not a table, but we 00336 # named it poorly from when this was a table so now we're 00337 # stuck with it 00338 Xml::openElement( 'div', array( 'id' => 'mw-search-top-table' ) ) . 00339 $this->shortDialog( $term, $num, $totalRes ) . 00340 Xml::closeElement( 'div' ) . 00341 $this->formHeader( $term ) . 00342 Xml::closeElement( 'form' ) 00343 ); 00344 00345 $filePrefix = $wgContLang->getFormattedNsText( NS_FILE ) . ':'; 00346 if ( trim( $term ) === '' || $filePrefix === trim( $term ) ) { 00347 // Empty query -- straight view of search form 00348 return; 00349 } 00350 00351 $out->addHtml( "<div class='searchresults'>" ); 00352 00353 // prev/next links 00354 $prevnext = null; 00355 if ( $num || $this->offset ) { 00356 // Show the create link ahead 00357 $this->showCreateLink( $title, $num, $titleMatches, $textMatches ); 00358 if ( $totalRes > $this->limit || $this->offset ) { 00359 if ( $this->searchEngineType !== null ) { 00360 $this->setExtraParam( 'srbackend', $this->searchEngineType ); 00361 } 00362 $prevnext = $this->getLanguage()->viewPrevNext( 00363 $this->getPageTitle(), 00364 $this->offset, 00365 $this->limit, 00366 $this->powerSearchOptions() + array( 'search' => $term ), 00367 $this->limit + $this->offset >= $totalRes 00368 ); 00369 } 00370 } 00371 wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) ); 00372 00373 $out->parserOptions()->setEditSection( false ); 00374 if ( $titleMatches ) { 00375 if ( $numTitleMatches > 0 ) { 00376 $out->wrapWikiMsg( "==$1==\n", 'titlematches' ); 00377 $out->addHTML( $this->showMatches( $titleMatches ) ); 00378 } 00379 $titleMatches->free(); 00380 } 00381 if ( $textMatches && !$textStatus ) { 00382 // output appropriate heading 00383 if ( $numTextMatches > 0 && $numTitleMatches > 0 ) { 00384 // if no title matches the heading is redundant 00385 $out->wrapWikiMsg( "==$1==\n", 'textmatches' ); 00386 } 00387 00388 // show interwiki results if any 00389 if ( $textMatches->hasInterwikiResults() ) { 00390 $out->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ) ); 00391 } 00392 // show results 00393 if ( $numTextMatches > 0 ) { 00394 $out->addHTML( $this->showMatches( $textMatches ) ); 00395 } 00396 00397 $textMatches->free(); 00398 } 00399 if ( $num === 0 ) { 00400 if ( $textStatus ) { 00401 $out->addHTML( '<div class="error">' . 00402 $textStatus->getMessage( 'search-error' ) . '</div>' ); 00403 } else { 00404 $out->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", 00405 array( 'search-nonefound', wfEscapeWikiText( $term ) ) ); 00406 $this->showCreateLink( $title, $num, $titleMatches, $textMatches ); 00407 } 00408 } 00409 $out->addHtml( "</div>" ); 00410 00411 if ( $prevnext ) { 00412 $out->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" ); 00413 } 00414 } 00415 00422 protected function showCreateLink( $title, $num, $titleMatches, $textMatches ) { 00423 // show direct page/create link if applicable 00424 00425 // Check DBkey !== '' in case of fragment link only. 00426 if ( is_null( $title ) || $title->getDBkey() === '' 00427 || ( $titleMatches !== null && $titleMatches->searchContainedSyntax() ) 00428 || ( $textMatches !== null && $textMatches->searchContainedSyntax() ) 00429 ) { 00430 // invalid title 00431 // preserve the paragraph for margins etc... 00432 $this->getOutput()->addHtml( '<p></p>' ); 00433 00434 return; 00435 } 00436 00437 $linkClass = 'mw-search-createlink'; 00438 if ( $title->isKnown() ) { 00439 $messageName = 'searchmenu-exists'; 00440 $linkClass = 'mw-search-exists'; 00441 } elseif ( $title->quickUserCan( 'create', $this->getUser() ) ) { 00442 $messageName = 'searchmenu-new'; 00443 } else { 00444 $messageName = 'searchmenu-new-nocreate'; 00445 } 00446 $params = array( 00447 $messageName, 00448 wfEscapeWikiText( $title->getPrefixedText() ), 00449 Message::numParam( $num ) 00450 ); 00451 wfRunHooks( 'SpecialSearchCreateLink', array( $title, &$params ) ); 00452 00453 // Extensions using the hook might still return an empty $messageName 00454 if ( $messageName ) { 00455 $this->getOutput()->wrapWikiMsg( "<p class=\"$linkClass\">\n$1</p>", $params ); 00456 } else { 00457 // preserve the paragraph for margins etc... 00458 $this->getOutput()->addHtml( '<p></p>' ); 00459 } 00460 } 00461 00465 protected function setupPage( $term ) { 00466 # Should advanced UI be used? 00467 $this->searchAdvanced = ( $this->profile === 'advanced' ); 00468 $out = $this->getOutput(); 00469 if ( strval( $term ) !== '' ) { 00470 $out->setPageTitle( $this->msg( 'searchresults' ) ); 00471 $out->setHTMLTitle( $this->msg( 'pagetitle' ) 00472 ->rawParams( $this->msg( 'searchresults-title' )->rawParams( $term )->text() ) 00473 ->inContentLanguage()->text() 00474 ); 00475 } 00476 // add javascript specific to special:search 00477 $out->addModules( 'mediawiki.special.search' ); 00478 } 00479 00487 protected function powerSearch( &$request ) { 00488 $arr = array(); 00489 foreach ( SearchEngine::searchableNamespaces() as $ns => $name ) { 00490 if ( $request->getCheck( 'ns' . $ns ) ) { 00491 $arr[] = $ns; 00492 } 00493 } 00494 00495 return $arr; 00496 } 00497 00503 protected function powerSearchOptions() { 00504 $opt = array(); 00505 if ( $this->profile !== 'advanced' ) { 00506 $opt['profile'] = $this->profile; 00507 } else { 00508 foreach ( $this->namespaces as $n ) { 00509 $opt['ns' . $n] = 1; 00510 } 00511 } 00512 00513 return $opt + $this->extraParams; 00514 } 00515 00521 protected function saveNamespaces() { 00522 $user = $this->getUser(); 00523 $request = $this->getRequest(); 00524 00525 if ( $user->isLoggedIn() && 00526 $user->matchEditToken( 00527 $request->getVal( 'nsRemember' ), 00528 'searchnamespace', 00529 $request 00530 ) 00531 ) { 00532 // Reset namespace preferences: namespaces are not searched 00533 // when they're not mentioned in the URL parameters. 00534 foreach ( MWNamespace::getValidNamespaces() as $n ) { 00535 $user->setOption( 'searchNs' . $n, false ); 00536 } 00537 // The request parameters include all the namespaces to be searched. 00538 // Even if they're the same as an existing profile, they're not eaten. 00539 foreach ( $this->namespaces as $n ) { 00540 $user->setOption( 'searchNs' . $n, true ); 00541 } 00542 00543 $user->saveSettings(); 00544 return true; 00545 } 00546 00547 return false; 00548 } 00549 00557 protected function showMatches( &$matches ) { 00558 global $wgContLang; 00559 00560 $profile = new ProfileSection( __METHOD__ ); 00561 $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); 00562 00563 $out = "<ul class='mw-search-results'>\n"; 00564 $result = $matches->next(); 00565 while ( $result ) { 00566 $out .= $this->showHit( $result, $terms ); 00567 $result = $matches->next(); 00568 } 00569 $out .= "</ul>\n"; 00570 00571 // convert the whole thing to desired language variant 00572 $out = $wgContLang->convert( $out ); 00573 00574 return $out; 00575 } 00576 00585 protected function showHit( $result, $terms ) { 00586 $profile = new ProfileSection( __METHOD__ ); 00587 00588 if ( $result->isBrokenTitle() ) { 00589 return ''; 00590 } 00591 00592 $title = $result->getTitle(); 00593 00594 $titleSnippet = $result->getTitleSnippet( $terms ); 00595 00596 if ( $titleSnippet == '' ) { 00597 $titleSnippet = null; 00598 } 00599 00600 $link_t = clone $title; 00601 00602 wfRunHooks( 'ShowSearchHitTitle', 00603 array( &$link_t, &$titleSnippet, $result, $terms, $this ) ); 00604 00605 $link = Linker::linkKnown( 00606 $link_t, 00607 $titleSnippet 00608 ); 00609 00610 //If page content is not readable, just return the title. 00611 //This is not quite safe, but better than showing excerpts from non-readable pages 00612 //Note that hiding the entry entirely would screw up paging. 00613 if ( !$title->userCan( 'read', $this->getUser() ) ) { 00614 return "<li>{$link}</li>\n"; 00615 } 00616 00617 // If the page doesn't *exist*... our search index is out of date. 00618 // The least confusing at this point is to drop the result. 00619 // You may get less results, but... oh well. :P 00620 if ( $result->isMissingRevision() ) { 00621 return ''; 00622 } 00623 00624 // format redirects / relevant sections 00625 $redirectTitle = $result->getRedirectTitle(); 00626 $redirectText = $result->getRedirectSnippet( $terms ); 00627 $sectionTitle = $result->getSectionTitle(); 00628 $sectionText = $result->getSectionSnippet( $terms ); 00629 $redirect = ''; 00630 00631 if ( !is_null( $redirectTitle ) ) { 00632 if ( $redirectText == '' ) { 00633 $redirectText = null; 00634 } 00635 00636 $redirect = "<span class='searchalttitle'>" . 00637 $this->msg( 'search-redirect' )->rawParams( 00638 Linker::linkKnown( $redirectTitle, $redirectText ) )->text() . 00639 "</span>"; 00640 } 00641 00642 $section = ''; 00643 00644 if ( !is_null( $sectionTitle ) ) { 00645 if ( $sectionText == '' ) { 00646 $sectionText = null; 00647 } 00648 00649 $section = "<span class='searchalttitle'>" . 00650 $this->msg( 'search-section' )->rawParams( 00651 Linker::linkKnown( $sectionTitle, $sectionText ) )->text() . 00652 "</span>"; 00653 } 00654 00655 // format text extract 00656 $extract = "<div class='searchresult'>" . $result->getTextSnippet( $terms ) . "</div>"; 00657 00658 $lang = $this->getLanguage(); 00659 00660 // format description 00661 $byteSize = $result->getByteSize(); 00662 $wordCount = $result->getWordCount(); 00663 $timestamp = $result->getTimestamp(); 00664 $size = $this->msg( 'search-result-size', $lang->formatSize( $byteSize ) ) 00665 ->numParams( $wordCount )->escaped(); 00666 00667 if ( $title->getNamespace() == NS_CATEGORY ) { 00668 $cat = Category::newFromTitle( $title ); 00669 $size = $this->msg( 'search-result-category-size' ) 00670 ->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() ) 00671 ->escaped(); 00672 } 00673 00674 $date = $lang->userTimeAndDate( $timestamp, $this->getUser() ); 00675 00676 $fileMatch = ''; 00677 // Include a thumbnail for media files... 00678 if ( $title->getNamespace() == NS_FILE ) { 00679 $img = $result->getFile(); 00680 $img = $img ?: wfFindFile( $title ); 00681 if ( $result->isFileMatch() ) { 00682 $fileMatch = "<span class='searchalttitle'>" . 00683 $this->msg( 'search-file-match' )->escaped() . "</span>"; 00684 } 00685 if ( $img ) { 00686 $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); 00687 if ( $thumb ) { 00688 $desc = $this->msg( 'parentheses' )->rawParams( $img->getShortDesc() )->escaped(); 00689 // Float doesn't seem to interact well with the bullets. 00690 // Table messes up vertical alignment of the bullets. 00691 // Bullets are therefore disabled (didn't look great anyway). 00692 return "<li>" . 00693 '<table class="searchResultImage">' . 00694 '<tr>' . 00695 '<td style="width: 120px; text-align: center; vertical-align: top;">' . 00696 $thumb->toHtml( array( 'desc-link' => true ) ) . 00697 '</td>' . 00698 '<td style="vertical-align: top;">' . 00699 "{$link} {$redirect} {$section} {$fileMatch}" . 00700 $extract . 00701 "<div class='mw-search-result-data'>{$desc} - {$date}</div>" . 00702 '</td>' . 00703 '</tr>' . 00704 '</table>' . 00705 "</li>\n"; 00706 } 00707 } 00708 } 00709 00710 $html = null; 00711 00712 $score = ''; 00713 if ( wfRunHooks( 'ShowSearchHit', array( 00714 $this, $result, $terms, 00715 &$link, &$redirect, &$section, &$extract, 00716 &$score, &$size, &$date, &$related, 00717 &$html 00718 ) ) ) { 00719 $html = "<li><div class='mw-search-result-heading'>" . 00720 "{$link} {$redirect} {$section} {$fileMatch}</div> {$extract}\n" . 00721 "<div class='mw-search-result-data'>{$size} - {$date}</div>" . 00722 "</li>\n"; 00723 } 00724 00725 return $html; 00726 } 00727 00736 protected function showInterwiki( $matches, $query ) { 00737 global $wgContLang; 00738 $profile = new ProfileSection( __METHOD__ ); 00739 00740 $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" . 00741 $this->msg( 'search-interwiki-caption' )->text() . "</div>\n"; 00742 $out .= "<ul class='mw-search-iwresults'>\n"; 00743 00744 // work out custom project captions 00745 $customCaptions = array(); 00746 // format per line <iwprefix>:<caption> 00747 $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() ); 00748 foreach ( $customLines as $line ) { 00749 $parts = explode( ":", $line, 2 ); 00750 if ( count( $parts ) == 2 ) { // validate line 00751 $customCaptions[$parts[0]] = $parts[1]; 00752 } 00753 } 00754 00755 if ( !is_array( $matches ) ) { 00756 $matches = array( $matches ); 00757 } 00758 00759 foreach ( $matches as $set ) { 00760 $prev = null; 00761 $result = $set->next(); 00762 while ( $result ) { 00763 $out .= $this->showInterwikiHit( $result, $prev, $query, $customCaptions ); 00764 $prev = $result->getInterwikiPrefix(); 00765 $result = $set->next(); 00766 } 00767 } 00768 00769 // @todo Should support paging in a non-confusing way (not sure how though, maybe via ajax).. 00770 $out .= "</ul></div>\n"; 00771 00772 // convert the whole thing to desired language variant 00773 $out = $wgContLang->convert( $out ); 00774 00775 return $out; 00776 } 00777 00788 protected function showInterwikiHit( $result, $lastInterwiki, $query, $customCaptions ) { 00789 $profile = new ProfileSection( __METHOD__ ); 00790 00791 if ( $result->isBrokenTitle() ) { 00792 return ''; 00793 } 00794 00795 $title = $result->getTitle(); 00796 00797 $titleSnippet = $result->getTitleSnippet(); 00798 00799 if ( $titleSnippet == '' ) { 00800 $titleSnippet = null; 00801 } 00802 00803 $link = Linker::linkKnown( 00804 $title, 00805 $titleSnippet 00806 ); 00807 00808 // format redirect if any 00809 $redirectTitle = $result->getRedirectTitle(); 00810 $redirectText = $result->getRedirectSnippet(); 00811 $redirect = ''; 00812 if ( !is_null( $redirectTitle ) ) { 00813 if ( $redirectText == '' ) { 00814 $redirectText = null; 00815 } 00816 00817 $redirect = "<span class='searchalttitle'>" . 00818 $this->msg( 'search-redirect' )->rawParams( 00819 Linker::linkKnown( $redirectTitle, $redirectText ) )->text() . 00820 "</span>"; 00821 } 00822 00823 $out = ""; 00824 // display project name 00825 if ( is_null( $lastInterwiki ) || $lastInterwiki != $title->getInterwiki() ) { 00826 if ( array_key_exists( $title->getInterwiki(), $customCaptions ) ) { 00827 // captions from 'search-interwiki-custom' 00828 $caption = $customCaptions[$title->getInterwiki()]; 00829 } else { 00830 // default is to show the hostname of the other wiki which might suck 00831 // if there are many wikis on one hostname 00832 $parsed = wfParseUrl( $title->getFullURL() ); 00833 $caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text(); 00834 } 00835 // "more results" link (special page stuff could be localized, but we might not know target lang) 00836 $searchTitle = Title::newFromText( $title->getInterwiki() . ":Special:Search" ); 00837 $searchLink = Linker::linkKnown( 00838 $searchTitle, 00839 $this->msg( 'search-interwiki-more' )->text(), 00840 array(), 00841 array( 00842 'search' => $query, 00843 'fulltext' => 'Search' 00844 ) 00845 ); 00846 $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'> 00847 {$searchLink}</span>{$caption}</div>\n<ul>"; 00848 } 00849 00850 $out .= "<li>{$link} {$redirect}</li>\n"; 00851 00852 return $out; 00853 } 00854 00862 protected function powerSearchBox( $term, $opts ) { 00863 global $wgContLang; 00864 00865 // Groups namespaces into rows according to subject 00866 $rows = array(); 00867 foreach ( SearchEngine::searchableNamespaces() as $namespace => $name ) { 00868 $subject = MWNamespace::getSubject( $namespace ); 00869 if ( !array_key_exists( $subject, $rows ) ) { 00870 $rows[$subject] = ""; 00871 } 00872 00873 $name = $wgContLang->getConverter()->convertNamespace( $namespace ); 00874 if ( $name == '' ) { 00875 $name = $this->msg( 'blanknamespace' )->text(); 00876 } 00877 00878 $rows[$subject] .= 00879 Xml::openElement( 'td' ) . 00880 Xml::checkLabel( 00881 $name, 00882 "ns{$namespace}", 00883 "mw-search-ns{$namespace}", 00884 in_array( $namespace, $this->namespaces ) 00885 ) . 00886 Xml::closeElement( 'td' ); 00887 } 00888 00889 $rows = array_values( $rows ); 00890 $numRows = count( $rows ); 00891 00892 // Lays out namespaces in multiple floating two-column tables so they'll 00893 // be arranged nicely while still accommodating different screen widths 00894 $namespaceTables = ''; 00895 for ( $i = 0; $i < $numRows; $i += 4 ) { 00896 $namespaceTables .= Xml::openElement( 00897 'table', 00898 array( 'cellpadding' => 0, 'cellspacing' => 0 ) 00899 ); 00900 00901 for ( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) { 00902 $namespaceTables .= Xml::tags( 'tr', null, $rows[$j] ); 00903 } 00904 00905 $namespaceTables .= Xml::closeElement( 'table' ); 00906 } 00907 00908 $showSections = array( 'namespaceTables' => $namespaceTables ); 00909 00910 wfRunHooks( 'SpecialSearchPowerBox', array( &$showSections, $term, $opts ) ); 00911 00912 $hidden = ''; 00913 foreach ( $opts as $key => $value ) { 00914 $hidden .= Html::hidden( $key, $value ); 00915 } 00916 00917 # Stuff to feed saveNamespaces() 00918 $remember = ''; 00919 $user = $this->getUser(); 00920 if ( $user->isLoggedIn() ) { 00921 $remember .= Xml::checkLabel( 00922 wfMessage( 'powersearch-remember' )->text(), 00923 'nsRemember', 00924 'mw-search-powersearch-remember', 00925 false, 00926 // The token goes here rather than in a hidden field so it 00927 // is only sent when necessary (not every form submission). 00928 array( 'value' => $user->getEditToken( 00929 'searchnamespace', 00930 $this->getRequest() 00931 ) ) 00932 ); 00933 } 00934 00935 // Return final output 00936 return Xml::openElement( 'fieldset', array( 'id' => 'mw-searchoptions' ) ) . 00937 Xml::element( 'legend', null, $this->msg( 'powersearch-legend' )->text() ) . 00938 Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() ) . 00939 Xml::element( 'div', array( 'id' => 'mw-search-togglebox' ), '', false ) . 00940 Xml::element( 'div', array( 'class' => 'divider' ), '', false ) . 00941 implode( Xml::element( 'div', array( 'class' => 'divider' ), '', false ), $showSections ) . 00942 $hidden . 00943 Xml::element( 'div', array( 'class' => 'divider' ), '', false ) . 00944 $remember . 00945 Xml::closeElement( 'fieldset' ); 00946 } 00947 00951 protected function getSearchProfiles() { 00952 // Builds list of Search Types (profiles) 00953 $nsAllSet = array_keys( SearchEngine::searchableNamespaces() ); 00954 00955 $profiles = array( 00956 'default' => array( 00957 'message' => 'searchprofile-articles', 00958 'tooltip' => 'searchprofile-articles-tooltip', 00959 'namespaces' => SearchEngine::defaultNamespaces(), 00960 'namespace-messages' => SearchEngine::namespacesAsText( 00961 SearchEngine::defaultNamespaces() 00962 ), 00963 ), 00964 'images' => array( 00965 'message' => 'searchprofile-images', 00966 'tooltip' => 'searchprofile-images-tooltip', 00967 'namespaces' => array( NS_FILE ), 00968 ), 00969 'all' => array( 00970 'message' => 'searchprofile-everything', 00971 'tooltip' => 'searchprofile-everything-tooltip', 00972 'namespaces' => $nsAllSet, 00973 ), 00974 'advanced' => array( 00975 'message' => 'searchprofile-advanced', 00976 'tooltip' => 'searchprofile-advanced-tooltip', 00977 'namespaces' => self::NAMESPACES_CURRENT, 00978 ) 00979 ); 00980 00981 wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) ); 00982 00983 foreach ( $profiles as &$data ) { 00984 if ( !is_array( $data['namespaces'] ) ) { 00985 continue; 00986 } 00987 sort( $data['namespaces'] ); 00988 } 00989 00990 return $profiles; 00991 } 00992 00997 protected function formHeader( $term ) { 00998 $out = Xml::openElement( 'div', array( 'class' => 'mw-search-formheader' ) ); 00999 01000 $bareterm = $term; 01001 if ( $this->startsWithImage( $term ) ) { 01002 // Deletes prefixes 01003 $bareterm = substr( $term, strpos( $term, ':' ) + 1 ); 01004 } 01005 01006 $profiles = $this->getSearchProfiles(); 01007 $lang = $this->getLanguage(); 01008 01009 // Outputs XML for Search Types 01010 $out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) ); 01011 $out .= Xml::openElement( 'ul' ); 01012 foreach ( $profiles as $id => $profile ) { 01013 if ( !isset( $profile['parameters'] ) ) { 01014 $profile['parameters'] = array(); 01015 } 01016 $profile['parameters']['profile'] = $id; 01017 01018 $tooltipParam = isset( $profile['namespace-messages'] ) ? 01019 $lang->commaList( $profile['namespace-messages'] ) : null; 01020 $out .= Xml::tags( 01021 'li', 01022 array( 01023 'class' => $this->profile === $id ? 'current' : 'normal' 01024 ), 01025 $this->makeSearchLink( 01026 $bareterm, 01027 array(), 01028 $this->msg( $profile['message'] )->text(), 01029 $this->msg( $profile['tooltip'], $tooltipParam )->text(), 01030 $profile['parameters'] 01031 ) 01032 ); 01033 } 01034 $out .= Xml::closeElement( 'ul' ); 01035 $out .= Xml::closeElement( 'div' ); 01036 $out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false ); 01037 $out .= Xml::closeElement( 'div' ); 01038 01039 // Hidden stuff 01040 $opts = array(); 01041 $opts['profile'] = $this->profile; 01042 01043 if ( $this->profile === 'advanced' ) { 01044 $out .= $this->powerSearchBox( $term, $opts ); 01045 } else { 01046 $form = ''; 01047 wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $this->profile, $term, $opts ) ); 01048 $out .= $form; 01049 } 01050 01051 return $out; 01052 } 01053 01060 protected function shortDialog( $term, $resultsShown, $totalNum ) { 01061 $out = Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ); 01062 $out .= Html::hidden( 'profile', $this->profile ) . "\n"; 01063 // Term box 01064 $out .= Html::input( 'search', $term, 'search', array( 01065 'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText', 01066 'size' => '50', 01067 'autofocus', 01068 'class' => 'mw-ui-input mw-ui-input-inline', 01069 ) ) . "\n"; 01070 $out .= Html::hidden( 'fulltext', 'Search' ) . "\n"; 01071 $out .= Xml::submitButton( 01072 $this->msg( 'searchbutton' )->text(), 01073 array( 'class' => array( 'mw-ui-button', 'mw-ui-progressive' ) ) 01074 ) . "\n"; 01075 01076 // Results-info 01077 if ( $totalNum > 0 && $this->offset < $totalNum ) { 01078 $top = $this->msg( 'search-showingresults' ) 01079 ->numParams( $this->offset + 1, $this->offset + $resultsShown, $totalNum ) 01080 ->numParams( $resultsShown ) 01081 ->parse(); 01082 $out .= Xml::tags( 'div', array( 'class' => 'results-info' ), $top ) . 01083 Xml::element( 'div', array( 'style' => 'clear:both' ), '', false ); 01084 } 01085 01086 return $out . $this->didYouMeanHtml; 01087 } 01088 01099 protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) { 01100 $opt = $params; 01101 foreach ( $namespaces as $n ) { 01102 $opt['ns' . $n] = 1; 01103 } 01104 01105 $stParams = array_merge( 01106 array( 01107 'search' => $term, 01108 'fulltext' => $this->msg( 'search' )->text() 01109 ), 01110 $opt 01111 ); 01112 01113 return Xml::element( 01114 'a', 01115 array( 01116 'href' => $this->getPageTitle()->getLocalURL( $stParams ), 01117 'title' => $tooltip 01118 ), 01119 $label 01120 ); 01121 } 01122 01129 protected function startsWithImage( $term ) { 01130 global $wgContLang; 01131 01132 $parts = explode( ':', $term ); 01133 if ( count( $parts ) > 1 ) { 01134 return $wgContLang->getNsIndex( $parts[0] ) == NS_FILE; 01135 } 01136 01137 return false; 01138 } 01139 01146 protected function startsWithAll( $term ) { 01147 01148 $allkeyword = $this->msg( 'searchall' )->inContentLanguage()->text(); 01149 01150 $parts = explode( ':', $term ); 01151 if ( count( $parts ) > 1 ) { 01152 return $parts[0] == $allkeyword; 01153 } 01154 01155 return false; 01156 } 01157 01163 public function getSearchEngine() { 01164 if ( $this->searchEngine === null ) { 01165 $this->searchEngine = $this->searchEngineType ? 01166 SearchEngine::create( $this->searchEngineType ) : SearchEngine::create(); 01167 } 01168 01169 return $this->searchEngine; 01170 } 01171 01176 function getProfile() { 01177 return $this->profile; 01178 } 01179 01184 function getNamespaces() { 01185 return $this->namespaces; 01186 } 01187 01197 public function setExtraParam( $key, $value ) { 01198 $this->extraParams[$key] = $value; 01199 } 01200 01201 protected function getGroupName() { 01202 return 'pages'; 01203 } 01204 }