- /**
- * Show whole set of results
- *
- * @param SearchResultSet $matches
- * @param string $interwiki Interwiki name
- *
- * @return string
- */
- protected function showMatches( $matches, $interwiki = null ) {
- global $wgContLang;
-
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
- $out = '';
- $result = $matches->next();
- $pos = $this->offset;
-
- if ( $result && $interwiki ) {
- $out .= $this->interwikiHeader( $interwiki, $matches );
- }
-
- $out .= "<ul class='mw-search-results'>\n";
- while ( $result ) {
- $out .= $this->showHit( $result, $terms, $pos++ );
- $result = $matches->next();
- }
- $out .= "</ul>\n";
-
- // convert the whole thing to desired language variant
- $out = $wgContLang->convert( $out );
-
- return $out;
- }
-
- /**
- * Format a single hit result
- *
- * @param SearchResult $result
- * @param array $terms Terms to highlight
- * @param int $position Position within the search results, including offset.
- *
- * @return string
- */
- protected function showHit( SearchResult $result, $terms, $position ) {
- if ( $result->isBrokenTitle() ) {
- return '';
- }
-
- $title = $result->getTitle();
-
- $titleSnippet = $result->getTitleSnippet();
-
- if ( $titleSnippet == '' ) {
- $titleSnippet = null;
- }
-
- $link_t = clone $title;
- $query = [];
-
- Hooks::run( 'ShowSearchHitTitle',
- [ &$link_t, &$titleSnippet, $result, $terms, $this, &$query ] );
-
- $link = Linker::linkKnown(
- $link_t,
- $titleSnippet,
- [ 'data-serp-pos' => $position ], // HTML attributes
- $query
- );
-
- // If page content is not readable, just return the title.
- // This is not quite safe, but better than showing excerpts from non-readable pages
- // Note that hiding the entry entirely would screw up paging.
- if ( !$title->userCan( 'read', $this->getUser() ) ) {
- return "<li>{$link}</li>\n";
- }
-
- // If the page doesn't *exist*... our search index is out of date.
- // The least confusing at this point is to drop the result.
- // You may get less results, but... oh well. :P
- if ( $result->isMissingRevision() ) {
- return '';
- }
-
- // format redirects / relevant sections
- $redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet();
- $sectionTitle = $result->getSectionTitle();
- $sectionText = $result->getSectionSnippet();
- $categorySnippet = $result->getCategorySnippet();
-
- $redirect = '';
- if ( !is_null( $redirectTitle ) ) {
- if ( $redirectText == '' ) {
- $redirectText = null;
- }
-
- $redirect = "<span class='searchalttitle'>" .
- $this->msg( 'search-redirect' )->rawParams(
- Linker::linkKnown( $redirectTitle, $redirectText ) )->text() .
- "</span>";
- }
-
- $section = '';
- if ( !is_null( $sectionTitle ) ) {
- if ( $sectionText == '' ) {
- $sectionText = null;
- }
-
- $section = "<span class='searchalttitle'>" .
- $this->msg( 'search-section' )->rawParams(
- Linker::linkKnown( $sectionTitle, $sectionText ) )->text() .
- "</span>";
- }
-
- $category = '';
- if ( $categorySnippet ) {
- $category = "<span class='searchalttitle'>" .
- $this->msg( 'search-category' )->rawParams( $categorySnippet )->text() .
- "</span>";
- }
-
- // format text extract
- $extract = "<div class='searchresult'>" . $result->getTextSnippet( $terms ) . "</div>";
-
- $lang = $this->getLanguage();
-
- // format description
- $byteSize = $result->getByteSize();
- $wordCount = $result->getWordCount();
- $timestamp = $result->getTimestamp();
- $size = $this->msg( 'search-result-size', $lang->formatSize( $byteSize ) )
- ->numParams( $wordCount )->escaped();
-
- if ( $title->getNamespace() == NS_CATEGORY ) {
- $cat = Category::newFromTitle( $title );
- $size = $this->msg( 'search-result-category-size' )
- ->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() )
- ->escaped();
- }
-
- $date = $lang->userTimeAndDate( $timestamp, $this->getUser() );
-
- $fileMatch = '';
- // Include a thumbnail for media files...
- if ( $title->getNamespace() == NS_FILE ) {
- $img = $result->getFile();
- $img = $img ?: wfFindFile( $title );
- if ( $result->isFileMatch() ) {
- $fileMatch = "<span class='searchalttitle'>" .
- $this->msg( 'search-file-match' )->escaped() . "</span>";
- }
- if ( $img ) {
- $thumb = $img->transform( [ 'width' => 120, 'height' => 120 ] );
- if ( $thumb ) {
- $desc = $this->msg( 'parentheses' )->rawParams( $img->getShortDesc() )->escaped();
- // Float doesn't seem to interact well with the bullets.
- // Table messes up vertical alignment of the bullets.
- // Bullets are therefore disabled (didn't look great anyway).
- return "<li>" .
- '<table class="searchResultImage">' .
- '<tr>' .
- '<td style="width: 120px; text-align: center; vertical-align: top;">' .
- $thumb->toHtml( [ 'desc-link' => true ] ) .
- '</td>' .
- '<td style="vertical-align: top;">' .
- "{$link} {$redirect} {$category} {$section} {$fileMatch}" .
- $extract .
- "<div class='mw-search-result-data'>{$desc} - {$date}</div>" .
- '</td>' .
- '</tr>' .
- '</table>' .
- "</li>\n";
- }
- }
- }
-
- $html = null;
-
- $score = '';
- $related = '';
- if ( Hooks::run( 'ShowSearchHit', [
- $this, $result, $terms,
- &$link, &$redirect, &$section, &$extract,
- &$score, &$size, &$date, &$related,
- &$html
- ] ) ) {
- $html = "<li><div class='mw-search-result-heading'>" .
- "{$link} {$redirect} {$category} {$section} {$fileMatch}</div> {$extract}\n" .
- "<div class='mw-search-result-data'>{$size} - {$date}</div>" .
- "</li>\n";
- }
-
- return $html;
- }
-
- /**
- * Extract custom captions from search-interwiki-custom message
- */
- protected function getCustomCaptions() {
- if ( is_null( $this->customCaptions ) ) {
- $this->customCaptions = [];
- // format per line <iwprefix>:<caption>
- $customLines = explode( "\n", $this->msg( 'search-interwiki-custom' )->text() );
- foreach ( $customLines as $line ) {
- $parts = explode( ":", $line, 2 );
- if ( count( $parts ) == 2 ) { // validate line
- $this->customCaptions[$parts[0]] = $parts[1];
- }
- }
- }
- }
-
- /**
- * Show results from other wikis
- *
- * @param SearchResultSet|array $matches
- * @param string $query
- *
- * @return string
- */
- protected function showInterwiki( $matches, $query ) {
- global $wgContLang;
-
- $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>" .
- $this->msg( 'search-interwiki-caption' )->text() . "</div>\n";
- $out .= "<ul class='mw-search-iwresults'>\n";
-
- // work out custom project captions
- $this->getCustomCaptions();
-
- if ( !is_array( $matches ) ) {
- $matches = [ $matches ];
- }
-
- foreach ( $matches as $set ) {
- $prev = null;
- $result = $set->next();
- while ( $result ) {
- $out .= $this->showInterwikiHit( $result, $prev, $query );
- $prev = $result->getInterwikiPrefix();
- $result = $set->next();
- }
- }
-
- // @todo Should support paging in a non-confusing way (not sure how though, maybe via ajax)..
- $out .= "</ul></div>\n";
-
- // convert the whole thing to desired language variant
- $out = $wgContLang->convert( $out );
-
- return $out;
- }
-
- /**
- * Show single interwiki link
- *
- * @param SearchResult $result
- * @param string $lastInterwiki
- * @param string $query
- *
- * @return string
- */
- protected function showInterwikiHit( $result, $lastInterwiki, $query ) {
- if ( $result->isBrokenTitle() ) {
- return '';
- }
-
- $title = $result->getTitle();
-
- $titleSnippet = $result->getTitleSnippet();
-
- if ( $titleSnippet == '' ) {
- $titleSnippet = null;
- }
-
- $link = Linker::linkKnown(
- $title,
- $titleSnippet
- );
-
- // format redirect if any
- $redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet();
- $redirect = '';
- if ( !is_null( $redirectTitle ) ) {
- if ( $redirectText == '' ) {
- $redirectText = null;
- }
-
- $redirect = "<span class='searchalttitle'>" .
- $this->msg( 'search-redirect' )->rawParams(
- Linker::linkKnown( $redirectTitle, $redirectText ) )->text() .
- "</span>";
- }
-
- $out = "";
- // display project name
- if ( is_null( $lastInterwiki ) || $lastInterwiki != $title->getInterwiki() ) {
- if ( array_key_exists( $title->getInterwiki(), $this->customCaptions ) ) {
- // captions from 'search-interwiki-custom'
- $caption = $this->customCaptions[$title->getInterwiki()];
- } else {
- // default is to show the hostname of the other wiki which might suck
- // if there are many wikis on one hostname
- $parsed = wfParseUrl( $title->getFullURL() );
- $caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text();
- }
- // "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText( $title->getInterwiki() . ":Special:Search" );
- $searchLink = Linker::linkKnown(
- $searchTitle,
- $this->msg( 'search-interwiki-more' )->text(),
- [],
- [
- 'search' => $query,
- 'fulltext' => 'Search'
- ]
- );
- $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'>
- {$searchLink}</span>{$caption}</div>\n<ul>";
- }
-
- $out .= "<li>{$link} {$redirect}</li>\n";
-
- return $out;
- }
-
- /**
- * Generates the power search box at [[Special:Search]]
- *
- * @param string $term Search term
- * @param array $opts
- * @return string HTML form
- */
- protected function powerSearchBox( $term, $opts ) {
- global $wgContLang;
-
- // Groups namespaces into rows according to subject
- $rows = [];
- foreach ( $this->searchConfig->searchableNamespaces() as $namespace => $name ) {
- $subject = MWNamespace::getSubject( $namespace );
- if ( !array_key_exists( $subject, $rows ) ) {
- $rows[$subject] = "";
- }
-
- $name = $wgContLang->getConverter()->convertNamespace( $namespace );
- if ( $name == '' ) {
- $name = $this->msg( 'blanknamespace' )->text();
- }
-
- $rows[$subject] .=
- Xml::openElement( 'td' ) .
- Xml::checkLabel(
- $name,
- "ns{$namespace}",
- "mw-search-ns{$namespace}",
- in_array( $namespace, $this->namespaces )
- ) .
- Xml::closeElement( 'td' );
- }
-
- $rows = array_values( $rows );
- $numRows = count( $rows );
-
- // Lays out namespaces in multiple floating two-column tables so they'll
- // be arranged nicely while still accommodating different screen widths
- $namespaceTables = '';
- for ( $i = 0; $i < $numRows; $i += 4 ) {
- $namespaceTables .= Xml::openElement( 'table' );
-
- for ( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
- $namespaceTables .= Xml::tags( 'tr', null, $rows[$j] );
- }
-
- $namespaceTables .= Xml::closeElement( 'table' );
- }
-
- $showSections = [ 'namespaceTables' => $namespaceTables ];
-
- Hooks::run( 'SpecialSearchPowerBox', [ &$showSections, $term, $opts ] );
-
- $hidden = '';
- foreach ( $opts as $key => $value ) {
- $hidden .= Html::hidden( $key, $value );
- }
-
- # Stuff to feed saveNamespaces()
- $remember = '';
- $user = $this->getUser();
- if ( $user->isLoggedIn() ) {
- $remember .= Xml::checkLabel(
- $this->msg( 'powersearch-remember' )->text(),
- 'nsRemember',
- 'mw-search-powersearch-remember',
- false,
- // The token goes here rather than in a hidden field so it
- // is only sent when necessary (not every form submission).
- [ 'value' => $user->getEditToken(
- 'searchnamespace',
- $this->getRequest()
- ) ]
- );
- }
-
- // Return final output
- return Xml::openElement( 'fieldset', [ 'id' => 'mw-searchoptions' ] ) .
- Xml::element( 'legend', null, $this->msg( 'powersearch-legend' )->text() ) .
- Xml::tags( 'h4', null, $this->msg( 'powersearch-ns' )->parse() ) .
- Xml::element( 'div', [ 'id' => 'mw-search-togglebox' ], '', false ) .
- Xml::element( 'div', [ 'class' => 'divider' ], '', false ) .
- implode( Xml::element( 'div', [ 'class' => 'divider' ], '', false ), $showSections ) .
- $hidden .
- Xml::element( 'div', [ 'class' => 'divider' ], '', false ) .
- $remember .
- Xml::closeElement( 'fieldset' );
- }
-