42 private $thumbnailPlaceholderHtml;
54 $this->hookRunner =
new HookRunner( $hookContainer );
55 $this->repoGroup = $repoGroup;
56 $this->thumbnailProvider = $thumbnailProvider;
57 $this->userOptionsManager = $userOptionsManager;
69 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
78 if ( !$this->specialPage->getAuthority()->definitelyCan(
'read', $result->getTitle() ) ) {
79 return Html::rawElement(
'li', [], $link );
85 $date = htmlspecialchars(
86 $this->specialPage->getLanguage()->userTimeAndDate(
87 $result->getTimestamp(),
88 $this->specialPage->getUser()
92 $snippet = $result->getTextSnippet();
94 $snippetWithEllipsis = $snippet . $this->specialPage->msg(
'ellipsis' );
95 $extract = Html::rawElement(
'div', [
'class' =>
'searchresult' ], $snippetWithEllipsis );
100 if ( $result->getTitle() && $result->getTitle()->getNamespace() !==
NS_FILE ) {
112 $terms = $result instanceof \SqlSearchResult ? $result->getTermMatches() : [];
113 if ( !$this->hookRunner->onShowSearchHit( $this->specialPage, $result,
114 $terms, $link, $redirect, $section, $extract, $score,
116 $desc, $date, $related, $html )
123 $joined =
"{$link} {$redirect} {$category} {$section} {$file}";
124 $meta = $this->
buildMeta( $desc, $date );
127 $html = Html::rawElement(
129 [
'class' =>
'mw-search-result-heading' ],
132 $html .= $extract .
' ' . $meta;
136 $gridCells = Html::rawElement(
138 [
'class' =>
'searchResultImage-thumbnail' ],
140 ) . Html::rawElement(
142 [
'class' =>
'searchResultImage-text' ],
145 $html = Html::rawElement(
147 [
'class' =>
'searchResultImage' ],
152 return Html::rawElement(
154 [
'class' => [
'mw-search-result',
'mw-search-result-ns-' . $result->getTitle()->getNamespace() ] ],
170 $snippet = $result->getTitleSnippet();
171 if ( $snippet ===
'' ) {
178 $title = clone $result->getTitle();
181 $attributes = [
'data-serp-pos' => $position ];
182 $this->hookRunner->onShowSearchHitTitle( $title, $snippet, $result,
187 $link = $this->linkRenderer->makeLink(
208 $inner = $title ===
null
210 : $this->linkRenderer->makeLink( $title, $text ?
new HtmlArmor( $text ) : null );
212 return "<span class='searchalttitle'>" .
213 $this->specialPage->msg( $msgKey )->rawParams( $inner )->parse()
222 $title = $result->getRedirectTitle();
223 return $title ===
null
233 $title = $result->getSectionTitle();
234 return $title ===
null
244 $snippet = $result->getCategorySnippet();
255 $title = $result->getTitle();
257 $cat = Category::newFromTitle( $title );
258 return $this->specialPage->msg(
'search-result-category-size' )
259 ->numParams( $cat->getMemberCount(), $cat->getSubcatCount(), $cat->getFileCount() )
262 } elseif ( $result->getByteSize() !==
null || $result->getWordCount() > 0 ) {
263 return $this->specialPage->msg(
'search-result-size' )
264 ->sizeParams( $result->getByteSize() )
265 ->numParams( $result->getWordCount() )
279 $title = $result->getTitle();
281 if ( $title ===
null || !$title->canExist() ) {
282 return [
'',
null, null ];
286 if ( $result->isFileMatch() ) {
287 $html = Html::rawElement(
289 [
'class' =>
'searchalttitle' ],
290 $this->specialPage->msg(
'search-file-match' )->escaped()
294 $allowExtraThumbsFromRequest = $this->specialPage->getRequest()->getVal(
'search-thumbnail-extra-namespaces' );
295 $allowExtraThumbsFromPreference = $this->userOptionsManager->getOption(
296 $this->specialPage->getUser(),
297 'search-thumbnail-extra-namespaces'
299 $allowExtraThumbs = (bool)( $allowExtraThumbsFromRequest ?? $allowExtraThumbsFromPreference );
300 if ( !$allowExtraThumbs && $title->getNamespace() !==
NS_FILE ) {
301 return [ $html,
null, null ];
304 $thumbnail = $this->getThumbnail( $result, self::THUMBNAIL_SIZE );
305 $thumbnailName = $thumbnail ? $thumbnail->getName() :
null;
306 if ( !$thumbnailName ) {
307 return [ $html,
null, $this->generateThumbnailHtml( $result ) ];
310 $img = $this->repoGroup->findFile( $thumbnailName );
312 return [ $html,
null, $this->generateThumbnailHtml( $result ) ];
317 $this->specialPage->msg(
'parentheses' )->rawParams( $img->
getShortDesc() )->escaped(),
318 $this->generateThumbnailHtml( $result, $thumbnail )
330 if ( $title ===
null || !$title->canExist() ) {
334 $thumbnails = $this->thumbnailProvider->getThumbnails( [ $title->getArticleID() => $title ], $size );
336 return $thumbnails[ $title->getArticleID() ] ??
null;
344 private function generateThumbnailHtml(
SearchResult $result, ?SearchResultThumbnail $thumbnail =
null ): ?string {
347 if ( $title ===
null || !$title->canExist() ) {
352 $showThumbnail = in_array( $title->getNamespace(), $namespacesWithThumbnails );
353 if ( !$showThumbnail ) {
357 $thumbnailName = $thumbnail ? $thumbnail->getName() :
null;
358 if ( !$thumbnail || !$thumbnailName ) {
359 return $this->generateThumbnailPlaceholderHtml();
362 $img = $this->repoGroup->findFile( $thumbnailName );
364 return $this->generateThumbnailPlaceholderHtml();
367 $thumb = $this->transformThumbnail( $img, $thumbnail );
369 if ( $title->getNamespace() ===
NS_FILE ) {
371 return $thumb->toHtml( [
374 'alt' => $this->specialPage->msg(
'search-thumbnail-alt' )->params( $title ),
379 return $thumb->toHtml( [
381 'custom-title-link' => $title,
383 'alt' => $this->specialPage->msg(
'search-thumbnail-alt' )->params( $title ),
387 return $this->generateThumbnailPlaceholderHtml();
395 private function transformThumbnail(
File $img, SearchResultThumbnail $thumbnail ) {
396 $optimalThumbnailWidth = $thumbnail->getWidth();
403 $thumbnailMaxDimension = max( $thumbnail->getWidth(), $thumbnail->getHeight() );
404 $thumbnailMinDimension = min( $thumbnail->getWidth(), $thumbnail->getHeight() );
405 $rescaleCoefficient = $thumbnailMinDimension
406 ? $thumbnailMaxDimension / $thumbnailMinDimension : 1;
411 $rescaledWidth = (int)round( $rescaleCoefficient * $thumbnail->getWidth() );
420 if ( $rescaledWidth !== $thumbnail->getWidth() ) {
421 $thumbLimits = $this->specialPage->getConfig()->get( MainConfigNames::ThumbLimits );
422 $largerThumbLimits = array_filter(
424 static function ( $limit ) use ( $rescaledWidth ) {
425 return $limit >= $rescaledWidth;
428 $optimalThumbnailWidth = $largerThumbLimits ? min( $largerThumbLimits ) : max( $thumbLimits );
431 return $img->
transform( [
'width' => $optimalThumbnailWidth ] );
434 private function generateThumbnailPlaceholderHtml(): string {
435 if ( $this->thumbnailPlaceholderHtml ) {
436 return $this->thumbnailPlaceholderHtml;
439 $path = MW_INSTALL_PATH .
'/resources/lib/ooui/themes/wikimediaui/images/icons/imageLayoutFrameless.svg';
440 $this->thumbnailPlaceholderHtml = Html::rawElement(
443 'class' =>
'searchResultImage-thumbnail-placeholder',
444 'aria-hidden' =>
'true',
446 file_get_contents(
$path )
448 return $this->thumbnailPlaceholderHtml;
459 if ( $desc && $date ) {
460 $meta =
"{$desc} - {$date}";
469 return "<div class='mw-search-result-data'>{$meta}</div>";