MediaWiki REL1_39
FullSearchResultWidget.php
Go to the documentation of this file.
1<?php
2
4
5use Category;
6use Html;
7use HtmlArmor;
12use Sanitizer;
13use SearchResult;
15use Title;
16
26 protected $specialPage;
28 protected $linkRenderer;
30 private $hookRunner;
31
33 HookContainer $hookContainer
34 ) {
35 $this->specialPage = $specialPage;
36 $this->linkRenderer = $linkRenderer;
37 $this->hookRunner = new HookRunner( $hookContainer );
38 }
39
45 public function render( SearchResult $result, $position ) {
46 // If the page doesn't *exist*... our search index is out of date.
47 // The least confusing at this point is to drop the result.
48 // You may get less results, but... on well. :P
49 if ( $result->isBrokenTitle() || $result->isMissingRevision() ) {
50 return '';
51 }
52
53 $link = $this->generateMainLinkHtml( $result, $position );
54 // If page content is not readable, just return ths title.
55 // This is not quite safe, but better than showing excerpts from
56 // non-readable pages. Note that hiding the entry entirely would
57 // screw up paging (really?).
58 if ( !$this->specialPage->getAuthority()->definitelyCan( 'read', $result->getTitle() ) ) {
59 return Html::rawElement( 'li', [], $link );
60 }
61
62 $redirect = $this->generateRedirectHtml( $result );
63 $section = $this->generateSectionHtml( $result );
64 $category = $this->generateCategoryHtml( $result );
65 $date = htmlspecialchars(
66 $this->specialPage->getLanguage()->userTimeAndDate(
67 $result->getTimestamp(),
68 $this->specialPage->getUser()
69 )
70 );
71 list( $file, $desc, $thumb ) = $this->generateFileHtml( $result );
72 $snippet = $result->getTextSnippet();
73 if ( $snippet ) {
74 $extract = Html::rawElement( 'div', [ 'class' => 'searchresult' ], $snippet );
75 } else {
76 $extract = '';
77 }
78
79 if ( $thumb === null ) {
80 // If no thumb, then the description is about size
81 $desc = $this->generateSizeHtml( $result );
82
83 // Let hooks do their own final construction if desired.
84 // FIXME: Not sure why this is only for results without thumbnails,
85 // but keeping it as-is for now to prevent breaking hook consumers.
86 $html = null;
87 $score = '';
88 $related = '';
89 // TODO: remove this instanceof and always pass [], let implementors do the cast if
90 // they want to be SearchDatabase specific
91 $terms = $result instanceof \SqlSearchResult ? $result->getTermMatches() : [];
92 if ( !$this->hookRunner->onShowSearchHit( $this->specialPage, $result,
93 $terms, $link, $redirect, $section, $extract, $score,
94 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
95 $desc, $date, $related, $html )
96 ) {
97 return $html;
98 }
99 }
100
101 // All the pieces have been collected. Now generate the final HTML
102 $joined = "{$link} {$redirect} {$category} {$section} {$file}";
103 $meta = $this->buildMeta( $desc, $date );
104
105 if ( $thumb === null ) {
106 $html = Html::rawElement(
107 'div',
108 [ 'class' => 'mw-search-result-heading' ],
109 $joined
110 );
111 $html .= $extract . ' ' . $meta;
112 } else {
113 $tableCells = Html::rawElement(
114 'td',
115 [ 'style' => 'width: 120px; text-align: center; vertical-align: top' ],
116 $thumb
117 ) . Html::rawElement(
118 'td',
119 [ 'style' => 'vertical-align: top' ],
120 "$joined $extract $meta"
121 );
122 $html = Html::rawElement(
123 'table',
124 [ 'class' => 'searchResultImage' ],
125 Html::rawElement(
126 'tr',
127 [],
128 $tableCells
129 )
130 );
131 }
132
133 return Html::rawElement( 'li', [ 'class' => 'mw-search-result' ], $html );
134 }
135
146 protected function generateMainLinkHtml( SearchResult $result, $position ) {
147 $snippet = $result->getTitleSnippet();
148 if ( $snippet === '' ) {
149 $snippet = null;
150 } else {
151 $snippet = new HtmlArmor( $snippet );
152 }
153
154 // clone to prevent hook from changing the title stored inside $result
155 $title = clone $result->getTitle();
156 $query = [];
157
158 $attributes = [ 'data-serp-pos' => $position ];
159 $this->hookRunner->onShowSearchHitTitle( $title, $snippet, $result,
160 $result instanceof \SqlSearchResult ? $result->getTermMatches() : [],
161 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
162 $this->specialPage, $query, $attributes );
163
164 $link = $this->linkRenderer->makeLink(
165 $title,
166 $snippet,
167 $attributes,
168 $query
169 );
170
171 return $link;
172 }
173
184 protected function generateAltTitleHtml( $msgKey, ?Title $title, $text ) {
185 $inner = $title === null
186 ? $text
187 : $this->linkRenderer->makeLink( $title, $text ? new HtmlArmor( $text ) : null );
188
189 return "<span class='searchalttitle'>" .
190 $this->specialPage->msg( $msgKey )->rawParams( $inner )->parse()
191 . "</span>";
192 }
193
198 protected function generateRedirectHtml( SearchResult $result ) {
199 $title = $result->getRedirectTitle();
200 return $title === null
201 ? ''
202 : $this->generateAltTitleHtml( 'search-redirect', $title, $result->getRedirectSnippet() );
203 }
204
209 protected function generateSectionHtml( SearchResult $result ) {
210 $title = $result->getSectionTitle();
211 return $title === null
212 ? ''
213 : $this->generateAltTitleHtml( 'search-section', $title, $result->getSectionSnippet() );
214 }
215
220 protected function generateCategoryHtml( SearchResult $result ) {
221 $snippet = $result->getCategorySnippet();
222 return $snippet
223 ? $this->generateAltTitleHtml( 'search-category', null, $snippet )
224 : '';
225 }
226
231 protected function generateSizeHtml( SearchResult $result ) {
232 $title = $result->getTitle();
233 if ( $title->getNamespace() === NS_CATEGORY ) {
234 $cat = Category::newFromTitle( $title );
235 return $this->specialPage->msg( 'search-result-category-size' )
236 ->numParams( $cat->getMemberCount(), $cat->getSubcatCount(), $cat->getFileCount() )
237 ->escaped();
238 // TODO: This is a bit odd...but requires changing the i18n message to fix
239 } elseif ( $result->getByteSize() !== null || $result->getWordCount() > 0 ) {
240 return $this->specialPage->msg( 'search-result-size' )
241 ->sizeParams( $result->getByteSize() )
242 ->numParams( $result->getWordCount() )
243 ->escaped();
244 }
245
246 return '';
247 }
248
255 protected function generateFileHtml( SearchResult $result ) {
256 $title = $result->getTitle();
257 if ( $title->getNamespace() !== NS_FILE ) {
258 return [ '', null, null ];
259 }
260
261 if ( $result->isFileMatch() ) {
262 $html = Html::rawElement(
263 'span',
264 [ 'class' => 'searchalttitle' ],
265 $this->specialPage->msg( 'search-file-match' )->escaped()
266 );
267 } else {
268 $html = '';
269 }
270
271 $descHtml = null;
272 $thumbHtml = null;
273
274 $img = $result->getFile() ?: MediaWikiServices::getInstance()->getRepoGroup()
275 ->findFile( $title );
276 if ( $img ) {
277 $thumb = $img->transform( [ 'width' => 120, 'height' => 120 ] );
278 if ( $thumb ) {
279 // File::getShortDesc() is documented to return HTML, but many handlers used to incorrectly
280 // return plain text (T395834), so sanitize it in case the same bug is present in extensions.
281 $unsafeShortDesc = $img->getShortDesc();
282 $shortDesc = Sanitizer::removeSomeTags( $unsafeShortDesc );
283
284 $descHtml = $this->specialPage->msg( 'parentheses' )
285 ->rawParams( $shortDesc )
286 ->escaped();
287 $thumbHtml = $thumb->toHtml( [ 'desc-link' => true ] );
288 }
289 }
290
291 return [ $html, $descHtml, $thumbHtml ];
292 }
293
301 protected function buildMeta( $desc, $date ) {
302 if ( $desc && $date ) {
303 $meta = "{$desc} - {$date}";
304 } elseif ( $desc ) {
305 $meta = $desc;
306 } elseif ( $date ) {
307 $meta = $date;
308 } else {
309 return '';
310 }
311
312 return "<div class='mw-search-result-data'>{$meta}</div>";
313 }
314}
const NS_FILE
Definition Defines.php:70
const NS_CATEGORY
Definition Defines.php:78
Category objects are immutable, strictly speaking.
Definition Category.php:33
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
This class is a collection of static functions that serve two purposes:
Definition Html.php:51
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Class that generates HTML anchor link elements for pages.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Renders a 'full' multi-line search result with metadata.
generateAltTitleHtml( $msgKey, ?Title $title, $text)
Generates an alternate title link, such as (redirect from Foo).
generateMainLinkHtml(SearchResult $result, $position)
Generates HTML for the primary call to action.
__construct(SpecialSearch $specialPage, LinkRenderer $linkRenderer, HookContainer $hookContainer)
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:41
NOTE: this class is being refactored into an abstract base class.
implements Special:Search - Run text & title search and display the output
Represents a title within MediaWiki.
Definition Title.php:49
Renders a single search result to HTML.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42