32 public const THUMBNAIL_SIZE = 90;
42 private $thumbnailProvider;
44 private $thumbnailPlaceholderHtml;
46 private $userOptionsManager;
58 $this->hookRunner =
new HookRunner( $hookContainer );
59 $this->repoGroup = $repoGroup;
60 $this->thumbnailProvider = $thumbnailProvider;
61 $this->userOptionsManager = $userOptionsManager;
73 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
82 if ( !$this->specialPage->getAuthority()->definitelyCan(
'read', $result->getTitle() ) ) {
83 return Html::rawElement(
'li', [], $link );
89 $date = htmlspecialchars(
90 $this->specialPage->getLanguage()->userTimeAndDate(
91 $result->getTimestamp(),
92 $this->specialPage->getUser()
96 $snippet = $result->getTextSnippet();
98 $snippetWithEllipsis = $snippet . $this->specialPage->msg(
'ellipsis' );
99 $extract = Html::rawElement(
'div', [
'class' =>
'searchresult' ], $snippetWithEllipsis );
104 if ( $result->getTitle() && $result->getTitle()->getNamespace() !==
NS_FILE ) {
116 $terms = $result instanceof \SqlSearchResult ? $result->getTermMatches() : [];
117 if ( !$this->hookRunner->onShowSearchHit( $this->specialPage, $result,
118 $terms, $link, $redirect, $section, $extract, $score,
120 $desc, $date, $related, $html )
127 $joined =
"{$link} {$redirect} {$category} {$section} {$file}";
128 $meta = $this->
buildMeta( $desc, $date );
131 $html = Html::rawElement(
133 [
'class' =>
'mw-search-result-heading' ],
136 $html .= $extract .
' ' . $meta;
140 $gridCells = Html::rawElement(
142 [
'class' =>
'searchResultImage-thumbnail' ],
144 ) . Html::rawElement(
146 [
'class' =>
'searchResultImage-text' ],
149 $html = Html::rawElement(
151 [
'class' =>
'searchResultImage' ],
156 return Html::rawElement(
158 [
'class' => [
'mw-search-result',
'mw-search-result-ns-' . $result->getTitle()->getNamespace() ] ],
174 $snippet = $result->getTitleSnippet();
175 if ( $snippet ===
'' ) {
182 $title = clone $result->getTitle();
185 $attributes = [
'data-serp-pos' => $position ];
186 $this->hookRunner->onShowSearchHitTitle( $title, $snippet, $result,
191 $link = $this->linkRenderer->makeLink(
212 $inner = $title ===
null
214 : $this->linkRenderer->makeLink( $title, $text ?
new HtmlArmor( $text ) : null );
216 return "<span class='searchalttitle'>" .
217 $this->specialPage->msg( $msgKey )->rawParams( $inner )->parse()
226 $title = $result->getRedirectTitle();
227 return $title ===
null
237 $title = $result->getSectionTitle();
238 return $title ===
null
248 $snippet = $result->getCategorySnippet();
259 $title = $result->getTitle();
261 $cat = Category::newFromTitle( $title );
262 return $this->specialPage->msg(
'search-result-category-size' )
263 ->numParams( $cat->getMemberCount(), $cat->getSubcatCount(), $cat->getFileCount() )
266 } elseif ( $result->getByteSize() !==
null || $result->getWordCount() > 0 ) {
267 return $this->specialPage->msg(
'search-result-size' )
268 ->sizeParams( $result->getByteSize() )
269 ->numParams( $result->getWordCount() )
283 $title = $result->getTitle();
285 if ( $title ===
null || !$title->canExist() ) {
286 return [
'',
null, null ];
290 if ( $result->isFileMatch() ) {
291 $html = Html::rawElement(
293 [
'class' =>
'searchalttitle' ],
294 $this->specialPage->msg(
'search-file-match' )->escaped()
298 $allowExtraThumbsFromRequest = $this->specialPage->getRequest()->getVal(
'search-thumbnail-extra-namespaces' );
299 $allowExtraThumbsFromPreference = $this->userOptionsManager->getOption(
300 $this->specialPage->getUser(),
301 'search-thumbnail-extra-namespaces'
303 $allowExtraThumbs = (bool)( $allowExtraThumbsFromRequest ?? $allowExtraThumbsFromPreference );
304 if ( !$allowExtraThumbs && $title->getNamespace() !==
NS_FILE ) {
305 return [ $html,
null, null ];
308 $thumbnail = $this->getThumbnail( $result, self::THUMBNAIL_SIZE );
309 $thumbnailName = $thumbnail ? $thumbnail->getName() :
null;
310 if ( !$thumbnailName ) {
311 return [ $html,
null, $this->generateThumbnailHtml( $result ) ];
314 $img = $this->repoGroup->findFile( $thumbnailName );
316 return [ $html,
null, $this->generateThumbnailHtml( $result ) ];
321 $this->specialPage->msg(
'parentheses' )->rawParams( $img->
getShortDesc() )->escaped(),
322 $this->generateThumbnailHtml( $result, $thumbnail )
334 if ( $title ===
null || !$title->canExist() ) {
338 $thumbnails = $this->thumbnailProvider->getThumbnails( [ $title->getArticleID() => $title ], $size );
340 return $thumbnails[ $title->getArticleID() ] ??
null;
348 private function generateThumbnailHtml(
SearchResult $result, SearchResultThumbnail $thumbnail =
null ): ?string {
351 if ( $title ===
null || !$title->canExist() ) {
356 $showThumbnail = in_array( $title->getNamespace(), $namespacesWithThumbnails );
357 if ( !$showThumbnail ) {
361 $thumbnailName = $thumbnail ? $thumbnail->getName() :
null;
362 if ( !$thumbnail || !$thumbnailName ) {
363 return $this->generateThumbnailPlaceholderHtml();
366 $img = $this->repoGroup->findFile( $thumbnailName );
368 return $this->generateThumbnailPlaceholderHtml();
371 $thumb = $this->transformThumbnail( $img, $thumbnail );
373 if ( $title->getNamespace() ===
NS_FILE ) {
375 return $thumb->toHtml( [
378 'alt' => $this->specialPage->msg(
'search-thumbnail-alt' )->params( $title ),
383 return $thumb->toHtml( [
385 'custom-title-link' => $title,
387 'alt' => $this->specialPage->msg(
'search-thumbnail-alt' )->params( $title ),
391 return $this->generateThumbnailPlaceholderHtml();
399 private function transformThumbnail(
File $img, SearchResultThumbnail $thumbnail ) {
400 $optimalThumbnailWidth = $thumbnail->getWidth();
407 $thumbnailMaxDimension = max( $thumbnail->getWidth(), $thumbnail->getHeight() );
408 $thumbnailMinDimension = min( $thumbnail->getWidth(), $thumbnail->getHeight() );
409 $rescaleCoefficient = $thumbnailMinDimension
410 ? $thumbnailMaxDimension / $thumbnailMinDimension : 1;
415 $rescaledWidth = (int)round( $rescaleCoefficient * $thumbnail->getWidth() );
424 if ( $rescaledWidth !== $thumbnail->getWidth() ) {
425 $thumbLimits = $this->specialPage->getConfig()->get( MainConfigNames::ThumbLimits );
426 $largerThumbLimits = array_filter(
428 static function ( $limit ) use ( $rescaledWidth ) {
429 return $limit >= $rescaledWidth;
432 $optimalThumbnailWidth = $largerThumbLimits ? min( $largerThumbLimits ) : max( $thumbLimits );
435 return $img->
transform( [
'width' => $optimalThumbnailWidth ] );
441 private function generateThumbnailPlaceholderHtml(): string {
442 if ( $this->thumbnailPlaceholderHtml ) {
443 return $this->thumbnailPlaceholderHtml;
446 $path = MW_INSTALL_PATH .
'/resources/lib/ooui/themes/wikimediaui/images/icons/imageLayoutFrameless.svg';
447 $this->thumbnailPlaceholderHtml = Html::rawElement(
450 'class' =>
'searchResultImage-thumbnail-placeholder',
451 'aria-hidden' =>
'true',
453 file_get_contents(
$path )
455 return $this->thumbnailPlaceholderHtml;
466 if ( $desc && $date ) {
467 $meta =
"{$desc} - {$date}";
476 return "<div class='mw-search-result-data'>{$meta}</div>";