EBernhardson has uploaded a new change for review. https://gerrit.wikimedia.org/r/322012
Change subject: [WIP] Pull rendering of single result out of SpecialSearch ...................................................................... [WIP] Pull rendering of single result out of SpecialSearch Bug: T150390 Change-Id: If78cb0c29ae394f16e465c15a8e8246c1b56dcea --- M autoload.php M includes/specials/SpecialSearch.php A includes/widget/BasicSearchResultWidget.php A includes/widget/InterwikiSearchResultWidget.php 4 files changed, 297 insertions(+), 230 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/core refs/changes/12/322012/1 diff --git a/autoload.php b/autoload.php index 30ef985..3b7d587 100644 --- a/autoload.php +++ b/autoload.php @@ -919,10 +919,13 @@ 'MediaWiki\\Tidy\\RaggettInternalPHP' => __DIR__ . '/includes/tidy/RaggettInternalPHP.php', 'MediaWiki\\Tidy\\RaggettWrapper' => __DIR__ . '/includes/tidy/RaggettWrapper.php', 'MediaWiki\\Tidy\\TidyDriverBase' => __DIR__ . '/includes/tidy/TidyDriverBase.php', + 'MediaWiki\\Widget\\BasicSearchResultSetWidget' => __DIR__ . '/includes/widget/BasicSearchResultSetWidget.php', + 'MediaWiki\\Widget\\BasicSearchResultWidget' => __DIR__ . '/includes/widget/BasicSearchResultWidget.php', 'MediaWiki\\Widget\\ComplexNamespaceInputWidget' => __DIR__ . '/includes/widget/ComplexNamespaceInputWidget.php', 'MediaWiki\\Widget\\ComplexTitleInputWidget' => __DIR__ . '/includes/widget/ComplexTitleInputWidget.php', 'MediaWiki\\Widget\\DateInputWidget' => __DIR__ . '/includes/widget/DateInputWidget.php', 'MediaWiki\\Widget\\DateTimeInputWidget' => __DIR__ . '/includes/widget/DateTimeInputWidget.php', + 'MediaWiki\\Widget\\InterwikiSearchResultWidget' => __DIR__ . '/includes/widget/InterwikiSearchResultWidget.php', 'MediaWiki\\Widget\\NamespaceInputWidget' => __DIR__ . '/includes/widget/NamespaceInputWidget.php', 'MediaWiki\\Widget\\SearchInputWidget' => __DIR__ . '/includes/widget/SearchInputWidget.php', 'MediaWiki\\Widget\\TitleInputWidget' => __DIR__ . '/includes/widget/TitleInputWidget.php', diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index 6b3517f..9994839 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -741,155 +741,8 @@ * @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; + $widget = new \MediaWiki\Widget\BasicSearchResultWidget( $this ); + return $widget->render( $result, $terms, $position ); } /** @@ -920,9 +773,6 @@ 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(); @@ -931,97 +781,63 @@ $matches = [ $matches ]; } + $iwResults = []; foreach ( $matches as $set ) { - $prev = null; $result = $set->next(); while ( $result ) { - $out .= $this->showInterwikiHit( $result, $prev, $query ); - $prev = $result->getInterwikiPrefix(); + if ( !$result->isBrokenTitle() ) { + $iwResults[$result->getTitle()->getInterwiki()][] = $result; + } $result = $set->next(); } } - // @todo Should support paging in a non-confusing way (not sure how though, maybe via ajax).. - $out .= "</ul></div>\n"; + $out = ''; + $widget = new MediaWiki\Widget\InterwikiSearchResultWidget( $this ); + foreach ( $iwResults as $iwPrefix => $results ) { + $out .= $this->iwHeaderHtml( $iwPrefix ); + $out .= "<ul class='mw-search-iwresults'>"; + foreach ( $results as $result ) { + $out .= $widget->render( $result ); + } + $ot .= "</ul>"; + } + + $out = + "<div id='mw-search-interwiki'>" . + "<div id='mw-search-interwiki-caption'>" . + $this->msg( 'search-interwiki-caption' )->text() . + "</div>" . + $out . + "</div>"; // convert the whole thing to desired language variant - $out = $wgContLang->convert( $out ); - - return $out; + return $wgContLang->convert( $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 ''; + protected function iwHeaderHtml( $iwPrefix ) { + if ( isset( $this->customCaptions[$iwPrefix] ) ) { + $caption = $this->customCaptions[$iwPrefix]; + } else { + $iwLookup = MediaWiki\MediaWikiServices::getInstance()->getInterwikiLookup(); + $interwiki = $iwLookup->fetch( $iwPrefix ); + $parsed = wfParseUrl( wfExpandUrl( $interwiki ? $interwiki->getURL() : '/' ) ); + $caption = $this->msg( 'search-interwiki-default', $parsed['host'] )->text(); } - - $title = $result->getTitle(); - - $titleSnippet = $result->getTitleSnippet(); - - if ( $titleSnippet == '' ) { - $titleSnippet = null; - } - - $link = Linker::linkKnown( - $title, - $titleSnippet + $searchLink = Linker::linkKnown( + Title::newFromText( "$iwPrefix:Special:Search" ), + $this->msg( 'search-interwiki-more' )->text(), + [], + [ + 'search' => $query, + 'fulltext' => 1, + ] ); - - // 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; + return + "<div class='mw-search-interwiki-project'>" . + "<span class='mw-search-interwiki-more'>{$searchLink}</span>" . + $caption . + "</div>"; } /** diff --git a/includes/widget/BasicSearchResultWidget.php b/includes/widget/BasicSearchResultWidget.php new file mode 100644 index 0000000..9627cbe --- /dev/null +++ b/includes/widget/BasicSearchResultWidget.php @@ -0,0 +1,203 @@ +<?php + +namespace MediaWiki\Widget; + +use Hooks; +use Linker; +use SearchResult; +use SpecialSearch; +use Title; + +class BasicSearchResultWidget { + /** @var SpecialSearch */ + protected $specialPage; + + public function __construct( SpecialSearch $specialPage ) { + $this->specialPage = $specialPage; + } + /** + * @param SearchResult $result The result to be rendered + * @param array $terms The terms to highlight (TODO: Move highlighting out of here?) + * @param int $position Position within the search results, including offset. + * @return string + */ + public function render( SearchResult $result, $terms, $position ) { + // 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... on well. :P + if ( $result->isBrokenTitle() || $result->isMissingRevision() ) { + return ''; + } + + $link = $this->generateMainLinkHtml( $result ); + // If page content is not readable, just return ths 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 (really?). + if ( !$result->getTitle()->userCan( 'read', $this->specialPage->getUser() ) ) { + return "<li>{$link}</li>"; + } + + $redirect = $this->generateRedirectHtml( $result ); + $section = $this->generateSectionHtml( $result ); + $category = $this->generateCategoryHtml( $result ); + $date = $this->specialPage->getLanguage()->userTimeAndDate( + $result->getTimestamp(), + $this->specialPage->getUser() + ); + list( $file, $desc, $thumb ) = $this->generateFileHtml( $result ); + $extract = "<div class='searchresult'>" . + $result->getTextSnippet( $terms ) . + "</div>"; + + if ( $thumb === null ) { + // If no thumb, then the description is about size + $desc = $this->generateSizeHtml( $result ); + + // Let hooks do their own final construction if desired. Not sure + // why this is only for results without thumbnails, but keeping it + // as-is for now to prevent breaking hook consumers. + $html = null; + $score = ''; + $related = ''; + if ( !Hooks::run( 'ShowSearchHit', [ + $this->specialPage, $result, $terms, + &$link, &$redirect, &$section, &$extract, + &$score, &$size, &$date, &$related, &$html + ] ) ) { + return $html; + } + } + + // All the pieces have been collected. Now generate the final HTML + $joined = "{$link} {$redirect} {$category} {$section} {$file}"; + $meta = "<div class='mw-search-result-data'>{$desc} - {$date}</div>"; + + if ( $thumb === null ) { + $html = + "<div class='mw-search-result-heading'>{$joined}</div>" . + "{$extract} {$meta}"; + } else { + $html = + "<table class='searchResultImage'>" . + "<tr>" . + "<td style='width: 120px; text-align: center; vertical-align: top'>" . + $thumb . + "</td>" . + "<td style='vertical-align: top'>" . + "{$joined} {$extract} {$meta}" . + "</td>" . + "</tr>" . + "</table>"; + } + + return "<li>{$html}</li>"; + } + + protected function generateMainLinkHtml( SearchResult $result ) { + $snippet = $result->getTitleSnippet(); + if ( $snippet === '' ) { + $snippet = null; + } + + // clone to prevent hook from changing the title stored inside $result + $title = clone $result->getTitle(); + $queryString = []; + + Hooks::run( 'ShowSearchHitTitle', + [ $title, &$snippet, $result, $terms, $this->specialPage, $queryString ] ); + + $link = Linker::linkKnown( + $title, + $snippet, + [ 'data-serp-pos' => $position ], + $queryString + ); + + return $link; + } + + protected function generateAltTitleHtml( $msgKey, Title $title = null, $text ) { + $inner = $title === null + ? $text + : Linker::linkKnown( $title, $text ?: null ); + + return "<span class='searchalttitle'>" . + $this->specialPage->msg( $msgKey )->rawParams( $inner )->text() + . "</span>"; + } + + protected function generateRedirectHtml( SearchResult $result ) { + $title = $result->getRedirectTitle(); + return $title === null + ? '' + : $this->generateAltTitleHtml( 'search-redirect', $title, $result->getRedirectSnippet() ); + } + + protected function generateSectionHtml( SearchResult $result ) { + $title = $result->getSectionTitle(); + return $title === null + ? '' + : $this->generateAltTitleHtml( 'search-section', $title, $result->getSectionText() ); + } + + protected function generateCategoryHtml( SearchResult $result ) { + $snippet = $result->getCategorySnippet(); + return $snippet + ? $this->generateAltTitleHtml( 'search-category', null, $snippet ) + : ''; + } + + protected function generateSizeHtml( SearchResult $result ) { + $title = $result->getTitle(); + if ( $title->getNamespace() === NS_CATEGORY ) { + $cat = Category::newFromTitle( $title ); + return $this->specialPage->msg( 'search-result-category-size' ) + ->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() ) + ->escaped(); + } else { + $lang = $this->specialPage->getLanguage(); + $bytes = $lang->formatSize( $result->getByteSize() ); + $words = $result->getWordCount(); + + return $this->specialPage->msg( 'search-result-size', $bytes ) + ->numParams( $words ) + ->escaped(); + } + } + + /** + * @param SearchResult $result + * @return array Three element array containing the main file html, + * a text description of the file, and finally the thumbnail html. + * If no thumbnail is available the second and third will be null. + */ + protected function generateFileHtml( SearchResult $result ) { + $title = $result->getTitle(); + if ( $title->getNamespace() !== NS_FILE ) { + return ['', null, null]; + } + + if ( $result->isFileMatch() ) { + $html = "<span class='searchalttitle'>" . + $this->specialPage->msg( 'search-file-match' )->escaped() . + "</span>"; + } + + $descHtml = null; + $thumbHtml = null; + + $img = $result->getFile() ?: wfFindFile( $title ); + if ( $img ) { + $thumb = $img->transform( [ 'width' => 120, 'height' => 120 ] ); + if ( $thumb ) { + $descHtml = $this->specialPage->msg( 'parentheses' ) + ->rawParams( $img->getShortDesc() ) + ->escaped(); + $thumbHtml = $thumb->toHtml( [ 'desc-link' => true ] ); + } + } + + return [ $html, $descHtml, $thumbHtml ]; + } +} diff --git a/includes/widget/InterwikiSearchResultWidget.php b/includes/widget/InterwikiSearchResultWidget.php new file mode 100644 index 0000000..b34df7c --- /dev/null +++ b/includes/widget/InterwikiSearchResultWidget.php @@ -0,0 +1,45 @@ +<?php + +namespace MediaWiki\Widget; + +use Linker; +use SearchResult; +use SpecialSearch; +use Title; + +class InterwikiSearchResultWidget { + /** @var SpecialSearch */ + protected $specialSearch; + + public function __construct( SpecialSearch $specialSearch ) { + $this->specialSearch = $specialSearch; + } + + public function render( SearchResult $result, $lastInterwiki ) { + $title = $result->getTitle(); + $titleSnippet = $result->getTitleSnippet(); + if ( $titleSnippet === '' ) { + $titleSnippet = null; + } + + $link = Linker::linkKnown( $title, $titleSnippet ); + + $redirectTitle = $result->getRedirectTitle(); + $redirectText = $result->getRedirectSnippet(); + $redirect = ''; + if ( $redirectTitle === null ) { + if ( $redirectText === '' ) { + $redirectText = null; + } + + $redirect = + "<span class='searchalttitle'>" . + $this->specialSearch->msg( 'search-redirect' )->rawParams( + Linker::linkKnown( $redirectTitle, $redirectText ) + )->text() . + "</span>"; + } + + return "<li>{$link} {$redirect}</li>"; + } +} -- To view, visit https://gerrit.wikimedia.org/r/322012 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: If78cb0c29ae394f16e465c15a8e8246c1b56dcea Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/core Gerrit-Branch: master Gerrit-Owner: EBernhardson <ebernhard...@wikimedia.org> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits