3 namespace MediaWiki\Widget\Search
;
8 use MediaWiki\Linker\LinkRenderer
;
9 use MediaWiki\MediaWikiServices
;
15 * Renders a 'full' multi-line search result with metadata.
18 * some *highlighted* *text* about the search result
19 * 5KB (651 words) - 12:40, 6 Aug 2016
21 class FullSearchResultWidget
implements SearchResultWidget
{
22 /** @var SpecialSearch */
23 protected $specialPage;
24 /** @var LinkRenderer */
25 protected $linkRenderer;
27 public function __construct( SpecialSearch
$specialPage, LinkRenderer
$linkRenderer ) {
28 $this->specialPage
= $specialPage;
29 $this->linkRenderer
= $linkRenderer;
33 * @param SearchResult $result The result to render
34 * @param string $terms Terms to be highlighted (@see SearchResult::getTextSnippet)
35 * @param int $position The result position, including offset
38 public function render( SearchResult
$result, $terms, $position ) {
39 // If the page doesn't *exist*... our search index is out of date.
40 // The least confusing at this point is to drop the result.
41 // You may get less results, but... on well. :P
42 if ( $result->isBrokenTitle() ||
$result->isMissingRevision() ) {
46 $link = $this->generateMainLinkHtml( $result, $terms, $position );
47 // If page content is not readable, just return ths title.
48 // This is not quite safe, but better than showing excerpts from
49 // non-readable pages. Note that hiding the entry entirely would
50 // screw up paging (really?).
51 if ( !$result->getTitle()->userCan( 'read', $this->specialPage
->getUser() ) ) {
52 return "<li>{$link}</li>";
55 $redirect = $this->generateRedirectHtml( $result );
56 $section = $this->generateSectionHtml( $result );
57 $category = $this->generateCategoryHtml( $result );
58 $date = $this->specialPage
->getLanguage()->userTimeAndDate(
59 $result->getTimestamp(),
60 $this->specialPage
->getUser()
62 list( $file, $desc, $thumb ) = $this->generateFileHtml( $result );
63 $snippet = $result->getTextSnippet( $terms );
65 $extract = "<div class='searchresult'>$snippet</div>";
70 if ( $thumb === null ) {
71 // If no thumb, then the description is about size
72 $desc = $this->generateSizeHtml( $result );
74 // Let hooks do their own final construction if desired.
75 // FIXME: Not sure why this is only for results without thumbnails,
76 // but keeping it as-is for now to prevent breaking hook consumers.
80 if ( !Hooks
::run( 'ShowSearchHit', [
81 $this->specialPage
, $result, $terms,
82 &$link, &$redirect, &$section, &$extract,
83 &$score, &$desc, &$date, &$related, &$html
89 // All the pieces have been collected. Now generate the final HTML
90 $joined = "{$link} {$redirect} {$category} {$section} {$file}";
91 $meta = $this->buildMeta( $desc, $date );
93 if ( $thumb === null ) {
95 "<div class='mw-search-result-heading'>{$joined}</div>" .
99 "<table class='searchResultImage'>" .
101 "<td style='width: 120px; text-align: center; vertical-align: top'>" .
104 "<td style='vertical-align: top'>" .
105 "{$joined} {$extract} {$meta}" .
111 return "<li class='mw-search-result'>{$html}</li>";
115 * Generates HTML for the primary call to action. It is
116 * typically the article title, but the search engine can
117 * return an exact snippet to use (typically the article
118 * title with highlighted words).
120 * @param SearchResult $result
121 * @param string $terms
122 * @param int $position
123 * @return string HTML
125 protected function generateMainLinkHtml( SearchResult
$result, $terms, $position ) {
126 $snippet = $result->getTitleSnippet();
127 if ( $snippet === '' ) {
130 $snippet = new HtmlArmor( $snippet );
133 // clone to prevent hook from changing the title stored inside $result
134 $title = clone $result->getTitle();
137 $attributes = [ 'data-serp-pos' => $position ];
138 Hooks
::run( 'ShowSearchHitTitle',
139 [ &$title, &$snippet, $result, $terms, $this->specialPage
, &$query, &$attributes ] );
141 $link = $this->linkRenderer
->makeLink(
152 * Generates an alternate title link, such as (redirect from <a>Foo</a>).
154 * @param string $msgKey i18n message used to wrap title
155 * @param Title|null $title The title to link to, or null to generate
156 * the message without a link. In that case $text must be non-null.
157 * @param string|null $text The text snippet to display, or null
159 * @return string HTML
161 protected function generateAltTitleHtml( $msgKey, Title
$title = null, $text ) {
162 $inner = $title === null
164 : $this->linkRenderer
->makeLink( $title, $text ?
new HtmlArmor( $text ) : null );
166 return "<span class='searchalttitle'>" .
167 $this->specialPage
->msg( $msgKey )->rawParams( $inner )->parse()
172 * @param SearchResult $result
173 * @return string HTML
175 protected function generateRedirectHtml( SearchResult
$result ) {
176 $title = $result->getRedirectTitle();
177 return $title === null
179 : $this->generateAltTitleHtml( 'search-redirect', $title, $result->getRedirectSnippet() );
183 * @param SearchResult $result
184 * @return string HTML
186 protected function generateSectionHtml( SearchResult
$result ) {
187 $title = $result->getSectionTitle();
188 return $title === null
190 : $this->generateAltTitleHtml( 'search-section', $title, $result->getSectionSnippet() );
194 * @param SearchResult $result
195 * @return string HTML
197 protected function generateCategoryHtml( SearchResult
$result ) {
198 $snippet = $result->getCategorySnippet();
200 ?
$this->generateAltTitleHtml( 'search-category', null, $snippet )
205 * @param SearchResult $result
206 * @return string HTML
208 protected function generateSizeHtml( SearchResult
$result ) {
209 $title = $result->getTitle();
210 if ( $title->getNamespace() === NS_CATEGORY
) {
211 $cat = Category
::newFromTitle( $title );
212 return $this->specialPage
->msg( 'search-result-category-size' )
213 ->numParams( $cat->getPageCount(), $cat->getSubcatCount(), $cat->getFileCount() )
215 // TODO: This is a bit odd...but requires changing the i18n message to fix
216 } elseif ( $result->getByteSize() !== null ||
$result->getWordCount() > 0 ) {
217 $lang = $this->specialPage
->getLanguage();
218 $bytes = $lang->formatSize( $result->getByteSize() );
219 $words = $result->getWordCount();
221 return $this->specialPage
->msg( 'search-result-size', $bytes )
222 ->numParams( $words )
230 * @param SearchResult $result
231 * @return array Three element array containing the main file html,
232 * a text description of the file, and finally the thumbnail html.
233 * If no thumbnail is available the second and third will be null.
235 protected function generateFileHtml( SearchResult
$result ) {
236 $title = $result->getTitle();
237 if ( $title->getNamespace() !== NS_FILE
) {
238 return [ '', null, null ];
241 if ( $result->isFileMatch() ) {
242 $html = "<span class='searchalttitle'>" .
243 $this->specialPage
->msg( 'search-file-match' )->escaped() .
252 $img = $result->getFile() ?
: MediaWikiServices
::getInstance()->getRepoGroup()
253 ->findFile( $title );
255 $thumb = $img->transform( [ 'width' => 120, 'height' => 120 ] );
257 $descHtml = $this->specialPage
->msg( 'parentheses' )
258 ->rawParams( $img->getShortDesc() )
260 $thumbHtml = $thumb->toHtml( [ 'desc-link' => true ] );
264 return [ $html, $descHtml, $thumbHtml ];
268 * @param string $desc HTML description of result, ex: size in bytes, or empty string
269 * @param string $date HTML representation of last edit date, or empty string
270 * @return string HTML A div combining $desc and $date with a separator in a <div>.
271 * If either is missing only one will be represented. If both are missing an empty
272 * string will be returned.
274 protected function buildMeta( $desc, $date ) {
275 if ( $desc && $date ) {
276 $meta = "{$desc} - {$date}";
285 return "<div class='mw-search-result-data'>{$meta}</div>";