23 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
27 use Wikimedia\WrappedString;
28 use Wikimedia\WrappedStringList;
43 use ProtectedHookAccessorTrait;
86 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
87 return $skinFactory->getSkinNames();
102 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
103 return $skinFactory->getAllowedSkins();
118 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
119 $skinNames = $skinFactory->getSkinNames();
122 $skinNames = array_change_key_case( $skinNames, CASE_LOWER );
123 $key = strtolower( $key );
127 if ( $key ==
'' || $key ==
'default' ) {
133 if ( isset( $skinNames[$key] ) ) {
148 if ( isset( $skinNames[$key] ) ) {
150 } elseif ( isset( $skinNames[$defaultSkin] ) ) {
153 return $fallbackSkin;
178 $this->skinname = $name;
199 return $this->options[
'responsive'] ??
false;
207 $skinMetaTags = $this->
getConfig()->get(
'SkinMetaTags' );
213 'width=device-width, initial-scale=1.0, ' .
214 'user-scalable=yes, minimum-scale=0.25, maximum-scale=5.0'
220 'twitter:card' =>
'summary_large_image',
221 'og:type' =>
'website',
225 foreach ( $tags as $key => $value ) {
226 if ( in_array( $key, $skinMetaTags ) ) {
255 'skin' => $this->options[
'styles'] ?? [],
262 'mediawiki.page.ready',
269 'skin' => $this->options[
'scripts'] ?? [],
279 if ( strpos( $out->getHTML(),
'sortable' ) !==
false ) {
280 $modules[
'content'][] =
'jquery.tablesorter';
281 $modules[
'styles'][
'content'][] =
'jquery.tablesorter.styles';
285 if ( strpos( $out->getHTML(),
'mw-collapsible' ) !==
false ) {
286 $modules[
'content'][] =
'jquery.makeCollapsible';
287 $modules[
'styles'][
'content'][] =
'jquery.makeCollapsible.styles';
292 if ( strpos( $out->getHTML(),
'mw-ui-button' ) !==
false ) {
293 $modules[
'styles'][
'content'][] =
'mediawiki.ui.button';
296 if ( $out->isTOCEnabled() ) {
297 $modules[
'content'][] =
'mediawiki.toc';
301 if ( $authority->getPerformer()->isRegistered()
302 && $authority->isAllowedAll(
'writeapi',
'viewmywatchlist',
'editmywatchlist' )
303 && $this->getRelevantTitle()->canExist()
305 $modules[
'watch'][] =
'mediawiki.page.watch.ajax';
308 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
309 if ( $userOptionsLookup->getBoolOption( $user,
'editsectiononrightclick' )
310 || ( $out->isArticle() && $userOptionsLookup->getOption( $user,
'editondblclick' ) )
312 $modules[
'user'][] =
'mediawiki.misc-authed-pref';
315 if ( $out->isSyndicated() ) {
316 $modules[
'styles'][
'syndicate'][] =
'mediawiki.feedlink';
330 if ( $user->isRegistered() ) {
331 $titles[] = $user->getUserPage();
332 $titles[] = $user->getTalkPage();
337 if (
$title->canExist() &&
$title->canHaveTalkPage() ) {
338 $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
339 if (
$title->isTalkPage() ) {
340 $titles[] = $namespaceInfo->getSubjectPage(
$title );
342 $titles[] = $namespaceInfo->getTalkPage(
$title );
347 if ( $this->
getConfig()->
get(
'FooterLinkCacheExpiry' ) <= 0 ) {
348 $titles = array_merge(
358 $this->getHookRunner()->onSkinPreloadExistence( $titles, $this );
361 $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
362 $lb = $linkBatchFactory->newLinkBatch( $titles );
363 $lb->setCaller( __METHOD__ );
377 return $this->
getOutput()->getRevisionId();
389 return $this->
getOutput()->isRevisionCurrent();
397 $this->mRelevantTitle =
$t;
411 return $this->mRelevantTitle ?? $this->
getTitle();
419 $this->mRelevantUser = $u;
431 if ( isset( $this->mRelevantUser ) ) {
436 $rootUser =
$title->getRootText();
443 $user->load( User::READ_NORMAL );
445 if ( $user->isRegistered() ) {
446 $this->mRelevantUser = $user;
510 $numeric =
'ns-' .
$title->getNamespace();
512 if (
$title->isSpecialPage() ) {
513 $type =
'ns-special';
515 list( $canonicalName ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
516 resolveAlias(
$title->getDBkey() );
517 if ( $canonicalName ) {
520 $type .=
' mw-invalidspecialpage';
523 if (
$title->isTalkPage() ) {
526 $type =
'ns-subject';
530 $type .=
' mw-editable';
537 return "$numeric $type $name $root";
547 'lang' =>
$lang->getHtmlCode(),
548 'dir' =>
$lang->getDir(),
549 'class' =>
'client-nojs',
569 $allCats = $out->getCategoryLinks();
571 $services = MediaWikiServices::getInstance();
572 $linkRenderer = $services->getLinkRenderer();
574 if ( $allCats === [] ) {
582 $colon = $this->
msg(
'colon-separator' )->escaped();
584 if ( !empty( $allCats[
'normal'] ) ) {
585 $t = $embed . implode( $pop . $embed, $allCats[
'normal'] ) . $pop;
587 $msg = $this->
msg(
'pagecategories' )->numParams( count( $allCats[
'normal'] ) );
588 $linkPage = $this->
msg(
'pagecategorieslink' )->inContentLanguage()->text();
590 if ( $pageCategoriesLinkTitle ) {
591 $link = $linkRenderer->makeLink( $pageCategoriesLinkTitle, $msg->text() );
593 $link = $msg->escaped();
595 $s .=
'<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
596 $link . $colon .
'<ul>' .
$t .
'</ul></div>';
600 if ( isset( $allCats[
'hidden'] ) ) {
601 $userOptionsLookup = $services->getUserOptionsLookup();
603 if ( $userOptionsLookup->getBoolOption( $this->getUser(),
'showhiddencats' ) ) {
604 $class =
' mw-hidden-cats-user-shown';
606 $class =
' mw-hidden-cats-ns-shown';
608 $class =
' mw-hidden-cats-hidden';
611 $s .=
"<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
612 $this->
msg(
'hidden-categories' )->numParams( count( $allCats[
'hidden'] ) )->escaped() .
613 $colon .
'<ul>' . $embed . implode( $pop . $embed, $allCats[
'hidden'] ) . $pop .
'</ul>' .
617 # optional 'dmoz-like' category browser. Will be shown under the list
618 # of categories an article belong to
619 if ( $this->
getConfig()->
get(
'UseCategoryBrowser' ) ) {
620 $s .=
'<br /><hr />';
622 # get a big array of the parents tree
623 $parenttree =
$title->getParentCategoryTree();
624 # Skin object passed by reference cause it can not be
625 # accessed under the method subfunction drawCategoryBrowser
627 # Clean out bogus first entry and sort them
628 unset( $tempout[0] );
630 # Output one per line
631 $s .= implode(
"<br />\n", $tempout );
644 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
646 foreach ( $tree as $element => $parent ) {
647 if ( empty( $parent ) ) {
648 # element start a new list
651 # grab the others elements
655 # add our current element to the list
657 $return .= $linkRenderer->makeLink( $eltitle, $eltitle->getText() );
667 $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
668 $showHiddenCats = $userOptionsLookup->getBoolOption( $this->
getUser(),
'showhiddencats' );
672 $allCats = $this->
getOutput()->getCategoryLinks();
675 $classes = [
'catlinks' ];
676 if ( empty( $allCats[
'normal'] ) && !( !empty( $allCats[
'hidden'] ) && $showHidden ) ) {
677 $classes[] =
'catlinks-allhidden';
682 [
'id' =>
'catlinks',
'class' => $classes,
'data-mw' =>
'interface' ],
704 if ( $this->getHookRunner()->onSkinAfterContent( $data, $this ) ) {
707 if ( trim( $data ) !=
'' ) {
711 $data =
"<div id='mw-data-after-content'>\n" .
716 wfDebug(
"Hook SkinAfterContent changed output processing." );
743 $chunks = [ $this->
getOutput()->getBottomScripts() ];
748 $this->getHookRunner()->onSkinAfterBottomScripts( $this, $extraHtml );
749 if ( $extraHtml !==
'' ) {
750 $chunks[] = $extraHtml;
752 return WrappedString::join(
"\n", $chunks );
764 $oldid = $this->
getOutput()->getRevisionId();
766 $canonicalUrl =
$title->getCanonicalURL(
'oldid=' . $oldid );
767 $url = htmlspecialchars(
wfExpandIRI( $canonicalUrl ) );
773 return $this->
msg(
'retrievedfrom' )
774 ->rawParams(
'<a dir="ltr" href="' . $url .
'">' . $url .
'</a>' )
782 $action = $this->
getRequest()->getVal(
'action',
'view' );
784 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
786 if ( ( !
$title->exists() || $action ==
'history' ) &&
787 $this->getAuthority()->probablyCan(
'deletedhistory',
$title )
789 $n =
$title->getDeletedEditsCount();
793 $msg =
'thisisdeleted';
795 $msg =
'viewdeleted';
798 $subtitle = $this->
msg( $msg )->rawParams(
799 $linkRenderer->makeKnownLink(
801 $this->msg(
'restorelink' )->numParams( $n )->text() )
807 if ( $action !==
'history' ) {
808 $links[] = $linkRenderer->makeKnownLink(
810 $this->
msg(
'viewpagelogs-lowercase' )->text(),
812 [
'page' =>
$title->getPrefixedText() ]
817 $this->getHookRunner()->onUndeletePageToolLinks(
822 . $this->
msg(
'word-separator' )->escaped()
823 . $this->
msg(
'parentheses' )
824 ->rawParams( $this->
getLanguage()->pipeList( $links ) )
828 return Html::rawElement(
'div', [
'class' =>
'mw-undelete-subtitle' ], $subtitle );
849 $services = MediaWikiServices::getInstance();
850 $linkRenderer = $services->getLinkRenderer();
852 $title = $out->getTitle();
855 if ( !$this->getHookRunner()->onSkinSubPageSubtitle( $subpages, $this, $out ) ) {
859 $hasSubpages = $services->getNamespaceInfo()->hasSubpages(
$title->getNamespace() );
860 if ( !$out->isArticle() || !$hasSubpages ) {
864 $ptext =
$title->getPrefixedText();
865 if ( strpos( $ptext,
'/' ) !==
false ) {
866 $links = explode(
'/', $ptext );
873 foreach ( $links as $link ) {
874 $growingLink .= $link;
878 if ( $linkObj && $linkObj->isKnown() ) {
879 $getlink = $linkRenderer->makeKnownLink( $linkObj, $display );
884 $subpages .=
$lang->getDirMarkEntity() . $this->
msg(
'pipe-separator' )->escaped();
886 $subpages .=
'< ';
889 $subpages .= $getlink;
909 return $searchPage->getLocalURL();
917 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
918 if (
$type ==
'detect' ) {
920 && !$this->
msg(
'history_copyright' )->inContentLanguage()->isDisabled()
928 if (
$type ==
'history' ) {
929 $msg =
'history_copyright';
936 if ( $config->get(
'RightsPage' ) ) {
938 $link = $linkRenderer->makeKnownLink(
941 } elseif ( $config->get(
'RightsUrl' ) ) {
943 } elseif ( $config->get(
'RightsText' ) ) {
944 $link = $config->get(
'RightsText' );
951 $this->getHookRunner()->onSkinCopyrightFooter( $this->
getTitle(),
$type, $msg, $link );
953 return $this->
msg( $msg )->rawParams( $link )->text();
963 $footerIcons = $config->get(
'FooterIcons' );
964 if ( $footerIcons[
'copyright'][
'copyright'] ) {
965 $out = $footerIcons[
'copyright'][
'copyright'];
966 } elseif ( $config->get(
'RightsIcon' ) ) {
967 $icon = htmlspecialchars( $config->get(
'RightsIcon' ) );
968 $url = $config->get(
'RightsUrl' );
971 $out .=
'<a href="' . htmlspecialchars( $url ) .
'">';
974 $text = htmlspecialchars( $config->get(
'RightsText' ) );
975 $out .=
"<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
990 $resourceBasePath = $this->
getConfig()->get(
'ResourceBasePath' );
991 $url1 = htmlspecialchars(
992 "$resourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
994 $url1_5 = htmlspecialchars(
995 "$resourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
997 $url2 = htmlspecialchars(
998 "$resourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
1000 $text =
'<a href="https://www.mediawiki.org/"><img src="' . $url1
1001 .
'" srcset="' . $url1_5 .
' 1.5x, ' . $url2 .
' 2x" '
1002 .
'height="31" width="88" alt="Powered by MediaWiki" loading="lazy" /></a>';
1003 $this->getHookRunner()->onSkinGetPoweredBy( $text, $this );
1013 $timestamp = $this->
getOutput()->getRevisionTimestamp();
1017 # No cached timestamp, load it from the database
1018 if ( $timestamp ===
null ) {
1019 $revId = $this->
getOutput()->getRevisionId();
1020 if ( $revId !==
null ) {
1021 $timestamp = MediaWikiServices::getInstance()
1022 ->getRevisionLookup()
1023 ->getTimestampFromId( $revId );
1028 $d = $language->userDate( $timestamp, $user );
1029 $t = $language->userTime( $timestamp, $user );
1030 $s =
' ' . $this->
msg(
'lastmodifiedat', $d,
$t )->parse();
1035 if ( MediaWikiServices::getInstance()->getDBLoadBalancer()->getLaggedReplicaMode() ) {
1036 $s .=
' <strong>' . $this->
msg(
'laggedreplicamode' )->parse() .
'</strong>';
1047 if ( $align !=
'' ) {
1048 $a =
" style='float: {$align};'";
1053 $mp = $this->
msg(
'mainpage' )->escaped();
1055 $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) :
'' );
1058 return "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
1070 if ( is_string( $icon ) ) {
1073 $url = $icon[
"url"] ??
null;
1074 unset( $icon[
"url"] );
1075 if ( isset( $icon[
"src"] ) && $withImage ===
'withImage' ) {
1077 $icon[
"loading"] =
'lazy';
1081 $html = htmlspecialchars( $icon[
"alt"] );
1085 [
"href" => $url,
"target" => $this->
getConfig()->
get(
'ExternalLinkTarget' ) ],
1101 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1102 $s = $linkRenderer->makeKnownLink(
1104 $this->
msg(
'mainpage' )->text()
1134 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1135 return $linkRenderer->makeKnownLink(
1137 $this->
msg( $desc )->text()
1148 if ( $this->
msg( $desc )->inContentLanguage()->isDisabled() ) {
1167 $callback =
function () {
1169 'privacy' => $this->
footerLink(
'privacy',
'privacypage' ),
1170 'about' => $this->
footerLink(
'aboutsite',
'aboutpage' ),
1171 'disclaimer' => $this->
footerLink(
'disclaimers',
'disclaimerpage' )
1175 $services = MediaWikiServices::getInstance();
1176 $msgCache = $services->getMessageCache();
1177 $wanCache = $services->getMainWANObjectCache();
1180 return ( $config->get(
'FooterLinkCacheExpiry' ) > 0 )
1181 ? $wanCache->getWithSetCallback(
1182 $wanCache->makeKey(
'footer-links' ),
1183 $config->get(
'FooterLinkCacheExpiry' ),
1189 $msgCache->getCheckKey( $this->getLanguage()->getCode() )
1205 return $this->
footerLink(
'privacy',
'privacypage' );
1216 return $this->
footerLink(
'aboutsite',
'aboutpage' );
1227 return $this->
footerLink(
'disclaimers',
'disclaimerpage' );
1241 if ( !$out->isRevisionCurrent() ) {
1242 $options[
'oldid'] = intval( $out->getRevisionId() );
1253 if ( $id instanceof
User ) {
1259 # The sending user must have a confirmed email address and the receiving
1260 # user must accept emails from the sender.
1261 return $this->
getUser()->canSendEmail()
1279 if ( $this->stylename ===
null ) {
1280 $class = static::class;
1281 throw new MWException(
"$class::\$stylename must be set to use getSkinStylePath()" );
1284 return $this->
getConfig()->get(
'StylePath' ) .
"/{$this->stylename}/$name";
1297 return $title->getLinkURL( $urlaction );
1313 if ( $proto ===
null ) {
1314 return $title->getLocalURL( $urlaction );
1316 return $title->getFullURL( $urlaction,
false, $proto );
1328 return $title->getLocalURL( $urlaction );
1337 public static function makeUrl( $name, $urlaction =
'' ) {
1342 return $title->getLocalURL( $urlaction );
1357 return $title->getLocalURL();
1374 return $title->getLocalURL( $urlaction );
1388 'href' =>
$title->getLocalURL( $urlaction ),
1389 'exists' =>
$title->isKnown(),
1404 'href' =>
$title->getLocalURL( $urlaction ),
1416 if ( !is_object(
$title ) ) {
1418 if ( !is_object(
$title ) ) {
1433 $map = $this->
getConfig()->get(
'InterlanguageLinkCodeMap' );
1434 return $map[ $code ] ?? $code;
1446 if ( $this->
getConfig()->
get(
'HideInterlanguageLinks' ) ) {
1449 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1452 $languageLinks = [];
1453 $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
1455 foreach ( $this->
getOutput()->getLanguageLinks() as $languageLinkText ) {
1456 $class =
'interlanguage-link interwiki-' . explode(
':', $languageLinkText, 2 )[0];
1459 if ( !$languageLinkTitle ) {
1465 $ilLangName = $langNameUtils->getLanguageName( $ilInterwikiCode );
1467 if ( strval( $ilLangName ) ===
'' ) {
1468 $ilDisplayTextMsg = $this->
msg(
"interlanguage-link-$ilInterwikiCode" );
1469 if ( !$ilDisplayTextMsg->isDisabled() ) {
1471 $ilLangName = $ilDisplayTextMsg->text();
1474 $ilLangName = $languageLinkText;
1478 $ilLangName = $this->
getLanguage()->ucfirst( $ilLangName );
1483 $ilLangLocalName = $langNameUtils->getLanguageName(
1485 $userLang->getCode()
1488 $languageLinkTitleText = $languageLinkTitle->getText();
1489 if ( $ilLangLocalName ===
'' ) {
1490 $ilFriendlySiteName = $this->
msg(
"interlanguage-link-sitename-$ilInterwikiCode" );
1491 if ( !$ilFriendlySiteName->isDisabled() ) {
1492 if ( $languageLinkTitleText ===
'' ) {
1493 $ilTitle = $this->
msg(
1494 'interlanguage-link-title-nonlangonly',
1495 $ilFriendlySiteName->text()
1498 $ilTitle = $this->
msg(
1499 'interlanguage-link-title-nonlang',
1500 $languageLinkTitleText,
1501 $ilFriendlySiteName->text()
1508 $ilTitle = $languageLinkTitle->getInterwiki() .
1509 ":$languageLinkTitleText";
1511 } elseif ( $languageLinkTitleText ===
'' ) {
1512 $ilTitle = $this->
msg(
1513 'interlanguage-link-title-langonly',
1517 $ilTitle = $this->
msg(
1518 'interlanguage-link-title',
1519 $languageLinkTitleText,
1526 'href' => $languageLinkTitle->getFullURL(),
1527 'text' => $ilLangName,
1528 'title' => $ilTitle,
1530 'link-class' =>
'interlanguage-link-target',
1531 'lang' => $ilInterwikiCodeBCP47,
1532 'hreflang' => $ilInterwikiCodeBCP47,
1534 $hookContainer->run(
1535 'SkinTemplateGetLanguageLink',
1539 $languageLinks[] = $languageLink;
1542 return $languageLinks;
1554 $thispage =
$title->getPrefixedDBkey();
1555 $uploadNavigationUrl = $this->
getConfig()->get(
'UploadNavigationUrl' );
1559 if ( $uploadNavigationUrl ) {
1560 $nav_urls[
'upload'] = [
'href' => $uploadNavigationUrl ];
1564 $nav_urls[
'upload'] =
false;
1568 $nav_urls[
'print'] =
false;
1569 $nav_urls[
'permalink'] =
false;
1570 $nav_urls[
'info'] =
false;
1571 $nav_urls[
'whatlinkshere'] =
false;
1572 $nav_urls[
'recentchangeslinked'] =
false;
1573 $nav_urls[
'contributions'] =
false;
1574 $nav_urls[
'log'] =
false;
1575 $nav_urls[
'blockip'] =
false;
1576 $nav_urls[
'mute'] =
false;
1577 $nav_urls[
'emailuser'] =
false;
1578 $nav_urls[
'userrights'] =
false;
1582 if ( !$out->isPrintable() && ( $out->isArticle() ||
$title->isSpecialPage() ) ) {
1583 $nav_urls[
'print'] = [
1584 'text' => $this->
msg(
'printableversion' )->text(),
1585 'href' =>
'javascript:print();'
1589 if ( $out->isArticle() ) {
1591 $revid = $out->getRevisionId();
1593 $nav_urls[
'permalink'] = [
1594 'text' => $this->
msg(
'permalink' )->text(),
1595 'href' =>
$title->getLocalURL(
"oldid=$revid" )
1600 if ( $out->isArticleRelated() ) {
1601 $nav_urls[
'whatlinkshere'] = [
1605 $nav_urls[
'info'] = [
1606 'text' => $this->
msg(
'pageinfo-toolboxlink' )->text(),
1607 'href' =>
$title->getLocalURL(
"action=info" )
1611 $nav_urls[
'recentchangeslinked'] = [
1624 if ( $user && $user->isRegistered() && $user->isHidden() &&
1625 !$this->getAuthority()->isAllowed(
'hideuser' )
1631 $rootUser = $user->getName();
1633 $nav_urls[
'contributions'] = [
1634 'text' => $this->
msg(
'tool-link-contributions', $rootUser )->text(),
1636 'tooltip-params' => [ $rootUser ],
1639 $nav_urls[
'log'] = [
1644 $nav_urls[
'blockip'] = [
1645 'text' => $this->
msg(
'blockip', $rootUser )->text(),
1651 $nav_urls[
'emailuser'] = [
1652 'text' => $this->
msg(
'tool-link-emailuser', $rootUser )->text(),
1654 'tooltip-params' => [ $rootUser ],
1658 if ( !$user->isAnon() ) {
1659 if ( $this->
getUser()->isRegistered() && $this->
getConfig()->
get(
'EnableSpecialMute' ) ) {
1660 $nav_urls[
'mute'] = [
1661 'text' => $this->
msg(
'mute-preferences' )->text(),
1668 $canChange = $sur->userCanChangeRights( $user );
1669 $nav_urls[
'userrights'] = [
1670 'text' => $this->
msg(
1671 $canChange ?
'tool-link-userrights' :
'tool-link-userrights-readonly',
1690 if ( $out->isSyndicated() ) {
1691 foreach ( $out->getSyndicationLinks() as $format => $link ) {
1694 'text' => $this->
msg(
"feed-$format" )->text(),
1727 $services = MediaWikiServices::getInstance();
1728 $callback =
function ( $old =
null, &$ttl = null ) {
1731 $this->getHookRunner()->onSkinBuildSidebar( $this, $bar );
1732 $msgCache = MediaWikiServices::getInstance()->getMessageCache();
1733 if ( $msgCache->isDisabled() ) {
1734 $ttl = WANObjectCache::TTL_UNCACHEABLE;
1740 $msgCache = $services->getMessageCache();
1741 $wanCache = $services->getMainWANObjectCache();
1745 $sidebar = $config->get(
'EnableSidebarCache' )
1746 ? $wanCache->getWithSetCallback(
1747 $wanCache->makeKey(
'sidebar', $languageCode ),
1748 $config->get(
'SidebarCacheExpiry' ),
1754 $msgCache->getCheckKey( $languageCode )
1767 $this->getHookRunner()->onSidebarBeforeOutput( $this, $sidebar );
1793 $lines = explode(
"\n", $text );
1797 $messageTitle = $config->get(
'EnableSidebarCache' )
1799 $messageCache = MediaWikiServices::getInstance()->getMessageCache();
1802 if ( strpos(
$line,
'*' ) !== 0 ) {
1807 if ( strpos(
$line,
'**' ) !== 0 ) {
1808 $heading = trim(
$line,
'* ' );
1809 if ( !array_key_exists( $heading, $bar ) ) {
1810 $bar[$heading] = [];
1815 if ( strpos(
$line,
'|' ) !==
false ) {
1816 $line = $messageCache->transform(
$line,
false,
null, $messageTitle );
1817 $line = array_map(
'trim', explode(
'|',
$line, 2 ) );
1818 if ( count(
$line ) !== 2 ) {
1826 $msgLink = $this->
msg( $line[0] )->title( $messageTitle )->inContentLanguage();
1827 if ( $msgLink->exists() ) {
1828 $link = $msgLink->text();
1829 if ( $link ==
'-' ) {
1835 $msgText = $this->
msg( $line[1] )->title( $messageTitle );
1836 if ( $msgText->exists() ) {
1837 $text = $msgText->text();
1846 if ( $config->get(
'NoFollowLinks' ) &&
1849 $extraAttribs[
'rel'] =
'nofollow';
1852 if ( $config->get(
'ExternalLinkTarget' ) ) {
1853 $extraAttribs[
'target'] = $config->get(
'ExternalLinkTarget' );
1860 $href =
$title->getLinkURL();
1862 $href =
'INVALID-TITLE';
1866 $bar[$heading][] = array_merge( [
1887 $newMessagesAlert =
'';
1889 $services = MediaWikiServices::getInstance();
1890 $linkRenderer = $services->getLinkRenderer();
1891 $userHasNewMessages = $services->getTalkPageNotificationManager()
1892 ->userHasNewMessages( $user );
1893 $timestamp = $services->getTalkPageNotificationManager()
1894 ->getLatestSeenMessageTimestamp( $user );
1895 $newtalks = !$userHasNewMessages ? [] : [
1899 'link' => $user->getTalkPage()->getLocalURL(),
1900 'rev' => $timestamp ? $services->getRevisionLookup()
1901 ->getRevisionByTimestamp( $user->getTalkPage(), $timestamp ) : null
1907 if ( !$this->getHookRunner()->onGetNewMessagesAlert(
1908 $newMessagesAlert, $newtalks, $user, $out )
1912 if ( $newMessagesAlert ) {
1913 return $newMessagesAlert;
1916 if ( $newtalks !== [] ) {
1917 $uTalkTitle = $user->getTalkPage();
1918 $lastSeenRev = $newtalks[0][
'rev'];
1920 if ( $lastSeenRev !==
null ) {
1922 $revStore = $services->getRevisionStore();
1923 $latestRev =
$revStore->getRevisionByTitle(
1926 RevisionLookup::READ_NORMAL
1928 if ( $latestRev !==
null ) {
1930 $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
1931 $numAuthors =
$revStore->countAuthorsBetween(
1932 $uTalkTitle->getArticleID(),
1937 RevisionStore::INCLUDE_NEW
1944 $plural = $plural ? 999 : 1;
1948 $newMessagesLink = $linkRenderer->makeKnownLink(
1950 $this->
msg(
'newmessageslinkplural' )->params( $plural )->text(),
1952 $uTalkTitle->isRedirect() ? [
'redirect' =>
'no' ] : []
1955 $newMessagesDiffLink = $linkRenderer->makeKnownLink(
1957 $this->
msg(
'newmessagesdifflinkplural' )->params( $plural )->text(),
1959 $lastSeenRev !==
null
1960 ? [
'oldid' => $lastSeenRev->getId(),
'diff' =>
'cur' ]
1961 : [
'diff' =>
'cur' ]
1964 if ( $numAuthors >= 1 && $numAuthors <= 10 ) {
1965 $newMessagesAlert = $this->
msg(
1966 'youhavenewmessagesfromusers',
1968 $newMessagesDiffLink
1969 )->numParams( $numAuthors, $plural );
1972 $newMessagesAlert = $this->
msg(
1973 $numAuthors > 10 ?
'youhavenewmessagesmanyusers' :
'youhavenewmessages',
1975 $newMessagesDiffLink
1976 )->numParams( $plural );
1978 $newMessagesAlert = $newMessagesAlert->text();
1980 $out->setCdnMaxage( 0 );
1983 return $newMessagesAlert;
1996 if ( $name ===
'default' ) {
1998 $notice = $config->get(
'SiteNotice' );
1999 if ( empty( $notice ) ) {
2003 $msg = $this->
msg( $name )->inContentLanguage();
2004 if ( $msg->isBlank() ) {
2006 } elseif ( $msg->isDisabled() ) {
2009 $notice = $msg->plain();
2012 $services = MediaWikiServices::getInstance();
2013 $cache = $services->getMainWANObjectCache();
2014 $parsed =
$cache->getWithSetCallback(
2017 $cache->makeKey( $name, $config->get(
'RenderHashAppend' ), md5( $notice ) ),
2020 function () use ( $notice ) {
2021 return $this->
getOutput()->parseAsInterface( $notice );
2025 $contLang = $services->getContentLanguage();
2029 'id' =>
'localNotice',
2030 'lang' => $contLang->getHtmlCode(),
2031 'dir' => $contLang->getDir()
2043 if ( $this->getHookRunner()->onSiteNoticeBefore( $siteNotice, $this ) ) {
2044 if ( $this->
getUser()->isRegistered() ) {
2048 if ( $anonNotice ===
false ) {
2051 $siteNotice = $anonNotice;
2054 if ( $siteNotice ===
false ) {
2059 $this->getHookRunner()->onSiteNoticeAfter( $siteNotice, $this );
2080 if ( $tooltip !==
null ) {
2081 $attribs[
'title'] = $this->
msg(
'editsectionhint' )->rawParams( $tooltip )
2082 ->inLanguage(
$lang )->text();
2087 'text' => $this->
msg(
'editsection' )->inLanguage( $lang )->text(),
2088 'targetTitle' => $nt,
2089 'attribs' => $attribs,
2090 'query' => [
'action' =>
'edit',
'section' => $section ]
2094 $this->getHookRunner()->onSkinEditSectionLinks( $this, $nt, $section, $tooltip, $links,
$lang );
2096 $result =
'<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
2098 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2100 foreach ( $links as $k => $linkDetails ) {
2101 $linksHtml[] = $linkRenderer->makeKnownLink(
2102 $linkDetails[
'targetTitle'],
2103 $linkDetails[
'text'],
2104 $linkDetails[
'attribs'],
2105 $linkDetails[
'query']
2110 '<span class="mw-editsection-divider">'
2111 . $this->
msg(
'pipe-separator' )->inLanguage(
$lang )->escaped()
2116 $result .=
'<span class="mw-editsection-bracket">]</span></span>';
2131 if ( $navUrls[
'whatlinkshere'] ??
null ) {
2132 $toolbox[
'whatlinkshere'] = $navUrls[
'whatlinkshere'];
2133 $toolbox[
'whatlinkshere'][
'id'] =
't-whatlinkshere';
2135 if ( $navUrls[
'recentchangeslinked'] ??
null ) {
2136 $toolbox[
'recentchangeslinked'] = $navUrls[
'recentchangeslinked'];
2137 $toolbox[
'recentchangeslinked'][
'msg'] =
'recentchangeslinked-toolbox';
2138 $toolbox[
'recentchangeslinked'][
'id'] =
't-recentchangeslinked';
2139 $toolbox[
'recentchangeslinked'][
'rel'] =
'nofollow';
2142 $toolbox[
'feeds'][
'id'] =
'feedlinks';
2143 $toolbox[
'feeds'][
'links'] = [];
2144 foreach ( $feedUrls as $key => $feed ) {
2145 $toolbox[
'feeds'][
'links'][$key] = $feed;
2146 $toolbox[
'feeds'][
'links'][$key][
'id'] =
"feed-$key";
2147 $toolbox[
'feeds'][
'links'][$key][
'rel'] =
'alternate';
2148 $toolbox[
'feeds'][
'links'][$key][
'type'] =
"application/{$key}+xml";
2149 $toolbox[
'feeds'][
'links'][$key][
'class'] =
'feedlink';
2152 foreach ( [
'contributions',
'log',
'blockip',
'emailuser',
'mute',
2153 'userrights',
'upload',
'specialpages' ] as $special
2155 if ( $navUrls[$special] ??
null ) {
2156 $toolbox[$special] = $navUrls[$special];
2157 $toolbox[$special][
'id'] =
"t-$special";
2160 if ( $navUrls[
'print'] ??
null ) {
2161 $toolbox[
'print'] = $navUrls[
'print'];
2162 $toolbox[
'print'][
'id'] =
't-print';
2163 $toolbox[
'print'][
'rel'] =
'alternate';
2164 $toolbox[
'print'][
'msg'] =
'printableversion';
2166 if ( $navUrls[
'permalink'] ??
null ) {
2167 $toolbox[
'permalink'] = $navUrls[
'permalink'];
2168 $toolbox[
'permalink'][
'id'] =
't-permalink';
2170 if ( $navUrls[
'info'] ??
null ) {
2171 $toolbox[
'info'] = $navUrls[
'info'];
2172 $toolbox[
'info'][
'id'] =
't-info';
2198 $out =
"<div class=\"mw-indicators mw-body-content\">\n";
2203 'id' => $indicatorData[
'id'],
2204 'class' => $indicatorData[
'class']
2206 $indicatorData[
'html']
2220 $indicatorData = [];
2221 foreach ( $indicators as $id =>
$content ) {
2222 $indicatorData[] = [
2224 'class' =>
'mw-indicator',
2228 return $indicatorData;
2244 $personal_tools = [];
2245 foreach ( $urls as $key => $plink ) {
2246 # The class on a personal_urls item is meant to go on the <a> instead
2247 # of the <li> so we have to use a single item "links" array instead
2248 # of using most of the personal_url's keys directly.
2251 [
'single-id' =>
"pt-$key" ],
2255 if ( isset( $plink[
'active'] ) ) {
2256 $ptool[
'active'] = $plink[
'active'];
2262 if ( isset( $plink[
'link-class'] ) ) {
2263 $ptool[
'links'][0][
'class'] = $plink[
'link-class'];
2274 if ( isset( $plink[$k] ) ) {
2275 $ptool[
'links'][0][$k] = $plink[$k];
2278 $personal_tools[$key] = $ptool;
2280 return $personal_tools;
2340 $text = $item[
'text'] ?? $this->
msg( $item[
'msg'] ?? $key )->text();
2342 $html = htmlspecialchars( $text );
2344 if ( isset(
$options[
'text-wrapper'] ) ) {
2345 $wrapper =
$options[
'text-wrapper'];
2346 if ( isset( $wrapper[
'tag'] ) ) {
2347 $wrapper = [ $wrapper ];
2349 while ( count( $wrapper ) > 0 ) {
2350 $element = array_pop( $wrapper );
2352 $html =
Html::rawElement( $element[
'tag'], $element[
'attributes'] ??
null, $html );
2356 if ( isset( $item[
'href'] ) || isset(
$options[
'link-fallback'] ) ) {
2358 foreach ( [
'single-id',
'text',
'msg',
'tooltiponly',
'context',
'primary',
2359 'tooltip-params',
'exists' ] as $k ) {
2360 unset( $attrs[$k] );
2363 if ( isset( $attrs[
'data'] ) ) {
2364 foreach ( $attrs[
'data'] as $key => $value ) {
2365 $attrs[
'data-' . $key ] = $value;
2367 unset( $attrs[
'data' ] );
2370 if ( isset( $item[
'id'] ) && !isset( $item[
'single-id'] ) ) {
2371 $item[
'single-id'] = $item[
'id'];
2374 $tooltipParams = [];
2375 if ( isset( $item[
'tooltip-params'] ) ) {
2376 $tooltipParams = $item[
'tooltip-params'];
2379 if ( isset( $item[
'single-id'] ) ) {
2380 $tooltipOption = isset( $item[
'exists'] ) && $item[
'exists'] ===
false ?
'nonexisting' :
null;
2382 if ( isset( $item[
'tooltiponly'] ) && $item[
'tooltiponly'] ) {
2384 if (
$title !==
false ) {
2385 $attrs[
'title'] =
$title;
2393 if ( isset( $tip[
'title'] ) && $tip[
'title'] !==
false ) {
2394 $attrs[
'title'] = $tip[
'title'];
2396 if ( isset( $tip[
'accesskey'] ) && $tip[
'accesskey'] !==
false ) {
2397 $attrs[
'accesskey'] = $tip[
'accesskey'];
2401 if ( isset(
$options[
'link-class'] ) ) {
2402 if ( isset( $attrs[
'class'] ) ) {
2404 if ( is_array( $attrs[
'class'] ) ) {
2405 $attrs[
'class'][] =
$options[
'link-class'];
2407 $attrs[
'class'] .=
" {$options['link-class']}";
2410 $attrs[
'class'] =
$options[
'link-class'];
2415 :
$options[
'link-fallback'], $attrs, $html );
2458 unset( $item[
'redundant'] );
2460 if ( isset( $item[
'links'] ) ) {
2462 foreach ( $item[
'links'] as $linkKey => $link ) {
2465 $html = implode(
' ', $links );
2469 foreach ( [
'id',
'class',
'active',
'tag',
'itemtitle' ] as $k ) {
2472 if ( isset( $item[
'id'] ) && !isset( $item[
'single-id'] ) ) {
2476 $link[
'single-id'] = $item[
'id'];
2478 if ( isset( $link[
'link-class'] ) ) {
2481 $link[
'class'] = $link[
'link-class'];
2482 unset( $link[
'link-class'] );
2488 foreach ( [
'id',
'class' ] as $attr ) {
2489 if ( isset( $item[$attr] ) ) {
2490 $attrs[$attr] = $item[$attr];
2493 if ( isset( $item[
'active'] ) && $item[
'active'] ) {
2494 if ( !isset( $attrs[
'class'] ) ) {
2495 $attrs[
'class'] =
'';
2499 if ( is_array( $attrs[
'class'] ) ) {
2500 $attrs[
'class'][] =
'active';
2502 $attrs[
'class'] .=
' active';
2503 $attrs[
'class'] = trim( $attrs[
'class'] );
2506 if ( isset( $item[
'itemtitle'] ) ) {
2507 $attrs[
'title'] = $item[
'itemtitle'];
2519 $autoCapHint = $this->
getConfig()->get(
'CapitalLinks' );
2523 'placeholder' => $this->
msg(
'searchsuggest-search' )->text(),
2526 'autocapitalize' => $autoCapHint ?
'sentences' :
'none',
2547 'value' => $this->
msg( $mode ==
'go' ?
'searcharticle' :
'searchbutton' )->text(),
2549 $realAttrs = array_merge(
2560 $buttonAttrs = array_merge(
2565 unset( $buttonAttrs[
'src'] );
2566 unset( $buttonAttrs[
'alt'] );
2567 unset( $buttonAttrs[
'width'] );
2568 unset( $buttonAttrs[
'height'] );
2570 'src' => $attrs[
'src'],
2571 'alt' => $attrs[
'alt'] ?? $this->
msg(
'searchbutton' )->text(),
2572 'width' => $attrs[
'width'] ??
null,
2573 'height' => $attrs[
'height'] ??
null,
2577 throw new MWException(
'Unknown mode passed to BaseTemplate::makeSearchButton' );
2594 $this->getHookRunner()->onSkinAfterPortlet( $this, $name, $html );
2607 if ( $subpagestr !==
'' ) {
2608 $subpagestr =
'<span class="subpages">' . $subpagestr .
'</span>';
2610 return $subpagestr . $out->getSubtitle();
2626 $title = $out->getTitle();
2627 $titleExists =
$title->exists();
2629 $maxCredits = $config->get(
'MaxCredits' );
2630 $showCreditsIfMax = $config->get(
'ShowCreditsIfMax' );
2631 $useCredits = $titleExists
2632 && $out->isArticle()
2633 && $out->isRevisionCurrent()
2634 && $maxCredits !== 0;
2637 if ( $useCredits ) {
2642 '@phan-var CreditsAction $action';
2645 'lastmod' => !$useCredits ? $this->
lastModified() :
null,
2646 'numberofwatchingusers' =>
null,
2647 'credits' => $useCredits ?
2648 $action->getCredits( $maxCredits, $showCreditsIfMax ) :
null,
2649 'copyright' => $titleExists &&
2654 foreach ( $data as $key => $existingItems ) {
2656 $this->getHookRunner()->onSkinAddFooterLinks( $this, $key, $newItems );
2657 $data[$key] = $existingItems + $newItems;