Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 716 |
|
0.00% |
0 / 21 |
CRAP | |
0.00% |
0 / 1 |
TimelessTemplate | |
0.00% |
0 / 716 |
|
0.00% |
0 / 21 |
18360 | |
0.00% |
0 / 1 |
execute | |
0.00% |
0 / 54 |
|
0.00% |
0 / 1 |
12 | |||
getContentBlock | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
2 | |||
getPortlet | |
0.00% |
0 / 79 |
|
0.00% |
0 / 1 |
110 | |||
mergeClasses | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getFooterBlock | |
0.00% |
0 / 69 |
|
0.00% |
0 / 1 |
110 | |||
getSidebarChunk | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
getLogo | |
0.00% |
0 / 59 |
|
0.00% |
0 / 1 |
182 | |||
getSearch | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
2 | |||
getMainNavigation | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
getHeaderHack | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 | |||
getPageToolSidebar | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
6 | |||
getUserLinks | |
0.00% |
0 / 52 |
|
0.00% |
0 / 1 |
182 | |||
getSiteNotices | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getContentSub | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getAfterContent | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
getPageTools | |
0.00% |
0 / 106 |
|
0.00% |
0 / 1 |
462 | |||
getCategories | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
72 | |||
getCatList | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
getVariants | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
getInterwikiLinks | |
0.00% |
0 / 32 |
|
0.00% |
0 / 1 |
56 | |||
getLogoImage | |
0.00% |
0 / 58 |
|
0.00% |
0 / 1 |
600 |
1 | <?php |
2 | /** |
3 | * BaseTemplate class for the Timeless skin |
4 | * |
5 | * @ingroup Skins |
6 | */ |
7 | |
8 | namespace MediaWiki\Skin\Timeless; |
9 | |
10 | use BaseTemplate; |
11 | use File; |
12 | use MediaWiki\Html\Html; |
13 | use MediaWiki\Linker\Linker; |
14 | use MediaWiki\MediaWikiServices; |
15 | use MediaWiki\Parser\Sanitizer; |
16 | use MediaWiki\ResourceLoader\SkinModule; |
17 | use MediaWiki\SpecialPage\SpecialPage; |
18 | |
19 | class TimelessTemplate extends BaseTemplate { |
20 | |
21 | /** @var array */ |
22 | protected $pileOfTools; |
23 | |
24 | /** @var (array|false)[] */ |
25 | protected $sidebar; |
26 | |
27 | /** @var array|null */ |
28 | protected $otherProjects; |
29 | |
30 | /** @var array|null */ |
31 | protected $collectionPortlet; |
32 | |
33 | /** @var array[] */ |
34 | protected $languages; |
35 | |
36 | /** @var string */ |
37 | protected $afterLangPortlet; |
38 | |
39 | /** |
40 | * Outputs the entire contents of the page |
41 | */ |
42 | public function execute() { |
43 | $this->sidebar = $this->data['sidebar']; |
44 | $this->languages = $this->sidebar['LANGUAGES']; |
45 | |
46 | // WikiBase sidebar thing |
47 | if ( isset( $this->sidebar['wikibase-otherprojects'] ) ) { |
48 | $this->otherProjects = $this->sidebar['wikibase-otherprojects']; |
49 | unset( $this->sidebar['wikibase-otherprojects'] ); |
50 | } |
51 | // Collection sidebar thing |
52 | if ( isset( $this->sidebar['coll-print_export'] ) ) { |
53 | $this->collectionPortlet = $this->sidebar['coll-print_export']; |
54 | unset( $this->sidebar['coll-print_export'] ); |
55 | } |
56 | |
57 | $this->pileOfTools = $this->getPageTools(); |
58 | $userLinks = $this->getUserLinks(); |
59 | |
60 | $html = Html::openElement( 'div', [ 'id' => 'mw-wrapper', 'class' => $userLinks['class'] ] ); |
61 | |
62 | $html .= Html::rawElement( 'div', [ 'id' => 'mw-header-container', 'class' => 'ts-container' ], |
63 | Html::rawElement( 'div', [ 'id' => 'mw-header', 'class' => 'ts-inner' ], |
64 | $userLinks['html'] . |
65 | $this->getLogo( 'p-logo-text', 'text' ) . |
66 | $this->getSearch() |
67 | ) . |
68 | $this->getClear() |
69 | ); |
70 | $html .= $this->getHeaderHack(); |
71 | |
72 | // For mobile |
73 | $html .= Html::element( 'div', [ 'id' => 'menus-cover' ] ); |
74 | |
75 | $html .= Html::rawElement( 'div', [ 'id' => 'mw-content-container', 'class' => 'ts-container' ], |
76 | Html::rawElement( 'div', [ 'id' => 'mw-content-block', 'class' => 'ts-inner' ], |
77 | Html::rawElement( 'div', [ 'id' => 'mw-content-wrapper' ], |
78 | $this->getContentBlock() . |
79 | $this->getAfterContent() |
80 | ) . |
81 | Html::rawElement( 'div', [ 'id' => 'mw-site-navigation' ], |
82 | $this->getLogo( 'p-logo', 'image' ) . |
83 | $this->getMainNavigation() . |
84 | $this->getSidebarChunk( |
85 | 'site-tools', |
86 | 'timeless-sitetools', |
87 | $this->getPortlet( |
88 | 'tb', |
89 | $this->pileOfTools['general'], |
90 | 'timeless-sitetools' |
91 | ) |
92 | ) |
93 | ) . |
94 | Html::rawElement( 'div', [ 'id' => 'mw-related-navigation' ], |
95 | $this->getPageToolSidebar() . |
96 | $this->getInterwikiLinks() . |
97 | $this->getCategories() |
98 | ) . |
99 | $this->getClear() |
100 | ) |
101 | ); |
102 | |
103 | $html .= Html::rawElement( 'div', |
104 | [ 'id' => 'mw-footer-container', 'class' => 'mw-footer-container ts-container' ], |
105 | $this->getFooterBlock( [ 'class' => [ 'mw-footer', 'ts-inner' ], 'id' => 'mw-footer' ] ) |
106 | ); |
107 | |
108 | $html .= Html::closeElement( 'div' ); |
109 | |
110 | // The unholy echo |
111 | echo $html; |
112 | } |
113 | |
114 | /** |
115 | * Generate the page content block |
116 | * Broken out here due to the excessive indenting, or stuff. |
117 | * |
118 | * @return string html |
119 | */ |
120 | protected function getContentBlock() { |
121 | $templateData = $this->getSkin()->getTemplateData(); |
122 | $html = Html::rawElement( |
123 | 'div', |
124 | [ 'id' => 'content', 'class' => 'mw-body', 'role' => 'main' ], |
125 | $this->getSiteNotices() . |
126 | $this->getIndicators() . |
127 | $templateData[ 'html-title-heading' ] . |
128 | Html::rawElement( 'div', [ 'id' => 'bodyContentOuter' ], |
129 | Html::rawElement( 'div', [ 'id' => 'siteSub' ], $this->getMsg( 'tagline' )->parse() ) . |
130 | Html::rawElement( 'div', [ 'id' => 'mw-page-header-links' ], |
131 | $this->getPortlet( |
132 | 'namespaces', |
133 | $this->pileOfTools['namespaces'], |
134 | 'timeless-namespaces', |
135 | [ 'extra-classes' => 'tools-inline' ] |
136 | ) . |
137 | $this->getPortlet( |
138 | 'more', |
139 | $this->pileOfTools['more'], |
140 | 'timeless-more', |
141 | [ 'extra-classes' => 'tools-inline' ] |
142 | ) . |
143 | $this->getVariants() . |
144 | $this->getPortlet( |
145 | 'views', |
146 | $this->pileOfTools['page-primary'], |
147 | 'timeless-pagetools', |
148 | [ 'extra-classes' => 'tools-inline' ] |
149 | ) |
150 | ) . |
151 | $this->getClear() . |
152 | Html::rawElement( 'div', [ 'id' => 'bodyContent' ], |
153 | $this->getContentSub() . |
154 | $this->get( 'bodytext' ) . |
155 | $this->getClear() |
156 | ) |
157 | ) |
158 | ); |
159 | |
160 | return Html::rawElement( 'div', [ 'id' => 'mw-content' ], $html ); |
161 | } |
162 | |
163 | /** |
164 | * Generates a block of navigation links with a header |
165 | * This is some random fork of some random fork of what was supposed to be in core. Latest |
166 | * version copied out of MonoBook, probably. (20190719) |
167 | * |
168 | * @param string $name |
169 | * @param array|string $content array of links for use with makeListItem, or a block of text |
170 | * Expected array format: |
171 | * [ |
172 | * $name => [ |
173 | * 'links' => [ '0' => |
174 | * [ |
175 | * 'href' => ..., |
176 | * 'single-id' => ..., |
177 | * 'text' => ... |
178 | * ] |
179 | * ], |
180 | * 'id' => ..., |
181 | * 'active' => ... |
182 | * ], |
183 | * ... |
184 | * ] |
185 | * @param null|string|array|bool $msg |
186 | * @param array $setOptions miscellaneous overrides, see below |
187 | * |
188 | * @return string html |
189 | * @suppress PhanTypeMismatchArgumentNullable |
190 | */ |
191 | protected function getPortlet( $name, $content, $msg = null, $setOptions = [] ) { |
192 | $skin = $this->getSkin(); |
193 | // random stuff to override with any provided options |
194 | $options = array_merge( [ |
195 | 'role' => 'navigation', |
196 | // extra classes/ids |
197 | 'id' => 'p-' . $name, |
198 | 'class' => [ 'mw-portlet', 'emptyPortlet' => !$content ], |
199 | 'extra-classes' => '', |
200 | 'body-id' => null, |
201 | 'body-class' => 'mw-portlet-body', |
202 | 'body-extra-classes' => '', |
203 | // wrapper for individual list items |
204 | 'text-wrapper' => [ 'tag' => 'span' ], |
205 | // option to stick arbitrary stuff at the beginning of the ul |
206 | 'list-prepend' => '' |
207 | ], $setOptions ); |
208 | |
209 | // Handle the different $msg possibilities |
210 | if ( $msg === null ) { |
211 | $msg = $name; |
212 | $msgParams = []; |
213 | } elseif ( is_array( $msg ) ) { |
214 | $msgString = array_shift( $msg ); |
215 | $msgParams = $msg; |
216 | $msg = $msgString; |
217 | } else { |
218 | $msgParams = []; |
219 | } |
220 | $msgObj = $this->getMsg( $msg, $msgParams ); |
221 | if ( $msgObj->exists() ) { |
222 | $msgString = $msgObj->parse(); |
223 | } else { |
224 | $msgString = htmlspecialchars( $msg ); |
225 | } |
226 | |
227 | $labelId = Sanitizer::escapeIdForAttribute( "p-$name-label" ); |
228 | |
229 | if ( is_array( $content ) ) { |
230 | $contentText = Html::openElement( 'ul', |
231 | [ 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ] |
232 | ); |
233 | $contentText .= $options['list-prepend']; |
234 | foreach ( $content as $key => $item ) { |
235 | if ( is_array( $options['text-wrapper'] ) ) { |
236 | $contentText .= $skin->makeListItem( |
237 | $key, |
238 | $item, |
239 | [ 'text-wrapper' => $options['text-wrapper'] ] |
240 | ); |
241 | } else { |
242 | $contentText .= $skin->makeListItem( |
243 | $key, |
244 | $item |
245 | ); |
246 | } |
247 | } |
248 | $contentText .= Html::closeElement( 'ul' ); |
249 | } else { |
250 | $contentText = $content; |
251 | } |
252 | |
253 | $divOptions = [ |
254 | 'role' => $options['role'], |
255 | 'class' => $this->mergeClasses( $options['class'], $options['extra-classes'] ), |
256 | 'id' => Sanitizer::escapeIdForAttribute( $options['id'] ), |
257 | 'title' => Linker::titleAttrib( $options['id'] ), |
258 | 'aria-labelledby' => $labelId |
259 | ]; |
260 | $labelOptions = [ |
261 | 'id' => $labelId, |
262 | 'lang' => $this->get( 'userlang' ), |
263 | 'dir' => $this->get( 'dir' ) |
264 | ]; |
265 | |
266 | $bodyDivOptions = [ |
267 | 'class' => $this->mergeClasses( $options['body-class'], $options['body-extra-classes'] ) |
268 | ]; |
269 | if ( is_string( $options['body-id'] ) ) { |
270 | $bodyDivOptions['id'] = $options['body-id']; |
271 | } |
272 | |
273 | $afterPortlet = ''; |
274 | $content = $this->getSkin()->getAfterPortlet( $name ); |
275 | if ( $content !== '' ) { |
276 | $afterPortlet = Html::rawElement( |
277 | 'div', |
278 | [ 'class' => [ 'after-portlet', 'after-portlet-' . $name ] ], |
279 | $content |
280 | ); |
281 | } |
282 | |
283 | if ( $name === 'lang' ) { |
284 | $this->afterLangPortlet = $afterPortlet; |
285 | } |
286 | |
287 | $html = Html::rawElement( 'div', $divOptions, |
288 | Html::rawElement( 'h3', $labelOptions, $msgString ) . |
289 | Html::rawElement( 'div', $bodyDivOptions, |
290 | $contentText . |
291 | $afterPortlet |
292 | ) |
293 | ); |
294 | |
295 | return $html; |
296 | } |
297 | |
298 | /** |
299 | * Helper function for getPortlet |
300 | * |
301 | * Merge all provided css classes into a single array |
302 | * Account for possible different input methods matching what Html::element stuff takes |
303 | * |
304 | * @param string|array $class base portlet/body class |
305 | * @param string|array $extraClasses any extra classes to also include |
306 | * |
307 | * @return array all classes to apply |
308 | */ |
309 | protected function mergeClasses( $class, $extraClasses ) { |
310 | if ( !is_array( $class ) ) { |
311 | $class = [ $class ]; |
312 | } |
313 | if ( !is_array( $extraClasses ) ) { |
314 | $extraClasses = [ $extraClasses ]; |
315 | } |
316 | |
317 | return array_merge( $class, $extraClasses ); |
318 | } |
319 | |
320 | /** |
321 | * Better renderer for getFooterIcons and getFooterLinks, based on Vector's HTML output |
322 | * (as of 2016) |
323 | * |
324 | * @param array $setOptions Miscellaneous other options |
325 | * * 'id' for footer id |
326 | * * 'class' for footer class |
327 | * * 'order' to determine whether icons or links appear first: 'iconsfirst' or links, though in |
328 | * practice we currently only check if it is or isn't 'iconsfirst' |
329 | * * 'link-prefix' to set the prefix for all link and block ids; most skins use 'f' or 'footer', |
330 | * as in id='f-whatever' vs id='footer-whatever' |
331 | * * 'link-style' to pass to getFooterLinks: "flat" to disable categorisation of links in a |
332 | * nested array |
333 | * |
334 | * @return string html |
335 | */ |
336 | protected function getFooterBlock( $setOptions = [] ) { |
337 | // Set options and fill in defaults |
338 | $options = $setOptions + [ |
339 | 'id' => 'footer', |
340 | 'class' => 'mw-footer', |
341 | 'order' => 'iconsfirst', |
342 | 'link-prefix' => 'footer', |
343 | 'link-style' => null |
344 | ]; |
345 | |
346 | // phpcs:ignore Generic.Files.LineLength.TooLong |
347 | '@phan-var array{id:string,class:string,order:string,link-prefix:string,link-style:?string} $options'; |
348 | $validFooterIcons = $this->get( 'footericons' ); |
349 | $validFooterLinks = $this->getFooterLinks( $options['link-style'] ); |
350 | |
351 | $html = ''; |
352 | |
353 | $html .= Html::openElement( 'div', [ |
354 | 'id' => $options['id'], |
355 | 'class' => $options['class'], |
356 | 'role' => 'contentinfo', |
357 | 'lang' => $this->get( 'userlang' ), |
358 | 'dir' => $this->get( 'dir' ) |
359 | ] ); |
360 | |
361 | $iconsHTML = ''; |
362 | if ( count( $validFooterIcons ) > 0 ) { |
363 | $iconsHTML .= Html::openElement( 'ul', [ 'id' => "{$options['link-prefix']}-icons" ] ); |
364 | foreach ( $validFooterIcons as $blockName => $footerIcons ) { |
365 | $iconsHTML .= Html::openElement( 'li', [ |
366 | 'id' => Sanitizer::escapeIdForAttribute( |
367 | "{$options['link-prefix']}-{$blockName}ico" |
368 | ), |
369 | 'class' => 'footer-icons' |
370 | ] ); |
371 | foreach ( $footerIcons as $icon ) { |
372 | $iconsHTML .= $this->getSkin()->makeFooterIcon( $icon ); |
373 | } |
374 | $iconsHTML .= Html::closeElement( 'li' ); |
375 | } |
376 | $iconsHTML .= Html::closeElement( 'ul' ); |
377 | } |
378 | |
379 | $linksHTML = ''; |
380 | if ( count( $validFooterLinks ) > 0 ) { |
381 | if ( $options['link-style'] === 'flat' ) { |
382 | $linksHTML .= Html::openElement( 'ul', [ |
383 | 'id' => "{$options['link-prefix']}-list", |
384 | 'class' => 'footer-places' |
385 | ] ); |
386 | foreach ( $validFooterLinks as $link ) { |
387 | $linksHTML .= Html::rawElement( |
388 | 'li', |
389 | [ 'id' => Sanitizer::escapeIdForAttribute( $link ) ], |
390 | $this->get( $link ) |
391 | ); |
392 | } |
393 | $linksHTML .= Html::closeElement( 'ul' ); |
394 | } else { |
395 | $linksHTML .= Html::openElement( 'div', [ 'id' => "{$options['link-prefix']}-list" ] ); |
396 | foreach ( $validFooterLinks as $category => $links ) { |
397 | $linksHTML .= Html::openElement( 'ul', |
398 | [ 'id' => Sanitizer::escapeIdForAttribute( |
399 | "{$options['link-prefix']}-{$category}" |
400 | ) ] |
401 | ); |
402 | foreach ( $links as $link ) { |
403 | $linksHTML .= Html::rawElement( |
404 | 'li', |
405 | [ 'id' => Sanitizer::escapeIdForAttribute( |
406 | "{$options['link-prefix']}-{$category}-{$link}" |
407 | ) ], |
408 | $this->get( $link ) |
409 | ); |
410 | } |
411 | $linksHTML .= Html::closeElement( 'ul' ); |
412 | } |
413 | $linksHTML .= Html::closeElement( 'div' ); |
414 | } |
415 | } |
416 | |
417 | if ( $options['order'] === 'iconsfirst' ) { |
418 | $html .= $iconsHTML . $linksHTML; |
419 | } else { |
420 | $html .= $linksHTML . $iconsHTML; |
421 | } |
422 | |
423 | $html .= $this->getClear() . Html::closeElement( 'div' ); |
424 | |
425 | return $html; |
426 | } |
427 | |
428 | /** |
429 | * Sidebar chunk containing one or more portlets |
430 | * |
431 | * @param string $id |
432 | * @param string $headerMessage |
433 | * @param string $content |
434 | * @param array $classes |
435 | * |
436 | * @return string html |
437 | */ |
438 | protected function getSidebarChunk( $id, $headerMessage, $content, $classes = [] ) { |
439 | $html = ''; |
440 | |
441 | $html .= Html::rawElement( |
442 | 'div', |
443 | [ |
444 | 'id' => Sanitizer::escapeIdForAttribute( $id ), |
445 | 'class' => array_merge( [ 'sidebar-chunk' ], $classes ) |
446 | ], |
447 | Html::rawElement( 'h2', [], |
448 | Html::element( 'span', [], |
449 | $this->getMsg( $headerMessage )->text() |
450 | ) |
451 | ) . |
452 | Html::rawElement( 'div', [ 'class' => 'sidebar-inner' ], $content ) |
453 | ); |
454 | |
455 | return $html; |
456 | } |
457 | |
458 | /** |
459 | * The logo and (optionally) site title |
460 | * |
461 | * @param string $id |
462 | * @param string $part whether it's only image, only text, or both |
463 | * |
464 | * @return string html |
465 | */ |
466 | protected function getLogo( $id = 'p-logo', $part = 'both' ) { |
467 | $html = ''; |
468 | $config = $this->getSkin()->getContext()->getConfig(); |
469 | |
470 | $html .= Html::openElement( |
471 | 'div', |
472 | [ |
473 | 'id' => Sanitizer::escapeIdForAttribute( $id ), |
474 | 'class' => 'mw-portlet', |
475 | 'role' => 'banner' |
476 | ] |
477 | ); |
478 | $logos = SkinModule::getAvailableLogos( $config, $this->getSkin()->getLanguage()->getCode() ); |
479 | if ( $part !== 'image' ) { |
480 | $wordmarkImage = $this->getLogoImage( $config->get( 'TimelessWordmark' ), true ); |
481 | if ( !$wordmarkImage && isset( $logos['wordmark'] ) ) { |
482 | $wordmarkData = $logos['wordmark']; |
483 | $wordmarkImage = Html::element( 'img', [ |
484 | 'src' => $wordmarkData['src'], |
485 | 'height' => $wordmarkData['height'] ?? null, |
486 | 'width' => $wordmarkData['width'] ?? null, |
487 | ] ); |
488 | } |
489 | |
490 | $titleClass = ''; |
491 | $siteTitle = ''; |
492 | if ( !$wordmarkImage ) { |
493 | $langConv = MediaWikiServices::getInstance()->getLanguageConverterFactory() |
494 | ->getLanguageConverter( $this->getSkin()->getLanguage() ); |
495 | if ( $langConv->hasVariants() ) { |
496 | $siteTitle = $langConv->convert( $this->getMsg( 'timeless-sitetitle' )->escaped() ); |
497 | } else { |
498 | $siteTitle = $this->getMsg( 'timeless-sitetitle' )->escaped(); |
499 | } |
500 | // width is 11em; 13 characters will probably fit? |
501 | if ( mb_strlen( $siteTitle ) > 13 ) { |
502 | $titleClass = 'long'; |
503 | } |
504 | } else { |
505 | $titleClass = 'wordmark'; |
506 | } |
507 | $html .= Html::rawElement( 'a', [ |
508 | 'id' => 'p-banner', |
509 | 'class' => [ 'mw-wiki-title', $titleClass ], |
510 | 'href' => $this->data['nav_urls']['mainpage']['href'] |
511 | ], |
512 | $wordmarkImage ?: $siteTitle |
513 | ); |
514 | |
515 | } |
516 | if ( $part !== 'text' ) { |
517 | $logoImage = $this->getLogoImage( $config->get( 'TimelessLogo' ) ); |
518 | if ( $logoImage === false ) { |
519 | $logoSrc = $logos['svg'] ?? $logos['icon'] ?? ''; |
520 | if ( $logoSrc !== '' ) { |
521 | $logoImage = Html::element( 'img', [ |
522 | 'src' => $logoSrc, |
523 | ] ); |
524 | } |
525 | } |
526 | |
527 | $html .= Html::rawElement( |
528 | 'a', |
529 | array_merge( |
530 | [ |
531 | 'class' => [ 'mw-wiki-logo', !$logoImage ? 'fallback' : 'timeless-logo' ], |
532 | 'href' => $this->data['nav_urls']['mainpage']['href'] |
533 | ], |
534 | Linker::tooltipAndAccesskeyAttribs( 'p-logo' ) |
535 | ), |
536 | $logoImage ?: '' |
537 | ); |
538 | } |
539 | $html .= Html::closeElement( 'div' ); |
540 | |
541 | return $html; |
542 | } |
543 | |
544 | /** |
545 | * The search box at the top |
546 | * |
547 | * @return string html |
548 | */ |
549 | protected function getSearch() { |
550 | $skin = $this->getSkin(); |
551 | $html = Html::openElement( 'div', [ 'class' => 'mw-portlet', 'id' => 'p-search' ] ); |
552 | |
553 | $html .= Html::rawElement( |
554 | 'h3', |
555 | [ 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ], |
556 | Html::rawElement( 'label', [ 'for' => 'searchInput' ], $this->getMsg( 'search' )->escaped() ) |
557 | ); |
558 | |
559 | $html .= Html::rawElement( 'form', [ 'action' => $this->get( 'wgScript' ), 'id' => 'searchform' ], |
560 | Html::rawElement( 'div', [ 'id' => 'simpleSearch' ], |
561 | Html::rawElement( 'div', [ 'id' => 'searchInput-container' ], |
562 | $skin->makeSearchInput( [ |
563 | 'id' => 'searchInput' |
564 | ] ) |
565 | ) . |
566 | Html::hidden( 'title', $this->get( 'searchtitle' ) ) . |
567 | $skin->makeSearchButton( |
568 | 'fulltext', |
569 | [ 'id' => 'mw-searchButton', 'class' => 'searchButton mw-fallbackSearchButton' ] |
570 | ) . |
571 | $skin->makeSearchButton( |
572 | 'go', |
573 | [ 'id' => 'searchButton', 'class' => 'searchButton' ] |
574 | ) |
575 | ) |
576 | ); |
577 | |
578 | return $html . Html::closeElement( 'div' ); |
579 | } |
580 | |
581 | /** |
582 | * Left sidebar navigation, usually |
583 | * |
584 | * @return string html |
585 | */ |
586 | protected function getMainNavigation() { |
587 | $html = ''; |
588 | |
589 | // Already hardcoded into header |
590 | $this->sidebar['SEARCH'] = false; |
591 | // Parsed as part of pageTools |
592 | $this->sidebar['TOOLBOX'] = false; |
593 | // Forcibly removed to separate chunk |
594 | $this->sidebar['LANGUAGES'] = false; |
595 | foreach ( $this->sidebar as $name => $content ) { |
596 | if ( $content === false ) { |
597 | continue; |
598 | } |
599 | // Numeric strings gets an integer when set as key, cast back - T73639 |
600 | $name = (string)$name; |
601 | $html .= $this->getPortlet( $name, $content ); |
602 | } |
603 | |
604 | return $this->getSidebarChunk( 'site-navigation', 'navigation', $html ); |
605 | } |
606 | |
607 | /** |
608 | * The colour bars |
609 | * Split this out so we don't have to look at it/can easily kill it later |
610 | * |
611 | * @return string html |
612 | */ |
613 | protected function getHeaderHack() { |
614 | $html = ''; |
615 | |
616 | // These are almost exactly the same and this is stupid. |
617 | $html .= Html::rawElement( 'div', [ 'id' => 'mw-header-hack', 'class' => 'color-bar' ], |
618 | Html::rawElement( 'div', [ 'class' => 'color-middle-container' ], |
619 | Html::element( 'div', [ 'class' => 'color-middle' ] ) |
620 | ) . |
621 | Html::element( 'div', [ 'class' => 'color-left' ] ) . |
622 | Html::element( 'div', [ 'class' => 'color-right' ] ) |
623 | ); |
624 | $html .= Html::rawElement( 'div', [ 'id' => 'mw-header-nav-hack' ], |
625 | Html::rawElement( 'div', [ 'class' => 'color-bar' ], |
626 | Html::rawElement( 'div', [ 'class' => 'color-middle-container' ], |
627 | Html::element( 'div', [ 'class' => 'color-middle' ] ) |
628 | ) . |
629 | Html::element( 'div', [ 'class' => 'color-left' ] ) . |
630 | Html::element( 'div', [ 'class' => 'color-right' ] ) |
631 | ) |
632 | ); |
633 | |
634 | return $html; |
635 | } |
636 | |
637 | /** |
638 | * Page tools in sidebar |
639 | * |
640 | * @return string html |
641 | */ |
642 | protected function getPageToolSidebar() { |
643 | $pageTools = $this->getPortlet( |
644 | 'cactions', |
645 | $this->pileOfTools['page-secondary'], |
646 | 'timeless-pageactions' |
647 | ); |
648 | $pageTools .= $this->getPortlet( |
649 | 'userpagetools', |
650 | $this->pileOfTools['user'], |
651 | 'timeless-userpagetools' |
652 | ); |
653 | $pageTools .= $this->getPortlet( |
654 | 'pagemisc', |
655 | $this->pileOfTools['page-tertiary'], |
656 | 'timeless-pagemisc' |
657 | ); |
658 | if ( $this->collectionPortlet !== null ) { |
659 | $pageTools .= $this->getPortlet( |
660 | 'coll-print_export', |
661 | $this->collectionPortlet |
662 | ); |
663 | } |
664 | |
665 | return $this->getSidebarChunk( 'page-tools', 'timeless-pageactions', $pageTools ); |
666 | } |
667 | |
668 | /** |
669 | * Personal/user links portlet for header |
670 | * |
671 | * @return array [ html, class ], where class is an extra class to apply to surrounding objects |
672 | * (for width adjustments) |
673 | */ |
674 | protected function getUserLinks() { |
675 | $skin = $this->getSkin(); |
676 | $user = $skin->getUser(); |
677 | $personalTools = $skin->getPersonalToolsForMakeListItem( $this->get( 'personal_urls' ) ); |
678 | // Preserve standard username label to allow customisation (T215822) |
679 | $userName = $personalTools['userpage']['links'][0]['text'] ?? $user->getName(); |
680 | |
681 | $extraTools = []; |
682 | |
683 | // Remove anon placeholder |
684 | if ( isset( $personalTools['anonuserpage'] ) ) { |
685 | unset( $personalTools['anonuserpage'] ); |
686 | } |
687 | // Remove temp user placeholder, as we display the user name in the dropdown header instead. |
688 | // Removing the use of .mw-userpage-tmp class also prevents the anchored popup from appearing, |
689 | // which is good, because there's no reasonable place to put it. |
690 | if ( |
691 | isset( $personalTools['userpage'] ) && |
692 | in_array( 'mw-userpage-tmp', $personalTools['userpage']['links'][0]['class'] ?? [] ) |
693 | ) { |
694 | unset( $personalTools['userpage'] ); |
695 | } |
696 | |
697 | // Remove Echo badges |
698 | if ( isset( $personalTools['notifications-alert'] ) ) { |
699 | $extraTools['notifications-alert'] = $personalTools['notifications-alert']; |
700 | unset( $personalTools['notifications-alert'] ); |
701 | } |
702 | if ( isset( $personalTools['notifications-notice'] ) ) { |
703 | $extraTools['notifications-notice'] = $personalTools['notifications-notice']; |
704 | unset( $personalTools['notifications-notice'] ); |
705 | } |
706 | $class = $extraTools === [] ? '' : 'extension-icons'; |
707 | |
708 | // Re-label some messages |
709 | if ( isset( $personalTools['userpage'] ) ) { |
710 | $personalTools['userpage']['links'][0]['text'] = $this->getMsg( 'timeless-userpage' )->text(); |
711 | } |
712 | if ( isset( $personalTools['mytalk'] ) ) { |
713 | $personalTools['mytalk']['links'][0]['text'] = $this->getMsg( 'timeless-talkpage' )->text(); |
714 | } |
715 | |
716 | // Labels |
717 | if ( $user->isNamed() ) { |
718 | $dropdownHeader = $userName; |
719 | $headerMsg = [ 'timeless-loggedinas', $userName ]; |
720 | } elseif ( $user->isTemp() ) { |
721 | $dropdownHeader = $user->getName(); |
722 | $headerMsg = 'timeless-notloggedin'; |
723 | } else { |
724 | $dropdownHeader = $this->getMsg( 'timeless-anonymous' )->text(); |
725 | $headerMsg = 'timeless-notloggedin'; |
726 | } |
727 | $html = Html::openElement( 'div', [ 'id' => 'user-tools' ] ); |
728 | |
729 | $html .= Html::rawElement( 'div', [ 'id' => 'personal' ], |
730 | Html::rawElement( 'h2', [], |
731 | Html::element( 'span', [], $dropdownHeader ) |
732 | ) . |
733 | Html::rawElement( 'div', [ 'id' => 'personal-inner', 'class' => 'dropdown' ], |
734 | $this->getPortlet( 'personal', $personalTools, $headerMsg ) |
735 | ) |
736 | ); |
737 | |
738 | // Extra icon stuff (echo etc) |
739 | if ( $extraTools !== [] ) { |
740 | $iconList = ''; |
741 | foreach ( $extraTools as $key => $item ) { |
742 | $iconList .= $skin->makeListItem( $key, $item ); |
743 | } |
744 | |
745 | $html .= Html::rawElement( |
746 | 'div', |
747 | [ 'id' => 'personal-extra', 'class' => 'p-body' ], |
748 | Html::rawElement( 'ul', [], $iconList ) |
749 | ); |
750 | } |
751 | |
752 | $html .= Html::closeElement( 'div' ); |
753 | |
754 | return [ |
755 | 'html' => $html, |
756 | 'class' => $class |
757 | ]; |
758 | } |
759 | |
760 | /** |
761 | * Notices that may appear above the firstHeading |
762 | * |
763 | * @return string html |
764 | */ |
765 | protected function getSiteNotices() { |
766 | $html = ''; |
767 | |
768 | if ( $this->data['sitenotice'] ) { |
769 | $html .= Html::rawElement( 'div', [ 'id' => 'siteNotice' ], $this->get( 'sitenotice' ) ); |
770 | } |
771 | if ( $this->data['newtalk'] ) { |
772 | $html .= Html::rawElement( 'div', [ 'class' => 'usermessage' ], $this->get( 'newtalk' ) ); |
773 | } |
774 | |
775 | return $html; |
776 | } |
777 | |
778 | /** |
779 | * Links and information that may appear below the firstHeading |
780 | * |
781 | * @return string html |
782 | */ |
783 | protected function getContentSub() { |
784 | $html = Html::openElement( 'div', [ 'id' => 'contentSub' ] ); |
785 | if ( $this->data['subtitle'] ) { |
786 | $html .= $this->get( 'subtitle' ); |
787 | } |
788 | if ( $this->data['undelete'] ) { |
789 | $html .= $this->get( 'undelete' ); |
790 | } |
791 | return $html . Html::closeElement( 'div' ); |
792 | } |
793 | |
794 | /** |
795 | * The data after content, catlinks, and potential other stuff that may appear within |
796 | * the content block but after the main content |
797 | * |
798 | * @return string html |
799 | */ |
800 | protected function getAfterContent() { |
801 | $html = ''; |
802 | |
803 | if ( $this->data['catlinks'] || $this->data['dataAfterContent'] ) { |
804 | $html .= Html::openElement( 'div', [ 'id' => 'content-bottom-stuff' ] ); |
805 | if ( $this->data['catlinks'] ) { |
806 | $html .= $this->get( 'catlinks' ); |
807 | } |
808 | if ( $this->data['dataAfterContent'] ) { |
809 | $html .= $this->get( 'dataAfterContent' ); |
810 | } |
811 | $html .= Html::closeElement( 'div' ); |
812 | } |
813 | |
814 | return $html; |
815 | } |
816 | |
817 | /** |
818 | * Generate pile of all the tools |
819 | * |
820 | * We can make a few assumptions based on where a tool started out: |
821 | * If it's in the cactions region, it's a page tool, probably primary or secondary |
822 | * ...that's all I can think of |
823 | * |
824 | * @return array of array of tools information (portlet formatting) |
825 | */ |
826 | protected function getPageTools() { |
827 | $title = $this->getSkin()->getTitle(); |
828 | $namespace = $title->getNamespace(); |
829 | |
830 | $sortedPileOfTools = [ |
831 | 'namespaces' => [], |
832 | 'page-primary' => [], |
833 | 'page-secondary' => [], |
834 | 'user' => [], |
835 | 'page-tertiary' => [], |
836 | 'more' => [], |
837 | 'general' => [] |
838 | ]; |
839 | |
840 | // Tools specific to the page |
841 | $pileOfEditTools = []; |
842 | $contentNavigation = $this->data['content_navigation']; |
843 | |
844 | foreach ( $contentNavigation as $navKey => $navBlock ) { |
845 | // Just use namespaces items as they are |
846 | if ( $navKey == 'namespaces' ) { |
847 | if ( $namespace < 0 && count( $navBlock ) < 2 ) { |
848 | // Put special page ns_pages in the more pile so they're not so lonely |
849 | $sortedPileOfTools['page-tertiary'] = $navBlock; |
850 | } else { |
851 | $sortedPileOfTools['namespaces'] = $navBlock; |
852 | } |
853 | } elseif ( $navKey == 'variants' ) { |
854 | // wat |
855 | $sortedPileOfTools['variants'] = $navBlock; |
856 | } else { |
857 | $pileOfEditTools = array_merge( $pileOfEditTools, $navBlock ); |
858 | } |
859 | } |
860 | |
861 | // Tools that may be general or page-related (typically the toolbox) |
862 | $pileOfTools = $this->sidebar['TOOLBOX']; |
863 | if ( $namespace >= 0 ) { |
864 | $pileOfTools['pagelog'] = [ |
865 | 'text' => $this->getMsg( 'timeless-pagelog' )->text(), |
866 | 'href' => SpecialPage::getTitleFor( 'Log' )->getLocalURL( |
867 | [ 'page' => $title->getPrefixedText() ] |
868 | ), |
869 | 'id' => 't-pagelog' |
870 | ]; |
871 | } |
872 | |
873 | // Mobile toggles |
874 | $pileOfTools['more'] = [ |
875 | 'text' => $this->getMsg( 'timeless-more' )->text(), |
876 | 'id' => 'ca-more', |
877 | 'class' => 'dropdown-toggle' |
878 | ]; |
879 | // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset |
880 | if ( !empty( $this->sidebar['LANGUAGES'] ) || $sortedPileOfTools['variants'] |
881 | || $this->otherProjects !== null ) { |
882 | $pileOfTools['languages'] = [ |
883 | 'text' => $this->getMsg( 'timeless-languages' )->escaped(), |
884 | 'id' => 'ca-languages', |
885 | 'class' => 'dropdown-toggle' |
886 | ]; |
887 | } |
888 | |
889 | // This is really dumb, and you're an idiot for doing it this way. |
890 | // Obviously if you're not the idiot who did this, I don't mean you. |
891 | foreach ( $pileOfEditTools as $navKey => $navBlock ) { |
892 | if ( in_array( $navKey, [ |
893 | 'watch', |
894 | 'unwatch' |
895 | ] ) ) { |
896 | $currentSet = 'namespaces'; |
897 | } elseif ( in_array( $navKey, [ |
898 | 'edit', |
899 | 'view', |
900 | 'history', |
901 | 'addsection', |
902 | 'viewsource' |
903 | ] ) ) { |
904 | $currentSet = 'page-primary'; |
905 | } elseif ( in_array( $navKey, [ |
906 | 'delete', |
907 | 'rename', |
908 | 'protect', |
909 | 'unprotect', |
910 | 'move' |
911 | ] ) ) { |
912 | $currentSet = 'page-secondary'; |
913 | } else { |
914 | // Catch random extension ones? |
915 | $currentSet = 'page-primary'; |
916 | } |
917 | $sortedPileOfTools[$currentSet][$navKey] = $navBlock; |
918 | } |
919 | foreach ( $pileOfTools as $navKey => $navBlock ) { |
920 | $currentSet = null; |
921 | |
922 | if ( $navKey === 'contributions' ) { |
923 | $currentSet = 'page-primary'; |
924 | } elseif ( in_array( $navKey, [ |
925 | 'blockip', |
926 | 'userrights', |
927 | 'log', |
928 | 'emailuser' |
929 | |
930 | ] ) ) { |
931 | $currentSet = 'user'; |
932 | } elseif ( in_array( $navKey, [ |
933 | 'whatlinkshere', |
934 | 'print', |
935 | 'info', |
936 | 'pagelog', |
937 | 'recentchangeslinked', |
938 | 'permalink', |
939 | 'wikibase', |
940 | 'cite' |
941 | ] ) ) { |
942 | $currentSet = 'page-tertiary'; |
943 | } elseif ( in_array( $navKey, [ |
944 | 'more', |
945 | 'languages' |
946 | ] ) ) { |
947 | $currentSet = 'more'; |
948 | } else { |
949 | $currentSet = 'general'; |
950 | } |
951 | $sortedPileOfTools[$currentSet][$navKey] = $navBlock; |
952 | } |
953 | |
954 | // Extra sorting for Extension:ProofreadPage namespace items |
955 | $tabs = [ |
956 | // This is the order we want them in... |
957 | 'proofreadPageScanLink', |
958 | 'proofreadPageIndexLink', |
959 | 'proofreadPageNextLink', |
960 | ]; |
961 | foreach ( $tabs as $tab ) { |
962 | if ( isset( $sortedPileOfTools['namespaces'][$tab] ) ) { |
963 | $toMove = $sortedPileOfTools['namespaces'][$tab]; |
964 | unset( $sortedPileOfTools['namespaces'][$tab] ); |
965 | |
966 | // move to end! |
967 | $sortedPileOfTools['namespaces'][$tab] = $toMove; |
968 | } |
969 | } |
970 | |
971 | return $sortedPileOfTools; |
972 | } |
973 | |
974 | /** |
975 | * Categories for the sidebar |
976 | * |
977 | * Assemble an array of categories. This doesn't show any categories for the |
978 | * action=history view, but that behaviour is consistent with other skins. |
979 | * |
980 | * @return string html |
981 | */ |
982 | protected function getCategories() { |
983 | $skin = $this->getSkin(); |
984 | $catHeader = 'categories'; |
985 | $catList = ''; |
986 | |
987 | $allCats = $skin->getOutput()->getCategoryLinks(); |
988 | if ( $allCats !== [] ) { |
989 | if ( isset( $allCats['normal'] ) && $allCats['normal'] !== [] ) { |
990 | $catList .= $this->getCatList( |
991 | $allCats['normal'], |
992 | 'normal-catlinks', |
993 | 'mw-normal-catlinks', |
994 | 'categories' |
995 | ); |
996 | } else { |
997 | $catHeader = 'hidden-categories'; |
998 | } |
999 | |
1000 | if ( isset( $allCats['hidden'] ) ) { |
1001 | $hiddenCatClass = [ 'mw-hidden-catlinks' ]; |
1002 | if ( MediaWikiServices::getInstance() |
1003 | ->getUserOptionsLookup() |
1004 | ->getBoolOption( $skin->getUser(), 'showhiddencats' ) |
1005 | ) { |
1006 | $hiddenCatClass[] = 'mw-hidden-cats-user-shown'; |
1007 | } elseif ( $skin->getTitle()->getNamespace() === NS_CATEGORY ) { |
1008 | $hiddenCatClass[] = 'mw-hidden-cats-ns-shown'; |
1009 | } else { |
1010 | $hiddenCatClass[] = 'mw-hidden-cats-hidden'; |
1011 | } |
1012 | $catList .= $this->getCatList( |
1013 | $allCats['hidden'], |
1014 | 'hidden-catlinks', |
1015 | $hiddenCatClass, |
1016 | [ 'hidden-categories', count( $allCats['hidden'] ) ] |
1017 | ); |
1018 | } |
1019 | } |
1020 | |
1021 | if ( $catList !== '' ) { |
1022 | return $this->getSidebarChunk( 'catlinks-sidebar', $catHeader, $catList ); |
1023 | } |
1024 | |
1025 | return ''; |
1026 | } |
1027 | |
1028 | /** |
1029 | * List of categories |
1030 | * |
1031 | * @param array $list |
1032 | * @param string $id |
1033 | * @param string|array $class |
1034 | * @param string|array $message i18n message name or an array of [ message name, params ] |
1035 | * |
1036 | * @return string html |
1037 | */ |
1038 | protected function getCatList( $list, $id, $class, $message ) { |
1039 | $html = Html::openElement( 'div', [ 'id' => "sidebar-{$id}", 'class' => $class ] ); |
1040 | |
1041 | $makeLinkItem = static function ( $linkHtml ) { |
1042 | return Html::rawElement( 'li', [], $linkHtml ); |
1043 | }; |
1044 | |
1045 | $categoryItems = array_map( $makeLinkItem, $list ); |
1046 | |
1047 | $categoriesHtml = Html::rawElement( 'ul', |
1048 | [], |
1049 | implode( '', $categoryItems ) |
1050 | ); |
1051 | |
1052 | $html .= $this->getPortlet( $id, $categoriesHtml, $message ); |
1053 | |
1054 | return $html . Html::closeElement( 'div' ); |
1055 | } |
1056 | |
1057 | /** |
1058 | * Interlanguage links block, with variants if applicable |
1059 | * Layout sort of assumes we're using ULS compact language handling |
1060 | * if there's a lot of languages. |
1061 | * |
1062 | * @return string html |
1063 | */ |
1064 | protected function getVariants() { |
1065 | $html = ''; |
1066 | |
1067 | if ( $this->pileOfTools['variants'] ) { |
1068 | $html .= $this->getPortlet( |
1069 | 'variants-desktop', |
1070 | $this->pileOfTools['variants'], |
1071 | 'variants', |
1072 | [ 'body-extra-classes' => 'dropdown' ] |
1073 | ); |
1074 | } |
1075 | |
1076 | return $html; |
1077 | } |
1078 | |
1079 | /** |
1080 | * Interwiki links block |
1081 | * |
1082 | * @return string html |
1083 | */ |
1084 | protected function getInterwikiLinks() { |
1085 | $html = ''; |
1086 | $variants = ''; |
1087 | $otherprojects = ''; |
1088 | $show = false; |
1089 | $variantsOnly = false; |
1090 | |
1091 | if ( $this->pileOfTools['variants'] ) { |
1092 | $variants = $this->getPortlet( |
1093 | 'variants', |
1094 | $this->pileOfTools['variants'] |
1095 | ); |
1096 | $show = true; |
1097 | $variantsOnly = true; |
1098 | } |
1099 | |
1100 | $languages = $this->getPortlet( 'lang', $this->languages, 'otherlanguages' ); |
1101 | |
1102 | // Force rendering of this section if there are languages or when the 'lang' |
1103 | // portlet has been modified by hook even if there are no language items. |
1104 | if ( count( $this->languages ) || $this->afterLangPortlet !== '' ) { |
1105 | $show = true; |
1106 | $variantsOnly = false; |
1107 | } else { |
1108 | $languages = ''; |
1109 | } |
1110 | |
1111 | // if using wikibase for 'in other projects' |
1112 | if ( $this->otherProjects !== null ) { |
1113 | $otherprojects = $this->getPortlet( |
1114 | 'wikibase-otherprojects', |
1115 | $this->otherProjects |
1116 | ); |
1117 | $show = true; |
1118 | $variantsOnly = false; |
1119 | } |
1120 | |
1121 | if ( $show ) { |
1122 | $html .= $this->getSidebarChunk( |
1123 | 'other-languages', |
1124 | 'timeless-projects', |
1125 | $variants . $languages . $otherprojects, |
1126 | $variantsOnly ? [ 'variants-only' ] : [] |
1127 | ); |
1128 | } |
1129 | |
1130 | return $html; |
1131 | } |
1132 | |
1133 | /** |
1134 | * Generate img-based logos for proper HiDPI support |
1135 | * |
1136 | * @param string|array|null $logo |
1137 | * @param bool $doLarge Render extra-large HiDPI logos for mobile devices? |
1138 | * |
1139 | * @return string|false html|we're not doing this |
1140 | */ |
1141 | protected function getLogoImage( $logo, $doLarge = false ) { |
1142 | if ( $logo === null ) { |
1143 | // not set, fall back to generic methods |
1144 | return false; |
1145 | } |
1146 | |
1147 | // Generate $logoData from a file upload |
1148 | if ( is_string( $logo ) ) { |
1149 | $file = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $logo ); |
1150 | |
1151 | if ( !$file || !$file->canRender() ) { |
1152 | // eeeeeh bail, scary |
1153 | return false; |
1154 | } |
1155 | $logoData = []; |
1156 | |
1157 | // Calculate intended sizes |
1158 | $width = $file->getWidth(); |
1159 | $height = $file->getHeight(); |
1160 | $bound = $width > $height ? $width : $height; |
1161 | $svg = File::normalizeExtension( $file->getExtension() ) === 'svg'; |
1162 | |
1163 | // Mobile stuff is generally a lot more than just 2ppp. Let's go with 4x? |
1164 | // Currently we're just doing this for wordmarks, which shouldn't get that |
1165 | // big in practice, so this is probably safe enough. And no need to use |
1166 | // this for desktop logos, so fall back to 2x for 2x as default... |
1167 | $large = $doLarge ? 4 : 2; |
1168 | |
1169 | if ( $bound <= 165 ) { |
1170 | // It's a 1x image |
1171 | $logoData['width'] = $width; |
1172 | $logoData['height'] = $height; |
1173 | |
1174 | if ( $svg ) { |
1175 | $logoData['1x'] = $file->createThumb( $logoData['width'] ); |
1176 | $logoData['1.5x'] = $file->createThumb( (int)( $logoData['width'] * 1.5 ) ); |
1177 | $logoData['2x'] = $file->createThumb( $logoData['width'] * $large ); |
1178 | } elseif ( $file->mustRender() ) { |
1179 | $logoData['1x'] = $file->createThumb( $logoData['width'] ); |
1180 | } else { |
1181 | $logoData['1x'] = $file->getUrl(); |
1182 | } |
1183 | |
1184 | } elseif ( $bound >= 230 && $bound <= 330 ) { |
1185 | // It's a 2x image |
1186 | $logoData['width'] = (int)( $width / 2 ); |
1187 | $logoData['height'] = (int)( $height / 2 ); |
1188 | |
1189 | $logoData['1x'] = $file->createThumb( $logoData['width'] ); |
1190 | $logoData['1.5x'] = $file->createThumb( (int)( $logoData['width'] * 1.5 ) ); |
1191 | |
1192 | if ( $svg || $file->mustRender() ) { |
1193 | $logoData['2x'] = $file->createThumb( $logoData['width'] * 2 ); |
1194 | } else { |
1195 | $logoData['2x'] = $file->getUrl(); |
1196 | } |
1197 | } else { |
1198 | // Okay, whatever, we get to pick something random |
1199 | // Yes I am aware this means they might have arbitrarily tall logos, |
1200 | // and you know what, let 'em, I don't care |
1201 | $logoData['width'] = 155; |
1202 | $logoData['height'] = File::scaleHeight( $width, $height, $logoData['width'] ); |
1203 | |
1204 | $logoData['1x'] = $file->createThumb( $logoData['width'] ); |
1205 | if ( $svg || $logoData['width'] * 1.5 <= $width ) { |
1206 | $logoData['1.5x'] = $file->createThumb( (int)( $logoData['width'] * 1.5 ) ); |
1207 | } |
1208 | if ( $svg || $logoData['width'] * 2 <= $width ) { |
1209 | $logoData['2x'] = $file->createThumb( $logoData['width'] * $large ); |
1210 | } |
1211 | } |
1212 | } elseif ( is_array( $logo ) ) { |
1213 | // manually set logo data for non-file-uploads |
1214 | $logoData = $logo; |
1215 | } else { |
1216 | // nope |
1217 | return false; |
1218 | } |
1219 | |
1220 | // Render the html output! |
1221 | $attribs = [ |
1222 | 'alt' => $this->getMsg( 'sitetitle' )->text(), |
1223 | // Should we care? It's just a logo... |
1224 | 'decoding' => 'auto', |
1225 | 'width' => $logoData['width'], |
1226 | 'height' => $logoData['height'], |
1227 | ]; |
1228 | |
1229 | if ( !isset( $logoData['1x'] ) && isset( $logoData['2x'] ) ) { |
1230 | // We'll allow it... |
1231 | $attribs['src'] = $logoData['2x']; |
1232 | } else { |
1233 | // Okay, we really do want a 1x otherwise. If this throws an error or |
1234 | // something because there's nothing here, GOOD. |
1235 | // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset |
1236 | $attribs['src'] = $logoData['1x']; |
1237 | |
1238 | // Throw the rest in a srcset |
1239 | unset( $logoData['1x'], $logoData['width'], $logoData['height'] ); |
1240 | $srcset = ''; |
1241 | foreach ( $logoData as $res => $path ) { |
1242 | if ( $srcset != '' ) { |
1243 | $srcset .= ', '; |
1244 | } |
1245 | $srcset .= $path . ' ' . $res; |
1246 | } |
1247 | |
1248 | if ( $srcset !== '' ) { |
1249 | $attribs['srcset'] = $srcset; |
1250 | } |
1251 | } |
1252 | |
1253 | return Html::element( 'img', $attribs ); |
1254 | } |
1255 | } |