43 private $thumbnailProvider;
45 private $thumbnailPlaceholderHtml;
47 private $userOptionsManager;
59 $this->hookRunner =
new HookRunner( $hookContainer );
60 $this->repoGroup = $repoGroup;
61 $this->thumbnailProvider = $thumbnailProvider;
62 $this->userOptionsManager = $userOptionsManager;
74 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
83 if ( !$this->specialPage->getAuthority()->definitelyCan(
'read', $result->getTitle() ) ) {
84 return Html::rawElement(
'li', [], $link );
90 $date = htmlspecialchars(
91 $this->specialPage->getLanguage()->userTimeAndDate(
92 $result->getTimestamp(),
93 $this->specialPage->getUser()
97 $snippet = $result->getTextSnippet();
99 $snippetWithEllipsis = $snippet . $this->specialPage->msg(
'ellipsis' );
100 $extract = Html::rawElement(
'div', [
'class' =>
'searchresult' ], $snippetWithEllipsis );
105 if ( $result->getTitle() && $result->getTitle()->getNamespace() !==
NS_FILE ) {
117 $terms = $result instanceof \SqlSearchResult ? $result->getTermMatches() : [];
118 if ( !$this->hookRunner->onShowSearchHit( $this->specialPage, $result,
119 $terms, $link, $redirect, $section, $extract, $score,
121 $desc, $date, $related, $html )
128 $joined =
"{$link} {$redirect} {$category} {$section} {$file}";
129 $meta = $this->
buildMeta( $desc, $date );
132 $html = Html::rawElement(
134 [
'class' =>
'mw-search-result-heading' ],
137 $html .= $extract .
' ' . $meta;
141 $gridCells = Html::rawElement(
143 [
'class' =>
'searchResultImage-thumbnail' ],
145 ) . Html::rawElement(
147 [
'class' =>
'searchResultImage-text' ],
150 $html = Html::rawElement(
152 [
'class' =>
'searchResultImage' ],
157 return Html::rawElement(
159 [
'class' => [
'mw-search-result',
'mw-search-result-ns-' . $result->getTitle()->getNamespace() ] ],
175 $snippet = $result->getTitleSnippet();
176 if ( $snippet ===
'' ) {
183 $title = clone $result->getTitle();
186 $attributes = [
'data-serp-pos' => $position ];
187 $this->hookRunner->onShowSearchHitTitle( $title, $snippet, $result,
192 $link = $this->linkRenderer->makeLink(
213 $inner = $title ===
null
215 : $this->linkRenderer->makeLink( $title, $text ?
new HtmlArmor( $text ) : null );
217 return "<span class='searchalttitle'>" .
218 $this->specialPage->msg( $msgKey )->rawParams( $inner )->parse()
227 $title = $result->getRedirectTitle();
228 return $title ===
null
238 $title = $result->getSectionTitle();
239 return $title ===
null
249 $snippet = $result->getCategorySnippet();
260 $title = $result->getTitle();
262 $cat = Category::newFromTitle( $title );
263 return $this->specialPage->msg(
'search-result-category-size' )
264 ->numParams( $cat->getMemberCount(), $cat->getSubcatCount(), $cat->getFileCount() )
267 } elseif ( $result->getByteSize() !==
null || $result->getWordCount() > 0 ) {
268 return $this->specialPage->msg(
'search-result-size' )
269 ->sizeParams( $result->getByteSize() )
270 ->numParams( $result->getWordCount() )
284 $title = $result->getTitle();
286 if ( $title ===
null || !$title->canExist() ) {
287 return [
'',
null, null ];
291 if ( $result->isFileMatch() ) {
292 $html = Html::rawElement(
294 [
'class' =>
'searchalttitle' ],
295 $this->specialPage->msg(
'search-file-match' )->escaped()
299 $allowExtraThumbsFromRequest = $this->specialPage->getRequest()->getVal(
'search-thumbnail-extra-namespaces' );
300 $allowExtraThumbsFromPreference = $this->userOptionsManager->getOption(
301 $this->specialPage->getUser(),
302 'search-thumbnail-extra-namespaces'
304 $allowExtraThumbs = (bool)( $allowExtraThumbsFromRequest ?? $allowExtraThumbsFromPreference );
305 if ( !$allowExtraThumbs && $title->getNamespace() !==
NS_FILE ) {
306 return [ $html,
null, null ];
309 $thumbnail = $this->getThumbnail( $result, self::THUMBNAIL_SIZE );
310 $thumbnailName = $thumbnail ? $thumbnail->getName() :
null;
311 if ( !$thumbnailName ) {
312 return [ $html,
null, $this->generateThumbnailHtml( $result ) ];
315 $img = $this->repoGroup->findFile( $thumbnailName );
317 return [ $html,
null, $this->generateThumbnailHtml( $result ) ];
322 $this->specialPage->msg(
'parentheses' )->rawParams( $img->
getShortDesc() )->escaped(),
323 $this->generateThumbnailHtml( $result, $thumbnail )
335 if ( $title ===
null || !$title->canExist() ) {
339 $thumbnails = $this->thumbnailProvider->getThumbnails( [ $title->getArticleID() => $title ], $size );
341 return $thumbnails[ $title->getArticleID() ] ??
null;
349 private function generateThumbnailHtml(
SearchResult $result, ?SearchResultThumbnail $thumbnail =
null ): ?string {
352 if ( $title ===
null || !$title->canExist() ) {
357 $showThumbnail = in_array( $title->getNamespace(), $namespacesWithThumbnails );
358 if ( !$showThumbnail ) {
362 $thumbnailName = $thumbnail ? $thumbnail->getName() :
null;
363 if ( !$thumbnail || !$thumbnailName ) {
364 return $this->generateThumbnailPlaceholderHtml();
367 $img = $this->repoGroup->findFile( $thumbnailName );
369 return $this->generateThumbnailPlaceholderHtml();
372 $thumb = $this->transformThumbnail( $img, $thumbnail );
374 if ( $title->getNamespace() ===
NS_FILE ) {
376 return $thumb->toHtml( [
379 'alt' => $this->specialPage->msg(
'search-thumbnail-alt' )->params( $title ),
384 return $thumb->toHtml( [
386 'custom-title-link' => $title,
388 'alt' => $this->specialPage->msg(
'search-thumbnail-alt' )->params( $title ),
392 return $this->generateThumbnailPlaceholderHtml();
400 private function transformThumbnail(
File $img, SearchResultThumbnail $thumbnail ) {
401 $optimalThumbnailWidth = $thumbnail->getWidth();
408 $thumbnailMaxDimension = max( $thumbnail->getWidth(), $thumbnail->getHeight() );
409 $thumbnailMinDimension = min( $thumbnail->getWidth(), $thumbnail->getHeight() );
410 $rescaleCoefficient = $thumbnailMinDimension
411 ? $thumbnailMaxDimension / $thumbnailMinDimension : 1;
416 $rescaledWidth = (int)round( $rescaleCoefficient * $thumbnail->getWidth() );
425 if ( $rescaledWidth !== $thumbnail->getWidth() ) {
426 $thumbLimits = $this->specialPage->getConfig()->get( MainConfigNames::ThumbLimits );
427 $largerThumbLimits = array_filter(
429 static function ( $limit ) use ( $rescaledWidth ) {
430 return $limit >= $rescaledWidth;
433 $optimalThumbnailWidth = $largerThumbLimits ? min( $largerThumbLimits ) : max( $thumbLimits );
436 return $img->
transform( [
'width' => $optimalThumbnailWidth ] );
442 private function generateThumbnailPlaceholderHtml(): string {
443 if ( $this->thumbnailPlaceholderHtml ) {
444 return $this->thumbnailPlaceholderHtml;
447 $path = MW_INSTALL_PATH .
'/resources/lib/ooui/themes/wikimediaui/images/icons/imageLayoutFrameless.svg';
448 $this->thumbnailPlaceholderHtml = Html::rawElement(
451 'class' =>
'searchResultImage-thumbnail-placeholder',
452 'aria-hidden' =>
'true',
454 file_get_contents(
$path )
456 return $this->thumbnailPlaceholderHtml;
467 if ( $desc && $date ) {
468 $meta =
"{$desc} - {$date}";
477 return "<div class='mw-search-result-data'>{$meta}</div>";