MediaWiki REL1_34
TimelessTemplate.php
Go to the documentation of this file.
1<?php
8
10 protected $pileOfTools;
11
13 protected $sidebar;
14
16 protected $otherProjects;
17
20
24 public function execute() {
25 $this->sidebar = $this->getSidebar();
26
27 // WikiBase sidebar thing
28 if ( isset( $this->sidebar['wikibase-otherprojects'] ) ) {
29 $this->otherProjects = $this->sidebar['wikibase-otherprojects'];
30 unset( $this->sidebar['wikibase-otherprojects'] );
31 }
32 // Collection sidebar thing
33 if ( isset( $this->sidebar['coll-print_export'] ) ) {
34 $this->collectionPortlet = $this->sidebar['coll-print_export'];
35 unset( $this->sidebar['coll-print_export'] );
36 }
37
38 $this->pileOfTools = $this->getPageTools();
39 $userLinks = $this->getUserLinks();
40
41 // Open html, body elements, etc
42 $html = $this->get( 'headelement' );
43
44 $html .= Html::openElement( 'div', [ 'id' => 'mw-wrapper', 'class' => $userLinks['class'] ] );
45
46 $html .= Html::rawElement( 'div', [ 'id' => 'mw-header-container', 'class' => 'ts-container' ],
47 Html::rawElement( 'div', [ 'id' => 'mw-header', 'class' => 'ts-inner' ],
48 $userLinks['html'] .
49 $this->getLogo( 'p-logo-text', 'text' ) .
50 $this->getSearch()
51 ) .
52 $this->getClear()
53 );
54 $html .= $this->getHeaderHack();
55
56 // For mobile
57 $html .= Html::element( 'div', [ 'id' => 'menus-cover' ] );
58
59 $html .= Html::rawElement( 'div', [ 'id' => 'mw-content-container', 'class' => 'ts-container' ],
60 Html::rawElement( 'div', [ 'id' => 'mw-content-block', 'class' => 'ts-inner' ],
61 Html::rawElement( 'div', [ 'id' => 'mw-content-wrapper' ],
62 $this->getContentBlock() .
63 $this->getAfterContent()
64 ) .
65 Html::rawElement( 'div', [ 'id' => 'mw-site-navigation' ],
66 $this->getLogo( 'p-logo', 'image' ) .
67 $this->getMainNavigation() .
68 $this->getSidebarChunk(
69 'site-tools',
70 'timeless-sitetools',
71 $this->getPortlet(
72 'tb',
73 $this->pileOfTools['general'],
74 'timeless-sitetools'
75 )
76 )
77 ) .
78 Html::rawElement( 'div', [ 'id' => 'mw-related-navigation' ],
79 $this->getPageToolSidebar() .
80 $this->getInterwikiLinks() .
81 $this->getCategories()
82 ) .
83 $this->getClear()
84 )
85 );
86
87 $html .= Html::rawElement( 'div', [ 'id' => 'mw-footer-container', 'class' => 'ts-container' ],
88 Html::rawElement( 'div', [ 'id' => 'mw-footer', 'class' => 'ts-inner' ],
89 $this->getFooter()
90 )
91 );
92
93 $html .= Html::closeElement( 'div' );
94
95 // BaseTemplate::printTrail() stuff (has no get version)
96 // Required for RL to run
97 $html .= MWDebug::getDebugHTML( $this->getSkin()->getContext() );
98 $html .= $this->get( 'bottomscripts' );
99 $html .= $this->get( 'reporttime' );
100
101 $html .= Html::closeElement( 'body' );
102 $html .= Html::closeElement( 'html' );
103
104 // The unholy echo
105 echo $html;
106 }
107
114 protected function getContentBlock() {
115 $html = Html::rawElement(
116 'div',
117 [ 'id' => 'content', 'class' => 'mw-body', 'role' => 'main' ],
118 $this->getSiteNotices() .
119 $this->getIndicators() .
120 Html::rawElement(
121 'h1',
122 [
123 'id' => 'firstHeading',
124 'class' => 'firstHeading',
125 'lang' => $this->get( 'pageLanguage' )
126 ],
127 $this->get( 'title' )
128 ) .
129 Html::rawElement( 'div', [ 'id' => 'bodyContentOuter' ],
130 Html::rawElement( 'div', [ 'id' => 'siteSub' ], $this->getMsg( 'tagline' )->parse() ) .
131 Html::rawElement( 'div', [ 'id' => 'mw-page-header-links' ],
132 $this->getPortlet(
133 'namespaces',
134 $this->pileOfTools['namespaces'],
135 'timeless-namespaces',
136 [ 'extra-classes' => 'tools-inline' ]
137 ) .
138 $this->getPortlet(
139 'more',
140 $this->pileOfTools['more'],
141 'timeless-more',
142 [ 'extra-classes' => 'tools-inline' ]
143 ) .
144 $this->getVariants() .
145 $this->getPortlet(
146 'views',
147 $this->pileOfTools['page-primary'],
148 'timeless-pagetools',
149 [ 'extra-classes' => 'tools-inline' ]
150 )
151 ) .
152 $this->getClear() .
153 Html::rawElement( 'div', [ 'class' => 'mw-body-content', 'id' => 'bodyContent' ],
154 $this->getContentSub() .
155 $this->get( 'bodytext' ) .
156 $this->getClear()
157 )
158 )
159 );
160
161 return Html::rawElement( 'div', [ 'id' => 'mw-content' ], $html );
162 }
163
191 protected function getPortlet( $name, $content, $msg = null, $setOptions = [] ) {
192 // random stuff to override with any provided options
193 $options = array_merge( [
194 'role' => 'navigation',
195 // extra classes/ids
196 'id' => 'p-' . $name,
197 'class' => [ 'mw-portlet', 'emptyPortlet' => !$content ],
198 'extra-classes' => '',
199 'body-id' => null,
200 'body-class' => 'mw-portlet-body',
201 'body-extra-classes' => '',
202 // wrapper for individual list items
203 'text-wrapper' => [ 'tag' => 'span' ],
204 // option to stick arbitrary stuff at the beginning of the ul
205 'list-prepend' => ''
206 ], $setOptions );
207
208 // Handle the different $msg possibilities
209 if ( $msg === null ) {
210 $msg = $name;
211 $msgParams = [];
212 } elseif ( is_array( $msg ) ) {
213 $msgString = array_shift( $msg );
214 $msgParams = $msg;
215 $msg = $msgString;
216 } else {
217 $msgParams = [];
218 }
219 $msgObj = $this->getMsg( $msg, $msgParams );
220 if ( $msgObj->exists() ) {
221 $msgString = $msgObj->parse();
222 } else {
223 $msgString = htmlspecialchars( $msg );
224 }
225
226 $labelId = Sanitizer::escapeIdForAttribute( "p-$name-label" );
227
228 if ( is_array( $content ) ) {
229 $contentText = Html::openElement( 'ul',
230 [ 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ]
231 );
232 $contentText .= $options['list-prepend'];
233 foreach ( $content as $key => $item ) {
234 if ( is_array( $options['text-wrapper'] ) ) {
235 $contentText .= $this->makeListItem(
236 $key,
237 $item,
238 [ 'text-wrapper' => $options['text-wrapper'] ]
239 );
240 } else {
241 $contentText .= $this->makeListItem(
242 $key,
243 $item
244 );
245 }
246 }
247 $contentText .= Html::closeElement( 'ul' );
248 } else {
249 $contentText = $content;
250 }
251
252 $divOptions = [
253 'role' => $options['role'],
254 'class' => $this->mergeClasses( $options['class'], $options['extra-classes'] ),
255 'id' => Sanitizer::escapeIdForAttribute( $options['id'] ),
256 'title' => Linker::titleAttrib( $options['id'] ),
257 'aria-labelledby' => $labelId
258 ];
259 $labelOptions = [
260 'id' => $labelId,
261 'lang' => $this->get( 'userlang' ),
262 'dir' => $this->get( 'dir' )
263 ];
264
265 $bodyDivOptions = [
266 'class' => $this->mergeClasses( $options['body-class'], $options['body-extra-classes'] )
267 ];
268 if ( is_string( $options['body-id'] ) ) {
269 $bodyDivOptions['id'] = $options['body-id'];
270 }
271
272 $html = Html::rawElement( 'div', $divOptions,
273 Html::rawElement( 'h3', $labelOptions, $msgString ) .
274 Html::rawElement( 'div', $bodyDivOptions,
275 $contentText .
276 $this->getAfterPortlet( $name )
277 )
278 );
279
280 return $html;
281 }
282
294 protected function mergeClasses( $class, $extraClasses ) {
295 if ( !is_array( $class ) ) {
296 $class = [ $class ];
297 }
298 if ( !is_array( $extraClasses ) ) {
299 $extraClasses = [ $extraClasses ];
300 }
301
302 return array_merge( $class, $extraClasses );
303 }
304
315 protected function getSidebarChunk( $id, $headerMessage, $content, $classes = [] ) {
316 $html = '';
317
318 $html .= Html::rawElement(
319 'div',
320 [
321 'id' => Sanitizer::escapeId( $id ),
322 'class' => array_merge( [ 'sidebar-chunk' ], $classes )
323 ],
324 Html::rawElement( 'h2', [],
325 Html::element( 'span', [],
326 $this->getMsg( $headerMessage )->text()
327 )
328 ) .
329 Html::rawElement( 'div', [ 'class' => 'sidebar-inner' ], $content )
330 );
331
332 return $html;
333 }
334
343 protected function getLogo( $id = 'p-logo', $part = 'both' ) {
344 $html = '';
345 $language = $this->getSkin()->getLanguage();
346 $config = $this->getSkin()->getContext()->getConfig();
347
348 $html .= Html::openElement(
349 'div',
350 [
351 'id' => Sanitizer::escapeId( $id ),
352 'class' => 'mw-portlet',
353 'role' => 'banner'
354 ]
355 );
356 if ( $part !== 'image' ) {
357 $wordmarkImage = $this->getLogoImage( $config->get( 'TimelessWordmark' ), true );
358
359 $titleClass = '';
360 if ( !$wordmarkImage ) {
361 if ( $language->hasVariants() ) {
362 $siteTitle = $language->convert( $this->getMsg( 'timeless-sitetitle' )->escaped() );
363 } else {
364 $siteTitle = $this->getMsg( 'timeless-sitetitle' )->escaped();
365 }
366 // width is 11em; 13 characters will probably fit?
367 if ( mb_strlen( $siteTitle ) > 13 ) {
368 $titleClass = 'long';
369 }
370 } else {
371 $titleClass = 'wordmark';
372 }
373 $html .= Html::rawElement( 'a', [
374 'id' => 'p-banner',
375 'class' => [ 'mw-wiki-title', $titleClass ],
376 'href' => $this->data['nav_urls']['mainpage']['href']
377 ],
378 $wordmarkImage ?: $siteTitle
379 );
380
381 }
382 if ( $part !== 'text' ) {
383 $logoImage = $this->getLogoImage( $config->get( 'TimelessLogo' ) );
384
385 $html .= Html::rawElement(
386 'a',
387 array_merge(
388 [
389 'class' => [ 'mw-wiki-logo', !$logoImage ? 'fallback' : 'timeless-logo' ],
390 'href' => $this->data['nav_urls']['mainpage']['href']
391 ],
393 ),
394 $logoImage ?: ''
395 );
396 }
397 $html .= Html::closeElement( 'div' );
398
399 return $html;
400 }
401
407 protected function getSearch() {
408 $html = '';
409
410 $html .= Html::openElement( 'div', [ 'class' => 'mw-portlet', 'id' => 'p-search' ] );
411
412 $html .= Html::rawElement(
413 'h3',
414 [ 'lang' => $this->get( 'userlang' ), 'dir' => $this->get( 'dir' ) ],
415 Html::rawElement( 'label', [ 'for' => 'searchInput' ], $this->getMsg( 'search' )->escaped() )
416 );
417
418 $html .= Html::rawElement( 'form', [ 'action' => $this->get( 'wgScript' ), 'id' => 'searchform' ],
419 Html::rawElement( 'div', [ 'id' => 'simpleSearch' ],
420 Html::rawElement( 'div', [ 'id' => 'searchInput-container' ],
421 $this->makeSearchInput( [
422 'id' => 'searchInput'
423 ] )
424 ) .
425 Html::hidden( 'title', $this->get( 'searchtitle' ) ) .
426 $this->makeSearchButton(
427 'fulltext',
428 [ 'id' => 'mw-searchButton', 'class' => 'searchButton mw-fallbackSearchButton' ]
429 ) .
430 $this->makeSearchButton(
431 'go',
432 [ 'id' => 'searchButton', 'class' => 'searchButton' ]
433 )
434 )
435 );
436
437 $html .= Html::closeElement( 'div' );
438
439 return $html;
440 }
441
447 protected function getMainNavigation() {
448 $html = '';
449
450 // Already hardcoded into header
451 $this->sidebar['SEARCH'] = false;
452 // Parsed as part of pageTools
453 $this->sidebar['TOOLBOX'] = false;
454 // Forcibly removed to separate chunk
455 $this->sidebar['LANGUAGES'] = false;
456
457 foreach ( $this->sidebar as $name => $content ) {
458 if ( $content === false ) {
459 continue;
460 }
461 // Numeric strings gets an integer when set as key, cast back - T73639
462 $name = (string)$name;
463 $html .= $this->getPortlet( $name, $content['content'] );
464 }
465
466 $html = $this->getSidebarChunk( 'site-navigation', 'navigation', $html );
467
468 return $html;
469 }
470
477 protected function getHeaderHack() {
478 $html = '';
479
480 // These are almost exactly the same and this is stupid.
481 $html .= Html::rawElement( 'div', [ 'id' => 'mw-header-hack', 'class' => 'color-bar' ],
482 Html::rawElement( 'div', [ 'class' => 'color-middle-container' ],
483 Html::element( 'div', [ 'class' => 'color-middle' ] )
484 ) .
485 Html::element( 'div', [ 'class' => 'color-left' ] ) .
486 Html::element( 'div', [ 'class' => 'color-right' ] )
487 );
488 $html .= Html::rawElement( 'div', [ 'id' => 'mw-header-nav-hack' ],
489 Html::rawElement( 'div', [ 'class' => 'color-bar' ],
490 Html::rawElement( 'div', [ 'class' => 'color-middle-container' ],
491 Html::element( 'div', [ 'class' => 'color-middle' ] )
492 ) .
493 Html::element( 'div', [ 'class' => 'color-left' ] ) .
494 Html::element( 'div', [ 'class' => 'color-right' ] )
495 )
496 );
497
498 return $html;
499 }
500
506 protected function getPageToolSidebar() {
507 $pageTools = '';
508 $pageTools .= $this->getPortlet(
509 'cactions',
510 $this->pileOfTools['page-secondary'],
511 'timeless-pageactions'
512 );
513 $pageTools .= $this->getPortlet(
514 'userpagetools',
515 $this->pileOfTools['user'],
516 'timeless-userpagetools'
517 );
518 $pageTools .= $this->getPortlet(
519 'pagemisc',
520 $this->pileOfTools['page-tertiary'],
521 'timeless-pagemisc'
522 );
523 if ( isset( $this->collectionPortlet ) ) {
524 $pageTools .= $this->getPortlet(
525 'coll-print_export',
526 $this->collectionPortlet['content']
527 );
528 }
529
530 return $this->getSidebarChunk( 'page-tools', 'timeless-pageactions', $pageTools );
531 }
532
539 protected function getUserLinks() {
540 $user = $this->getSkin()->getUser();
541 $personalTools = $this->getPersonalTools();
542 // Preserve standard username label to allow customisation (T215822)
543 $userName = $personalTools['userpage']['links'][0]['text'] ?? $user->getName();
544
545 $html = '';
546 $extraTools = [];
547
548 // Remove Echo badges
549 if ( isset( $personalTools['notifications-alert'] ) ) {
550 $extraTools['notifications-alert'] = $personalTools['notifications-alert'];
551 unset( $personalTools['notifications-alert'] );
552 }
553 if ( isset( $personalTools['notifications-notice'] ) ) {
554 $extraTools['notifications-notice'] = $personalTools['notifications-notice'];
555 unset( $personalTools['notifications-notice'] );
556 }
557 $class = empty( $extraTools ) ? '' : 'extension-icons';
558
559 // Re-label some messages
560 if ( isset( $personalTools['userpage'] ) ) {
561 $personalTools['userpage']['links'][0]['text'] = $this->getMsg( 'timeless-userpage' )->text();
562 }
563 if ( isset( $personalTools['mytalk'] ) ) {
564 $personalTools['mytalk']['links'][0]['text'] = $this->getMsg( 'timeless-talkpage' )->text();
565 }
566
567 // Labels
568 if ( $user->isLoggedIn() ) {
569 $dropdownHeader = $userName;
570 $headerMsg = [ 'timeless-loggedinas', $userName ];
571 } else {
572 $dropdownHeader = $this->getMsg( 'timeless-anonymous' )->text();
573 $headerMsg = 'timeless-notloggedin';
574 }
575 $html .= Html::openElement( 'div', [ 'id' => 'user-tools' ] );
576
577 $html .= Html::rawElement( 'div', [ 'id' => 'personal' ],
578 Html::rawElement( 'h2', [],
579 Html::element( 'span', [], $dropdownHeader )
580 ) .
581 Html::rawElement( 'div', [ 'id' => 'personal-inner', 'class' => 'dropdown' ],
582 $this->getPortlet( 'personal', $personalTools, $headerMsg )
583 )
584 );
585
586 // Extra icon stuff (echo etc)
587 if ( !empty( $extraTools ) ) {
588 $iconList = '';
589 foreach ( $extraTools as $key => $item ) {
590 $iconList .= $this->makeListItem( $key, $item );
591 }
592
593 $html .= Html::rawElement(
594 'div',
595 [ 'id' => 'personal-extra', 'class' => 'p-body' ],
596 Html::rawElement( 'ul', [], $iconList )
597 );
598 }
599
600 $html .= Html::closeElement( 'div' );
601
602 return [
603 'html' => $html,
604 'class' => $class
605 ];
606 }
607
613 protected function getSiteNotices() {
614 $html = '';
615
616 if ( $this->data['sitenotice'] ) {
617 $html .= Html::rawElement( 'div', [ 'id' => 'siteNotice' ], $this->get( 'sitenotice' ) );
618 }
619 if ( $this->data['newtalk'] ) {
620 $html .= Html::rawElement( 'div', [ 'class' => 'usermessage' ], $this->get( 'newtalk' ) );
621 }
622
623 return $html;
624 }
625
631 protected function getContentSub() {
632 $html = '';
633
634 $html .= Html::openElement( 'div', [ 'id' => 'contentSub' ] );
635 if ( $this->data['subtitle'] ) {
636 $html .= $this->get( 'subtitle' );
637 }
638 if ( $this->data['undelete'] ) {
639 $html .= $this->get( 'undelete' );
640 }
641 $html .= Html::closeElement( 'div' );
642
643 return $html;
644 }
645
652 protected function getAfterContent() {
653 $html = '';
654
655 if ( $this->data['catlinks'] || $this->data['dataAfterContent'] ) {
656 $html .= Html::openElement( 'div', [ 'id' => 'content-bottom-stuff' ] );
657 if ( $this->data['catlinks'] ) {
658 $html .= $this->get( 'catlinks' );
659 }
660 if ( $this->data['dataAfterContent'] ) {
661 $html .= $this->get( 'dataAfterContent' );
662 }
663 $html .= Html::closeElement( 'div' );
664 }
665
666 return $html;
667 }
668
678 protected function getPageTools() {
679 $title = $this->getSkin()->getTitle();
680 $namespace = $title->getNamespace();
681
682 $sortedPileOfTools = [
683 'namespaces' => [],
684 'page-primary' => [],
685 'page-secondary' => [],
686 'user' => [],
687 'page-tertiary' => [],
688 'more' => [],
689 'general' => []
690 ];
691
692 // Tools specific to the page
693 $pileOfEditTools = [];
694 foreach ( $this->data['content_navigation'] as $navKey => $navBlock ) {
695 // Just use namespaces items as they are
696 if ( $navKey == 'namespaces' ) {
697 if ( $namespace < 0 && count( $navBlock ) < 2 ) {
698 // Put special page ns_pages in the more pile so they're not so lonely
699 $sortedPileOfTools['page-tertiary'] = $navBlock;
700 } else {
701 $sortedPileOfTools['namespaces'] = $navBlock;
702 }
703 } elseif ( $navKey == 'variants' ) {
704 // wat
705 $sortedPileOfTools['variants'] = $navBlock;
706 } else {
707 $pileOfEditTools = array_merge( $pileOfEditTools, $navBlock );
708 }
709 }
710
711 // Tools that may be general or page-related (typically the toolbox)
712 $pileOfTools = $this->getToolbox();
713 if ( $namespace >= 0 ) {
714 $pileOfTools['pagelog'] = [
715 'text' => $this->getMsg( 'timeless-pagelog' )->text(),
716 'href' => SpecialPage::getTitleFor( 'Log' )->getLocalURL(
717 [ 'page' => $title->getPrefixedText() ]
718 ),
719 'id' => 't-pagelog'
720 ];
721 }
722
723 // Mobile toggles
724 $pileOfTools['more'] = [
725 'text' => $this->getMsg( 'timeless-more' )->text(),
726 'id' => 'ca-more',
727 'class' => 'dropdown-toggle'
728 ];
729 if ( $this->data['language_urls'] !== false || $sortedPileOfTools['variants']
730 || isset( $this->otherProjects ) ) {
731 $pileOfTools['languages'] = [
732 'text' => $this->getMsg( 'timeless-languages' )->escaped(),
733 'id' => 'ca-languages',
734 'class' => 'dropdown-toggle'
735 ];
736 }
737
738 // This is really dumb, and you're an idiot for doing it this way.
739 // Obviously if you're not the idiot who did this, I don't mean you.
740 foreach ( $pileOfEditTools as $navKey => $navBlock ) {
741 $currentSet = null;
742
743 if ( in_array( $navKey, [
744 'watch',
745 'unwatch'
746 ] ) ) {
747 $currentSet = 'namespaces';
748 } elseif ( in_array( $navKey, [
749 'edit',
750 'view',
751 'history',
752 'addsection',
753 'viewsource'
754 ] ) ) {
755 $currentSet = 'page-primary';
756 } elseif ( in_array( $navKey, [
757 'delete',
758 'rename',
759 'protect',
760 'unprotect',
761 'move'
762 ] ) ) {
763 $currentSet = 'page-secondary';
764 } else {
765 // Catch random extension ones?
766 $currentSet = 'page-primary';
767 }
768 $sortedPileOfTools[$currentSet][$navKey] = $navBlock;
769 }
770 foreach ( $pileOfTools as $navKey => $navBlock ) {
771 $currentSet = null;
772
773 if ( $navKey === 'contributions' ) {
774 $currentSet = 'page-primary';
775 } elseif ( in_array( $navKey, [
776 'blockip',
777 'userrights',
778 'log',
779 'emailuser'
780
781 ] ) ) {
782 $currentSet = 'user';
783 } elseif ( in_array( $navKey, [
784 'whatlinkshere',
785 'print',
786 'info',
787 'pagelog',
788 'recentchangeslinked',
789 'permalink',
790 'wikibase',
791 'cite'
792 ] ) ) {
793 $currentSet = 'page-tertiary';
794 } elseif ( in_array( $navKey, [
795 'more',
796 'languages'
797 ] ) ) {
798 $currentSet = 'more';
799 } else {
800 $currentSet = 'general';
801 }
802 $sortedPileOfTools[$currentSet][$navKey] = $navBlock;
803 }
804
805 // Extra sorting for Extension:ProofreadPage namespace items
806 $tabs = [
807 'proofreadPagePrevLink',
808 // This is the order we want them in...
809 'proofreadPageScanLink',
810 'proofreadPageIndexLink',
811 'proofreadPageNextLink',
812 ];
813 foreach ( $tabs as $tab ) {
814 if ( isset( $sortedPileOfTools['namespaces'][$tab] ) ) {
815 $toMove = $sortedPileOfTools['namespaces'][$tab];
816 unset( $sortedPileOfTools['namespaces'][$tab] );
817
818 // add a hover tooltip, mostly for the icons
819 $toMove['title'] = $toMove['text'];
820
821 if ( $tab === 'proofreadPagePrevLink' ) {
822 // prev at start
823 $sortedPileOfTools['namespaces'] = array_merge(
824 [ $tab => $toMove ],
825 $sortedPileOfTools['namespaces']
826 );
827 } else {
828 // move others to end
829 $sortedPileOfTools['namespaces'][$tab] = $toMove;
830 }
831 }
832 }
833
834 return $sortedPileOfTools;
835 }
836
845 protected function getCategories() {
846 $skin = $this->getSkin();
847 $catList = '';
848 $html = '';
849
850 $allCats = $skin->getOutput()->getCategoryLinks();
851 if ( !empty( $allCats ) ) {
852 if ( !empty( $allCats['normal'] ) ) {
853 $catHeader = 'categories';
854 $catList .= $this->getCatList(
855 $allCats['normal'],
856 'normal-catlinks',
857 'mw-normal-catlinks',
858 'categories'
859 );
860 } else {
861 $catHeader = 'hidden-categories';
862 }
863
864 if ( isset( $allCats['hidden'] ) ) {
865 $hiddenCatClass = [ 'mw-hidden-catlinks' ];
866 if ( $skin->getUser()->getBoolOption( 'showhiddencats' ) ) {
867 $hiddenCatClass[] = 'mw-hidden-cats-user-shown';
868 } elseif ( $skin->getTitle()->getNamespace() == NS_CATEGORY ) {
869 $hiddenCatClass[] = 'mw-hidden-cats-ns-shown';
870 } else {
871 $hiddenCatClass[] = 'mw-hidden-cats-hidden';
872 }
873 $catList .= $this->getCatList(
874 $allCats['hidden'],
875 'hidden-catlinks',
876 $hiddenCatClass,
877 [ 'hidden-categories', count( $allCats['hidden'] ) ]
878 );
879 }
880 }
881
882 if ( $catList !== '' ) {
883 $html = $this->getSidebarChunk( 'catlinks-sidebar', $catHeader, $catList );
884 }
885
886 return $html;
887 }
888
899 protected function getCatList( $list, $id, $class, $message ) {
900 $html = Html::openElement( 'div', [ 'id' => "sidebar-{$id}", 'class' => $class ] );
901
902 $makeLinkItem = function ( $linkHtml ) {
903 return Html::rawElement( 'li', [], $linkHtml );
904 };
905
906 $categoryItems = array_map( $makeLinkItem, $list );
907
908 $categoriesHtml = Html::rawElement( 'ul',
909 [],
910 implode( '', $categoryItems )
911 );
912
913 $html .= $this->getPortlet( $id, $categoriesHtml, $message );
914
915 $html .= Html::closeElement( 'div' );
916
917 return $html;
918 }
919
927 protected function getVariants() {
928 $html = '';
929
930 if ( $this->pileOfTools['variants'] ) {
931 $html .= $this->getPortlet(
932 'variants-desktop',
933 $this->pileOfTools['variants'],
934 'variants',
935 [ 'body-extra-classes' => 'dropdown' ]
936 );
937 }
938
939 return $html;
940 }
941
947 protected function getInterwikiLinks() {
948 $html = '';
949 $variants = '';
950 $otherprojects = '';
951 $languages = '';
952 $show = false;
953 $variantsOnly = false;
954
955 if ( $this->pileOfTools['variants'] ) {
956 $variants = $this->getPortlet(
957 'variants',
958 $this->pileOfTools['variants']
959 );
960 $show = true;
961 $variantsOnly = true;
962 }
963 if ( $this->data['language_urls'] !== false ) {
964 $languages = $this->getPortlet(
965 'lang',
966 $this->data['language_urls'] ?: [],
967 'otherlanguages'
968 );
969 $show = true;
970 $variantsOnly = false;
971 }
972 // if using wikibase for 'in other projects'
973 if ( isset( $this->otherProjects ) ) {
974 $otherprojects = $this->getPortlet(
975 'wikibase-otherprojects',
976 $this->otherProjects['content']
977 );
978 $show = true;
979 $variantsOnly = false;
980 }
981
982 if ( $show ) {
983 $html .= $this->getSidebarChunk(
984 'other-languages',
985 'timeless-projects',
986 $variants . $languages . $otherprojects,
987 $variantsOnly ? [ 'variants-only' ] : []
988 );
989 }
990
991 return $html;
992 }
993
1002 protected function getLogoImage( $logo, $doLarge = false ) {
1003 if ( $logo === null ) {
1004 // not set, fall back to generic methods
1005 return false;
1006 }
1007
1008 // Generate $logoData from a file upload
1009 if ( is_string( $logo ) ) {
1010 $file = wfFindFile( $logo );
1011
1012 if ( !$file || !$file->canRender() ) {
1013 // eeeeeh bail, scary
1014 return false;
1015 }
1016 $logoData = [];
1017
1018 // Calculate intended sizes
1019 $width = $file->getWidth();
1020 $height = $file->getHeight();
1021 $bound = $width > $height ? $width : $height;
1022 $svg = File::normalizeExtension( $file->getExtension() ) === 'svg';
1023
1024 // Mobile stuff is generally a lot more than just 2ppp. Let's go with 4x?
1025 // Currently we're just doing this for wordmarks, which shouldn't get that
1026 // big in practice, so this is probably safe enough. And no need to use
1027 // this for desktop logos, so fall back to 2x for 2x as default...
1028 $large = $doLarge ? 4 : 2;
1029
1030 if ( $bound <= 165 ) {
1031 // It's a 1x image
1032 $logoData['width'] = $width;
1033 $logoData['height'] = $height;
1034
1035 if ( $svg ) {
1036 $logoData['1x'] = $file->createThumb( $logoData['width'] );
1037 $logoData['1.5x'] = $file->createThumb( $logoData['width'] * 1.5 );
1038 $logoData['2x'] = $file->createThumb( $logoData['width'] * $large );
1039 } elseif ( $file->mustRender() ) {
1040 $logoData['1x'] = $file->createThumb( $logoData['width'] );
1041 } else {
1042 $logoData['1x'] = $file->getUrl();
1043 }
1044
1045 } elseif ( $bound >= 230 && $bound <= 330 ) {
1046 // It's a 2x image
1047 $logoData['width'] = $width / 2;
1048 $logoData['height'] = $height / 2;
1049
1050 $logoData['1x'] = $file->createThumb( $logoData['width'] );
1051 $logoData['1.5x'] = $file->createThumb( $logoData['width'] * 1.5 );
1052
1053 if ( $svg || $file->mustRender() ) {
1054 $logoData['2x'] = $file->createThumb( $logoData['width'] * 2 );
1055 } else {
1056 $logoData['2x'] = $file->getUrl();
1057 }
1058 } else {
1059 // Okay, whatever, we get to pick something random
1060 // Yes I am aware this means they might have arbitrarily tall logos,
1061 // and you know what, let 'em, I don't care
1062 $logoData['width'] = 155;
1063 $logoData['height'] = File::scaleHeight( $width, $height, $logoData['width'] );
1064
1065 $logoData['1x'] = $file->createThumb( $logoData['width'] );
1066 if ( $svg || $logoData['width'] * 1.5 <= $width ) {
1067 $logoData['1.5x'] = $file->createThumb( $logoData['width'] * 1.5 );
1068 }
1069 if ( $svg || $logoData['width'] * 2 <= $width ) {
1070 $logoData['2x'] = $file->createThumb( $logoData['width'] * $large );
1071 }
1072 }
1073 } elseif ( is_array( $logo ) ) {
1074 // manually set logo data for non-file-uploads
1075 $logoData = $logo;
1076 } else {
1077 // nope
1078 return false;
1079 }
1080
1081 // Render the html output!
1082 $attribs = [
1083 'alt' => $this->getMsg( 'sitetitle' )->text(),
1084 // Should we care? It's just a logo...
1085 'decoding' => 'auto',
1086 'width' => $logoData['width'],
1087 'height' => $logoData['height'],
1088 ];
1089
1090 if ( !isset( $logoData['1x'] ) && isset( $logoData['2x'] ) ) {
1091 // We'll allow it...
1092 $attribs['src'] = $logoData['2x'];
1093 } else {
1094 // Okay, we really do want a 1x otherwise. If this throws an error or
1095 // something because there's nothing here, GOOD.
1096 $attribs['src'] = $logoData['1x'];
1097
1098 // Throw the rest in a srcset
1099 unset( $logoData['1x'], $logoData['width'], $logoData['height'] );
1100 $srcset = '';
1101 foreach ( $logoData as $res => $path ) {
1102 if ( $srcset != '' ) {
1103 $srcset .= ', ';
1104 }
1105 $srcset .= $path . ' ' . $res;
1106 }
1107
1108 if ( $srcset !== '' ) {
1109 $attribs['srcset'] = $srcset;
1110 }
1111 }
1112
1113 return Html::element( 'img', $attribs );
1114 }
1115}
wfFindFile( $title, $options=[])
Find a file.
getContext()
New base template for a skin's template extended from QuickTemplate this class features helper method...
makeSearchButton( $mode, $attrs=[])
getToolbox()
Create an array of common toolbox items from the data in the quicktemplate stored by SkinTemplate.
getSidebar( $options=[])
getPersonalTools()
Create an array of personal tools items from the data in the quicktemplate stored by SkinTemplate.
makeListItem( $key, $item, $options=[])
Generates a list item for a navigation, portlet, portal, sidebar... list.
getMsg( $name,... $params)
Get a Message object with its context set.
getIndicators()
Get the suggested HTML for page status indicators: icons (or short text snippets) usually displayed i...
makeSearchInput( $attrs=[])
getFooter( $iconStyle='icononly', $linkStyle='flat')
Renderer for getFooterIcons and getFooterLinks.
getClear()
Get a div with the core visualClear class, for clearing floats.
getAfterPortlet( $name)
Allows extensions to hook into known portlets and add stuff to them.
static titleAttrib( $name, $options=null, array $msgParams=[])
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition Linker.php:2026
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2195
getSkin()
Get the Skin object related to this object.
BaseTemplate class for the Timeless skin.
getLogo( $id='p-logo', $part='both')
The logo and (optionally) site title.
getHeaderHack()
The colour bars Split this out so we don't have to look at it/can easily kill it later.
getVariants()
Interlanguage links block, with variants if applicable Layout sort of assumes we're using ULS compact...
getContentBlock()
Generate the page content block Broken out here due to the excessive indenting, or stuff.
getSiteNotices()
Notices that may appear above the firstHeading.
array null $otherProjects
getContentSub()
Links and information that may appear below the firstHeading.
getPageTools()
Generate pile of all the tools.
getLogoImage( $logo, $doLarge=false)
Generate img-based logos for proper HiDPI support.
getMainNavigation()
Left sidebar navigation, usually.
mergeClasses( $class, $extraClasses)
Helper function for getPortlet.
array null $collectionPortlet
getSidebarChunk( $id, $headerMessage, $content, $classes=[])
Sidebar chunk containing one or more portlets.
getCategories()
Categories for the sidebar.
getInterwikiLinks()
Interwiki links block.
getPageToolSidebar()
Page tools in sidebar.
getUserLinks()
Personal/user links portlet for header.
getSearch()
The search box at the top.
getAfterContent()
The data after content, catlinks, and potential other stuff that may appear within the content block ...
execute()
Outputs the entire contents of the page.
getCatList( $list, $id, $class, $message)
List of categories.
getPortlet( $name, $content, $msg=null, $setOptions=[])
Generates a block of navigation links with a header This is some random fork of some random fork of w...
const NS_CATEGORY
Definition Defines.php:83
$content
Definition router.php:78
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
switch( $options['output']) $languages
Definition transstat.php:76