24 use Wikimedia\WrappedString;
25 use Wikimedia\WrappedStringList;
58 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
59 return $skinFactory->getSkinNames();
70 foreach ( self::getSkinNames() as $skinKey => $skinName ) {
71 $messages[] =
"skinname-$skinKey";
89 unset( $allowedSkins[$skip] );
110 $skinNames = array_change_key_case( $skinNames, CASE_LOWER );
111 $key = strtolower( $key );
115 if ( $key ==
'' || $key ==
'default' ) {
121 if ( isset( $skinNames[$key] ) ) {
136 if ( isset( $skinNames[$key] ) ) {
138 } elseif ( isset( $skinNames[$defaultSkin] ) ) {
141 return $fallbackSkin;
192 'mediawiki.legacy.shared',
193 'mediawiki.legacy.commonPrint',
200 'mediawiki.page.startup',
204 'mediawiki.page.ready',
208 'mediawiki.searchSuggest',
219 if ( strpos( $out->getHTML(),
'sortable' ) !==
false ) {
220 $modules[
'content'][] =
'jquery.tablesorter';
221 $modules[
'styles'][
'content'][] =
'jquery.tablesorter.styles';
225 if ( strpos( $out->getHTML(),
'mw-collapsible' ) !==
false ) {
226 $modules[
'content'][] =
'jquery.makeCollapsible';
227 $modules[
'styles'][
'content'][] =
'jquery.makeCollapsible.styles';
232 if ( strpos( $out->getHTML(),
'mw-ui-button' ) !==
false ) {
233 $modules[
'styles'][
'content'][] =
'mediawiki.ui.button';
236 if ( $out->isTOCEnabled() ) {
237 $modules[
'content'][] =
'mediawiki.toc';
238 $modules[
'styles'][
'content'][] =
'mediawiki.toc.styles';
242 if ( $user->isLoggedIn()
243 && MediaWikiServices::getInstance()
244 ->getPermissionManager()
245 ->userHasAllRights( $user,
'writeapi',
'viewmywatchlist',
'editmywatchlist' )
248 $modules[
'watch'][] =
'mediawiki.page.watch.ajax';
251 if ( $user->getBoolOption(
'editsectiononrightclick' ) ) {
252 $modules[
'user'][] =
'mediawiki.action.view.rightClickEdit';
256 if ( $out->isArticle() && $user->getOption(
'editondblclick' ) ) {
257 $modules[
'user'][] =
'mediawiki.action.view.dblClickEdit';
260 if ( $out->isSyndicated() ) {
261 $modules[
'styles'][
'syndicate'][] =
'mediawiki.feedlink';
275 if ( $user->isLoggedIn() ) {
276 $titles[] = $user->getUserPage();
277 $titles[] = $user->getTalkPage();
282 if (
$title->canExist() &&
$title->canHaveTalkPage() ) {
283 if (
$title->isTalkPage() ) {
284 $titles[] =
$title->getSubjectPage();
286 $titles[] =
$title->getTalkPage();
301 Hooks::run(
'SkinPreloadExistence', [ &$titles, $this ] );
305 $lb->setCaller( __METHOD__ );
317 return $this->
getOutput()->getRevisionId();
327 return $this->
getOutput()->isRevisionCurrent();
336 $this->mRelevantTitle =
$t;
350 return $this->mRelevantTitle ?? $this->
getTitle();
359 $this->mRelevantUser = $u;
371 if ( isset( $this->mRelevantUser ) ) {
376 $rootUser =
$title->getRootText();
383 $user->load( User::READ_NORMAL );
385 if ( $user->isLoggedIn() ) {
386 $this->mRelevantUser = $user;
407 return ResourceLoader::makeInlineScript(
408 ResourceLoader::makeConfigSetScript( $data ),
424 'ctype' =>
'text/css',
444 $numeric =
'ns-' .
$title->getNamespace();
447 if (
$title->isSpecialPage() ) {
448 $type =
'ns-special';
450 list( $canonicalName ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
451 resolveAlias(
$title->getDBkey() );
452 if ( $canonicalName ) {
453 $type .=
' ' . Sanitizer::escapeClass(
"mw-special-$canonicalName" );
455 $type .=
' mw-invalidspecialpage';
458 if (
$title->isTalkPage() ) {
461 $type =
'ns-subject';
464 if ( MediaWikiServices::getInstance()->getPermissionManager()
465 ->quickUserCan(
'edit', $user,
$title )
467 $type .=
' mw-editable';
471 $name = Sanitizer::escapeClass(
'page-' .
$title->getPrefixedText() );
472 $root = Sanitizer::escapeClass(
'rootpage-' .
$title->getRootTitle()->getPrefixedText() );
474 return "$numeric $type $name $root";
484 'lang' =>
$lang->getHtmlCode(),
485 'dir' =>
$lang->getDir(),
486 'class' =>
'client-nojs',
506 return $this->
getConfig()->get(
'Logo' );
527 $allCats = $out->getCategoryLinks();
528 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
530 if ( $allCats === [] ) {
538 $colon = $this->
msg(
'colon-separator' )->escaped();
540 if ( !empty( $allCats[
'normal'] ) ) {
541 $t = $embed . implode( $pop . $embed, $allCats[
'normal'] ) . $pop;
543 $msg = $this->
msg(
'pagecategories' )->numParams( count( $allCats[
'normal'] ) );
544 $linkPage = $this->
msg(
'pagecategorieslink' )->inContentLanguage()->text();
546 $link =
$title ? $linkRenderer->makeLink(
$title, $msg->text() ) : $msg->escaped();
547 $s .=
'<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
548 $link . $colon .
'<ul>' .
$t .
'</ul></div>';
552 if ( isset( $allCats[
'hidden'] ) ) {
553 if ( $this->
getUser()->getBoolOption(
'showhiddencats' ) ) {
554 $class =
' mw-hidden-cats-user-shown';
556 $class =
' mw-hidden-cats-ns-shown';
558 $class =
' mw-hidden-cats-hidden';
561 $s .=
"<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
562 $this->
msg(
'hidden-categories' )->numParams( count( $allCats[
'hidden'] ) )->escaped() .
563 $colon .
'<ul>' . $embed . implode( $pop . $embed, $allCats[
'hidden'] ) . $pop .
'</ul>' .
567 # optional 'dmoz-like' category browser. Will be shown under the list
568 # of categories an article belong to
569 if ( $this->
getConfig()->
get(
'UseCategoryBrowser' ) ) {
570 $s .=
'<br /><hr />';
572 # get a big array of the parents tree
573 $parenttree = $this->
getTitle()->getParentCategoryTree();
574 # Skin object passed by reference cause it can not be
575 # accessed under the method subfunction drawCategoryBrowser
577 # Clean out bogus first entry and sort them
578 unset( $tempout[0] );
580 # Output one per line
581 $s .= implode(
"<br />\n", $tempout );
594 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
596 foreach ( $tree as $element => $parent ) {
597 if ( empty( $parent ) ) {
598 # element start a new list
601 # grab the others elements
605 # add our current element to the list
607 $return .= $linkRenderer->makeLink( $eltitle, $eltitle->getText() );
621 $allCats = $out->getCategoryLinks();
622 $showHidden = $this->
getUser()->getBoolOption(
'showhiddencats' ) ||
625 $classes = [
'catlinks' ];
626 if ( empty( $allCats[
'normal'] ) && !( !empty( $allCats[
'hidden'] ) && $showHidden ) ) {
627 $classes[] =
'catlinks-allhidden';
630 return Html::rawElement(
632 [
'id' =>
'catlinks',
'class' => $classes,
'data-mw' =>
'interface' ],
654 if (
Hooks::run(
'SkinAfterContent', [ &$data, $this ] ) ) {
657 if ( trim( $data ) !=
'' ) {
661 $data =
"<div id='mw-data-after-content'>\n" .
666 wfDebug(
"Hook SkinAfterContent changed output processing.\n" );
690 $chunks = [ $this->
getOutput()->getBottomScripts() ];
695 Hooks::run(
'SkinAfterBottomScripts', [ $this, &$extraHtml ] );
696 if ( $extraHtml !==
'' ) {
697 $chunks[] = $extraHtml;
699 return WrappedString::join(
"\n", $chunks );
709 $oldid = $this->
getOutput()->getRevisionId();
711 $canonicalUrl = $this->
getTitle()->getCanonicalURL(
'oldid=' . $oldid );
712 $url = htmlspecialchars(
wfExpandIRI( $canonicalUrl ) );
718 return $this->
msg(
'retrievedfrom' )
719 ->rawParams(
'<a dir="ltr" href="' . $url .
'">' . $url .
'</a>' )
727 $action = $this->
getRequest()->getVal(
'action',
'view' );
729 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
730 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
732 if ( ( !
$title->exists() || $action ==
'history' ) &&
733 $permissionManager->quickUserCan(
'deletedhistory', $this->getUser(),
$title )
738 if ( $permissionManager->quickUserCan(
'undelete',
739 $this->getUser(), $this->
getTitle() )
741 $msg =
'thisisdeleted';
743 $msg =
'viewdeleted';
746 $subtitle = $this->
msg( $msg )->rawParams(
747 $linkRenderer->makeKnownLink(
749 $this->
msg(
'restorelink' )->numParams( $n )->text() )
758 . $this->
msg(
'word-separator' )->escaped()
759 . $this->
msg(
'parentheses' )
760 ->rawParams( $this->
getLanguage()->pipeList( $links ) )
763 return Html::rawElement(
'div', [
'class' =>
'mw-undelete-subtitle' ], $subtitle );
775 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
776 if ( $out ===
null ) {
779 $title = $out->getTitle();
782 if ( !
Hooks::run(
'SkinSubPageSubtitle', [ &$subpages, $this, $out ] ) ) {
787 $out->isArticle() && MediaWikiServices::getInstance()->getNamespaceInfo()->
788 hasSubpages(
$title->getNamespace() )
790 $ptext =
$title->getPrefixedText();
791 if ( strpos( $ptext,
'/' ) !==
false ) {
792 $links = explode(
'/', $ptext );
799 foreach ( $links as $link ) {
800 $growinglink .= $link;
804 if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
805 $getlink = $linkRenderer->makeKnownLink(
812 $subpages .=
$lang->getDirMarkEntity() . $this->
msg(
'pipe-separator' )->escaped();
814 $subpages .=
'< ';
817 $subpages .= $getlink;
835 return $searchPage->getLocalURL();
852 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
853 if (
$type ==
'detect' ) {
855 && !$this->
msg(
'history_copyright' )->inContentLanguage()->isDisabled()
863 if (
$type ==
'history' ) {
864 $msg =
'history_copyright';
871 if ( $config->get(
'RightsPage' ) ) {
873 $link = $linkRenderer->makeKnownLink(
876 } elseif ( $config->get(
'RightsUrl' ) ) {
878 } elseif ( $config->get(
'RightsText' ) ) {
879 $link = $config->get(
'RightsText' );
890 'SkinCopyrightFooter',
894 return $this->
msg( $msg )->rawParams( $link )->text();
904 $footerIcons = $config->get(
'FooterIcons' );
905 if ( $footerIcons[
'copyright'][
'copyright'] ) {
906 $out = $footerIcons[
'copyright'][
'copyright'];
907 } elseif ( $config->get(
'RightsIcon' ) ) {
908 $icon = htmlspecialchars( $config->get(
'RightsIcon' ) );
909 $url = $config->get(
'RightsUrl' );
912 $out .=
'<a href="' . htmlspecialchars( $url ) .
'">';
915 $text = htmlspecialchars( $config->get(
'RightsText' ) );
916 $out .=
"<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
931 $resourceBasePath = $this->
getConfig()->get(
'ResourceBasePath' );
932 $url1 = htmlspecialchars(
933 "$resourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
935 $url1_5 = htmlspecialchars(
936 "$resourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
938 $url2 = htmlspecialchars(
939 "$resourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
941 $text =
'<a href="https://www.mediawiki.org/"><img src="' . $url1
942 .
'" srcset="' . $url1_5 .
' 1.5x, ' . $url2 .
' 2x" '
943 .
'height="31" width="88" alt="Powered by MediaWiki" /></a>';
944 Hooks::run(
'SkinGetPoweredBy', [ &$text, $this ] );
954 $timestamp = $this->
getOutput()->getRevisionTimestamp();
956 # No cached timestamp, load it from the database
957 if ( $timestamp ===
null ) {
965 $s =
' ' . $this->
msg(
'lastmodifiedat', $d,
$t )->parse();
970 if ( MediaWikiServices::getInstance()->getDBLoadBalancer()->getLaggedReplicaMode() ) {
971 $s .=
' <strong>' . $this->
msg(
'laggedslavemode' )->parse() .
'</strong>';
982 if ( $align !=
'' ) {
983 $a =
" style='float: {$align};'";
988 $mp = $this->
msg(
'mainpage' )->escaped();
990 $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) :
'' );
993 $s =
"<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
1007 if ( is_string( $icon ) ) {
1010 $url = $icon[
"url"] ??
null;
1011 unset( $icon[
"url"] );
1012 if ( isset( $icon[
"src"] ) && $withImage ===
'withImage' ) {
1014 $html = Html::element(
'img', $icon );
1016 $html = htmlspecialchars( $icon[
"alt"] );
1019 $html = Html::rawElement(
'a',
1020 [
"href" => $url,
"target" => $this->
getConfig()->
get(
'ExternalLinkTarget' ) ],
1032 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1033 $s = $linkRenderer->makeKnownLink(
1035 $this->
msg(
'mainpage' )->text()
1049 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1054 return $linkRenderer->makeKnownLink(
1056 $this->
msg( $desc )->text()
1067 if ( $this->
msg( $desc )->inContentLanguage()->isDisabled() ) {
1084 return $this->
footerLink(
'privacy',
'privacypage' );
1092 return $this->
footerLink(
'aboutsite',
'aboutpage' );
1100 return $this->
footerLink(
'disclaimers',
'disclaimerpage' );
1111 $options = [
'action' =>
'edit' ];
1125 if ( $id instanceof
User ) {
1131 # The sending user must have a confirmed email address and the receiving
1132 # user must accept emails from the sender.
1133 return $this->
getUser()->canSendEmail()
1149 if ( $this->stylename ===
null ) {
1150 $class = static::class;
1151 throw new MWException(
"$class::\$stylename must be set to use getSkinStylePath()" );
1154 return $this->
getConfig()->get(
'StylePath' ) .
"/{$this->stylename}/$name";
1167 return $title->getLinkURL( $urlaction );
1183 if ( is_null( $proto ) ) {
1184 return $title->getLocalURL( $urlaction );
1186 return $title->getFullURL( $urlaction,
false, $proto );
1198 return $title->getLocalURL( $urlaction );
1209 return $title->getLocalURL( $urlaction );
1217 static function makeUrl( $name, $urlaction =
'' ) {
1221 return $title->getLocalURL( $urlaction );
1249 return $title->getLocalURL( $urlaction );
1263 'href' =>
$title->getLocalURL( $urlaction ),
1264 'exists' =>
$title->isKnown(),
1279 'href' =>
$title->getLocalURL( $urlaction ),
1291 if ( !is_object(
$title ) ) {
1293 if ( !is_object(
$title ) ) {
1321 $services = MediaWikiServices::getInstance();
1322 $callback =
function ( $old =
null, &$ttl = null ) {
1325 Hooks::run(
'SkinBuildSidebar', [ $this, &$bar ] );
1326 $msgCache = MediaWikiServices::getInstance()->getMessageCache();
1327 if ( $msgCache->isDisabled() ) {
1328 $ttl = WANObjectCache::TTL_UNCACHEABLE;
1334 $msgCache = $services->getMessageCache();
1335 $wanCache = $services->getMainWANObjectCache();
1338 $sidebar = $config->get(
'EnableSidebarCache' )
1339 ? $wanCache->getWithSetCallback(
1340 $wanCache->makeKey(
'sidebar', $this->getLanguage()->getCode() ),
1341 $config->get(
'SidebarCacheExpiry' ),
1347 $msgCache->getCheckKey( $this->
getLanguage()->getCode() )
1355 Hooks::run(
'SidebarBeforeOutput', [ $this, &$sidebar ] );
1381 $lines = explode(
"\n", $text );
1385 $messageTitle = $config->get(
'EnableSidebarCache' )
1389 if ( strpos(
$line,
'*' ) !== 0 ) {
1394 if ( strpos(
$line,
'**' ) !== 0 ) {
1395 $heading = trim(
$line,
'* ' );
1396 if ( !array_key_exists( $heading, $bar ) ) {
1397 $bar[$heading] = [];
1402 if ( strpos(
$line,
'|' ) !==
false ) {
1404 $line = array_map(
'trim', explode(
'|',
$line, 2 ) );
1405 if ( count(
$line ) !== 2 ) {
1413 $msgLink = $this->
msg( $line[0] )->title( $messageTitle )->inContentLanguage();
1414 if ( $msgLink->exists() ) {
1415 $link = $msgLink->text();
1416 if ( $link ==
'-' ) {
1422 $msgText = $this->
msg( $line[1] )->title( $messageTitle );
1423 if ( $msgText->exists() ) {
1424 $text = $msgText->text();
1433 if ( $config->get(
'NoFollowLinks' ) &&
1436 $extraAttribs[
'rel'] =
'nofollow';
1439 if ( $config->get(
'ExternalLinkTarget' ) ) {
1440 $extraAttribs[
'target'] = $config->get(
'ExternalLinkTarget' );
1447 $href =
$title->getLinkURL();
1449 $href =
'INVALID-TITLE';
1453 $bar[$heading][] = array_merge( [
1456 'id' => Sanitizer::escapeIdForAttribute(
'n-' . strtr(
$line[1],
' ',
'-' ) ),
1474 $newMessagesAlert =
'';
1476 $newtalks = $user->getNewMessageLinks();
1478 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1481 if ( !
Hooks::run(
'GetNewMessagesAlert', [ &$newMessagesAlert, $newtalks, $user, $out ] ) ) {
1484 if ( $newMessagesAlert ) {
1485 return $newMessagesAlert;
1489 $uTalkTitle = $user->getTalkPage();
1490 $lastSeenRev = $newtalks[0][
'rev'] ??
null;
1492 if ( $lastSeenRev !==
null ) {
1495 if ( $latestRev !==
null ) {
1497 $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
1498 $nofAuthors = $uTalkTitle->countAuthorsBetween(
1499 $lastSeenRev, $latestRev, 10,
'include_new' );
1505 $plural = $plural ? 999 : 1;
1509 $newMessagesLink = $linkRenderer->makeKnownLink(
1511 $this->
msg(
'newmessageslinkplural' )->params( $plural )->text(),
1513 $uTalkTitle->isRedirect() ? [
'redirect' =>
'no' ] : []
1516 $newMessagesDiffLink = $linkRenderer->makeKnownLink(
1518 $this->
msg(
'newmessagesdifflinkplural' )->params( $plural )->text(),
1520 $lastSeenRev !==
null
1521 ? [
'oldid' => $lastSeenRev->getId(),
'diff' =>
'cur' ]
1522 : [
'diff' =>
'cur' ]
1525 if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
1526 $newMessagesAlert = $this->
msg(
1527 'youhavenewmessagesfromusers',
1529 $newMessagesDiffLink
1530 )->numParams( $nofAuthors, $plural );
1533 $newMessagesAlert = $this->
msg(
1534 $nofAuthors > 10 ?
'youhavenewmessagesmanyusers' :
'youhavenewmessages',
1536 $newMessagesDiffLink
1537 )->numParams( $plural );
1539 $newMessagesAlert = $newMessagesAlert->text();
1541 $out->setCdnMaxage( 0 );
1542 } elseif ( count( $newtalks ) ) {
1543 $sep = $this->
msg(
'newtalkseparator' )->escaped();
1546 foreach ( $newtalks as $newtalk ) {
1549 [
'href' => $newtalk[
'link'] ], $newtalk[
'wiki']
1552 $parts = implode( $sep, $msgs );
1553 $newMessagesAlert = $this->
msg(
'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
1554 $out->setCdnMaxage( 0 );
1557 return $newMessagesAlert;
1570 if ( $name ===
'default' ) {
1572 $notice = $config->get(
'SiteNotice' );
1573 if ( empty( $notice ) ) {
1577 $msg = $this->
msg( $name )->inContentLanguage();
1578 if ( $msg->isBlank() ) {
1580 } elseif ( $msg->isDisabled() ) {
1583 $notice = $msg->plain();
1586 $services = MediaWikiServices::getInstance();
1587 $cache = $services->getMainWANObjectCache();
1588 $parsed =
$cache->getWithSetCallback(
1591 $cache->makeKey( $name, $config->get(
'RenderHashAppend' ), md5( $notice ) ),
1594 function () use ( $notice ) {
1595 return $this->
getOutput()->parseAsInterface( $notice );
1599 $contLang = $services->getContentLanguage();
1600 return Html::rawElement(
1603 'id' =>
'localNotice',
1604 'lang' => $contLang->getHtmlCode(),
1605 'dir' => $contLang->getDir()
1619 if (
Hooks::run(
'SiteNoticeBefore', [ &$siteNotice, $this ] ) ) {
1620 if ( is_object( $this->
getUser() ) && $this->
getUser()->isLoggedIn() ) {
1624 if ( $anonNotice ===
false ) {
1627 $siteNotice = $anonNotice;
1630 if ( $siteNotice ===
false ) {
1635 Hooks::run(
'SiteNoticeAfter', [ &$siteNotice, $this ] );
1657 if ( !is_null( $tooltip ) ) {
1658 $attribs[
'title'] = $this->
msg(
'editsectionhint' )->rawParams( $tooltip )
1659 ->inLanguage(
$lang )->text();
1664 'text' => $this->
msg(
'editsection' )->inLanguage( $lang )->text(),
1665 'targetTitle' => $nt,
1666 'attribs' => $attribs,
1667 'query' => [
'action' =>
'edit',
'section' => $section ]
1671 Hooks::run(
'SkinEditSectionLinks', [ $this, $nt, $section, $tooltip, &$links,
$lang ] );
1673 $result =
'<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
1675 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1677 foreach ( $links as $k => $linkDetails ) {
1678 $linksHtml[] = $linkRenderer->makeKnownLink(
1679 $linkDetails[
'targetTitle'],
1680 $linkDetails[
'text'],
1681 $linkDetails[
'attribs'],
1682 $linkDetails[
'query']
1687 '<span class="mw-editsection-divider">'
1688 . $this->
msg(
'pipe-separator' )->inLanguage(
$lang )->escaped()
1693 $result .=
'<span class="mw-editsection-bracket">]</span></span>';