MediaWiki REL1_35
Skin.php
Go to the documentation of this file.
1<?php
23use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
26use Wikimedia\WrappedString;
27use Wikimedia\WrappedStringList;
28
41abstract class Skin extends ContextSource {
42 use ProtectedHookAccessorTrait;
43
47 protected $skinname = null;
48
52 protected $options = [];
53 protected $mRelevantTitle = null;
54 protected $mRelevantUser = null;
55
60 public $stylename = null;
61
66 public static function getSkinNames() {
67 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
68 return $skinFactory->getSkinNames();
69 }
70
78 public static function getAllowedSkins() {
79 global $wgSkipSkins;
80
81 $allowedSkins = self::getSkinNames();
82
83 // Internal skins not intended for general use
84 unset( $allowedSkins['fallback'] );
85 unset( $allowedSkins['apioutput'] );
86
87 foreach ( $wgSkipSkins as $skip ) {
88 unset( $allowedSkins[$skip] );
89 }
90
91 return $allowedSkins;
92 }
93
103 public static function normalizeKey( $key ) {
105
106 $skinNames = self::getSkinNames();
107
108 // Make keys lowercase for case-insensitive matching.
109 $skinNames = array_change_key_case( $skinNames, CASE_LOWER );
110 $key = strtolower( $key );
111 $defaultSkin = strtolower( $wgDefaultSkin );
112 $fallbackSkin = strtolower( $wgFallbackSkin );
113
114 if ( $key == '' || $key == 'default' ) {
115 // Don't return the default immediately;
116 // in a misconfiguration we need to fall back.
117 $key = $defaultSkin;
118 }
119
120 if ( isset( $skinNames[$key] ) ) {
121 return $key;
122 }
123
124 // Older versions of the software used a numeric setting
125 // in the user preferences.
126 $fallback = [
127 0 => $defaultSkin,
128 2 => 'cologneblue'
129 ];
130
131 if ( isset( $fallback[$key] ) ) {
132 $key = $fallback[$key];
133 }
134
135 if ( isset( $skinNames[$key] ) ) {
136 return $key;
137 } elseif ( isset( $skinNames[$defaultSkin] ) ) {
138 return $defaultSkin;
139 } else {
140 return $fallbackSkin;
141 }
142 }
143
152 public function __construct( $options = null ) {
153 if ( is_string( $options ) ) {
154 $this->skinname = $options;
155 } elseif ( $options ) {
156 $this->options = $options;
157 $name = $options['name'] ?? null;
158 // Note: skins might override the public $skinname method instead
159 if ( $name ) {
160 $this->skinname = $name;
161 }
162 }
163 }
164
168 public function getSkinName() {
169 return $this->skinname;
170 }
171
176 public function initPage( OutputPage $out ) {
177 $this->preloadExistence();
178 }
179
191 public function getDefaultModules() {
192 $out = $this->getOutput();
193 $user = $this->getUser();
194
195 // Modules declared in the $modules literal are loaded
196 // for ALL users, on ALL pages, in ALL skins.
197 // Keep this list as small as possible!
198 $modules = [
199 // The 'styles' key sets render-blocking style modules
200 // Unlike other keys in $modules, this is an associative array
201 // where each key is its own group pointing to a list of modules
202 'styles' => [
203 'skin' => $this->options['styles'] ?? [],
204 'core' => [],
205 'content' => [],
206 'syndicate' => [],
207 ],
208 'core' => [
209 'site',
210 'mediawiki.page.startup',
211 ],
212 // modules that enhance the content in some way
213 'content' => [
214 'mediawiki.page.ready',
215 ],
216 // modules relating to search functionality
217 'search' => [],
218 // Skins can register their own scripts
219 'skin' => $this->options['scripts'] ?? [],
220 // modules relating to functionality relating to watching an article
221 'watch' => [],
222 // modules which relate to the current users preferences
223 'user' => [],
224 // modules relating to RSS/Atom Feeds
225 'syndicate' => [],
226 ];
227
228 // Preload jquery.tablesorter for mediawiki.page.ready
229 if ( strpos( $out->getHTML(), 'sortable' ) !== false ) {
230 $modules['content'][] = 'jquery.tablesorter';
231 $modules['styles']['content'][] = 'jquery.tablesorter.styles';
232 }
233
234 // Preload jquery.makeCollapsible for mediawiki.page.ready
235 if ( strpos( $out->getHTML(), 'mw-collapsible' ) !== false ) {
236 $modules['content'][] = 'jquery.makeCollapsible';
237 $modules['styles']['content'][] = 'jquery.makeCollapsible.styles';
238 }
239
240 // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button
241 // on every page is deprecated. Express a dependency instead.
242 if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) {
243 $modules['styles']['content'][] = 'mediawiki.ui.button';
244 }
245
246 if ( $out->isTOCEnabled() ) {
247 $modules['content'][] = 'mediawiki.toc';
248 $modules['styles']['content'][] = 'mediawiki.toc.styles';
249 }
250
251 $prefMgr = MediaWikiServices::getInstance()->getPermissionManager();
252 if ( $user->isLoggedIn()
253 && $prefMgr->userHasAllRights( $user, 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
254 && $this->getRelevantTitle()->canExist()
255 ) {
256 $modules['watch'][] = 'mediawiki.page.watch.ajax';
257 }
258
259 if ( $user->getBoolOption( 'editsectiononrightclick' )
260 || ( $out->isArticle() && $user->getOption( 'editondblclick' ) )
261 ) {
262 $modules['user'][] = 'mediawiki.misc-authed-pref';
263 }
264
265 if ( $out->isSyndicated() ) {
266 $modules['styles']['syndicate'][] = 'mediawiki.feedlink';
267 }
268
269 return $modules;
270 }
271
275 protected function preloadExistence() {
276 $titles = [];
277
278 // User/talk link
279 $user = $this->getUser();
280 if ( $user->isLoggedIn() ) {
281 $titles[] = $user->getUserPage();
282 $titles[] = $user->getTalkPage();
283 }
284
285 // Check, if the page can hold some kind of content, otherwise do nothing
286 $title = $this->getRelevantTitle();
287 if ( $title->canExist() && $title->canHaveTalkPage() ) {
288 $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
289 if ( $title->isTalkPage() ) {
290 $titles[] = $namespaceInfo->getSubjectPage( $title );
291 } else {
292 $titles[] = $namespaceInfo->getTalkPage( $title );
293 }
294 }
295
296 // Footer links (used by SkinTemplate::prepareQuickTemplate)
297 if ( $this->getConfig()->get( 'FooterLinkCacheExpiry' ) <= 0 ) {
298 $titles = array_merge(
299 $titles,
300 array_filter( [
301 $this->footerLinkTitle( 'privacy', 'privacypage' ),
302 $this->footerLinkTitle( 'aboutsite', 'aboutpage' ),
303 $this->footerLinkTitle( 'disclaimers', 'disclaimerpage' ),
304 ] )
305 );
306 }
307
308 $this->getHookRunner()->onSkinPreloadExistence( $titles, $this );
309
310 if ( $titles ) {
311 $lb = new LinkBatch( $titles );
312 $lb->setCaller( __METHOD__ );
313 $lb->execute();
314 }
315 }
316
323 public function getRevisionId() {
324 return $this->getOutput()->getRevisionId();
325 }
326
333 public function isRevisionCurrent() {
334 return $this->getOutput()->isRevisionCurrent();
335 }
336
342 public function setRelevantTitle( $t ) {
343 $this->mRelevantTitle = $t;
344 }
345
356 public function getRelevantTitle() {
357 return $this->mRelevantTitle ?? $this->getTitle();
358 }
359
365 public function setRelevantUser( $u ) {
366 $this->mRelevantUser = $u;
367 }
368
377 public function getRelevantUser() {
378 if ( isset( $this->mRelevantUser ) ) {
379 return $this->mRelevantUser;
380 }
381 $title = $this->getRelevantTitle();
382 if ( $title->hasSubjectNamespace( NS_USER ) ) {
383 $rootUser = $title->getRootText();
384 if ( User::isIP( $rootUser ) ) {
385 $this->mRelevantUser = User::newFromName( $rootUser, false );
386 } else {
387 $user = User::newFromName( $rootUser, false );
388
389 if ( $user ) {
390 $user->load( User::READ_NORMAL );
391
392 if ( $user->isLoggedIn() ) {
393 $this->mRelevantUser = $user;
394 }
395 }
396 }
397 return $this->mRelevantUser;
398 }
399 return null;
400 }
401
405 abstract public function outputPage();
406
412 public static function makeVariablesScript( $data, $nonce = null ) {
413 if ( $data ) {
416 $nonce
417 );
418 }
419 return '';
420 }
421
428 public static function getDynamicStylesheetQuery() {
429 wfDeprecated( __METHOD__, '1.32' );
430 return [
431 'action' => 'raw',
432 'ctype' => 'text/css',
433 ];
434 }
435
442 public function setupSkinUserCss( OutputPage $out ) {
443 // Stub.
444 }
445
451 public function getPageClasses( $title ) {
452 $numeric = 'ns-' . $title->getNamespace();
453 $user = $this->getUser();
454
455 if ( $title->isSpecialPage() ) {
456 $type = 'ns-special';
457 // T25315: provide a class based on the canonical special page name without subpages
458 list( $canonicalName ) = MediaWikiServices::getInstance()->getSpecialPageFactory()->
459 resolveAlias( $title->getDBkey() );
460 if ( $canonicalName ) {
461 $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
462 } else {
463 $type .= ' mw-invalidspecialpage';
464 }
465 } else {
466 if ( $title->isTalkPage() ) {
467 $type = 'ns-talk';
468 } else {
469 $type = 'ns-subject';
470 }
471 // T208315: add HTML class when the user can edit the page
472 if ( MediaWikiServices::getInstance()->getPermissionManager()
473 ->quickUserCan( 'edit', $user, $title )
474 ) {
475 $type .= ' mw-editable';
476 }
477 }
478
479 $name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
480 $root = Sanitizer::escapeClass( 'rootpage-' . $title->getRootTitle()->getPrefixedText() );
481
482 return "$numeric $type $name $root";
483 }
484
489 public function getHtmlElementAttributes() {
490 $lang = $this->getLanguage();
491 return [
492 'lang' => $lang->getHtmlCode(),
493 'dir' => $lang->getDir(),
494 'class' => 'client-nojs',
495 ];
496 }
497
503 protected function getLogo() {
505 }
506
510 public function getCategoryLinks() {
511 $out = $this->getOutput();
512 $allCats = $out->getCategoryLinks();
513 $title = $this->getTitle();
514 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
515
516 if ( $allCats === [] ) {
517 return '';
518 }
519
520 $embed = "<li>";
521 $pop = "</li>";
522
523 $s = '';
524 $colon = $this->msg( 'colon-separator' )->escaped();
525
526 if ( !empty( $allCats['normal'] ) ) {
527 $t = $embed . implode( $pop . $embed, $allCats['normal'] ) . $pop;
528
529 $msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) );
530 $linkPage = $this->msg( 'pagecategorieslink' )->inContentLanguage()->text();
531 $pageCategoriesLinkTitle = Title::newFromText( $linkPage );
532 if ( $pageCategoriesLinkTitle ) {
533 $link = $linkRenderer->makeLink( $pageCategoriesLinkTitle, $msg->text() );
534 } else {
535 $link = $msg->escaped();
536 }
537 $s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
538 $link . $colon . '<ul>' . $t . '</ul></div>';
539 }
540
541 # Hidden categories
542 if ( isset( $allCats['hidden'] ) ) {
543 if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
544 $class = ' mw-hidden-cats-user-shown';
545 } elseif ( $title->inNamespace( NS_CATEGORY ) ) {
546 $class = ' mw-hidden-cats-ns-shown';
547 } else {
548 $class = ' mw-hidden-cats-hidden';
549 }
550
551 $s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
552 $this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
553 $colon . '<ul>' . $embed . implode( $pop . $embed, $allCats['hidden'] ) . $pop . '</ul>' .
554 '</div>';
555 }
556
557 # optional 'dmoz-like' category browser. Will be shown under the list
558 # of categories an article belong to
559 if ( $this->getConfig()->get( 'UseCategoryBrowser' ) ) {
560 $s .= '<br /><hr />';
561
562 # get a big array of the parents tree
563 $parenttree = $title->getParentCategoryTree();
564 # Skin object passed by reference cause it can not be
565 # accessed under the method subfunction drawCategoryBrowser
566 $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
567 # Clean out bogus first entry and sort them
568 unset( $tempout[0] );
569 asort( $tempout );
570 # Output one per line
571 $s .= implode( "<br />\n", $tempout );
572 }
573
574 return $s;
575 }
576
582 protected function drawCategoryBrowser( $tree ) {
583 $return = '';
584 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
585
586 foreach ( $tree as $element => $parent ) {
587 if ( empty( $parent ) ) {
588 # element start a new list
589 $return .= "\n";
590 } else {
591 # grab the others elements
592 $return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
593 }
594
595 # add our current element to the list
596 $eltitle = Title::newFromText( $element );
597 $return .= $linkRenderer->makeLink( $eltitle, $eltitle->getText() );
598 }
599
600 return $return;
601 }
602
606 public function getCategories() {
607 $catlinks = $this->getCategoryLinks();
608 // Check what we're showing
609 $allCats = $this->getOutput()->getCategoryLinks();
610 $showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
611 $this->getTitle()->inNamespace( NS_CATEGORY );
612
613 $classes = [ 'catlinks' ];
614 if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
615 $classes[] = 'catlinks-allhidden';
616 }
617
618 return Html::rawElement(
619 'div',
620 [ 'id' => 'catlinks', 'class' => $classes, 'data-mw' => 'interface' ],
621 $catlinks
622 );
623 }
624
639 protected function afterContentHook() {
640 $data = '';
641
642 if ( $this->getHookRunner()->onSkinAfterContent( $data, $this ) ) {
643 // adding just some spaces shouldn't toggle the output
644 // of the whole <div/>, so we use trim() here
645 if ( trim( $data ) != '' ) {
646 // Doing this here instead of in the skins to
647 // ensure that the div has the same ID in all
648 // skins
649 $data = "<div id='mw-data-after-content'>\n" .
650 "\t$data\n" .
651 "</div>\n";
652 }
653 } else {
654 wfDebug( "Hook SkinAfterContent changed output processing." );
655 }
656
657 return $data;
658 }
659
667 protected function generateDebugHTML() {
668 wfDeprecated( __METHOD__, '1.35' );
669 return MWDebug::getHTMLDebugLog();
670 }
671
677 public function bottomScripts() {
678 // TODO and the suckage continues. This function is really just a wrapper around
679 // OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
680 // up at some point
681 $chunks = [ $this->getOutput()->getBottomScripts() ];
682
683 // Keep the hook appendage separate to preserve WrappedString objects.
684 // This enables BaseTemplate::getTrail() to merge them where possible.
685 $extraHtml = '';
686 $this->getHookRunner()->onSkinAfterBottomScripts( $this, $extraHtml );
687 if ( $extraHtml !== '' ) {
688 $chunks[] = $extraHtml;
689 }
690 return WrappedString::join( "\n", $chunks );
691 }
692
700 public function printSource() {
701 $title = $this->getTitle();
702 $oldid = $this->getOutput()->getRevisionId();
703 if ( $oldid ) {
704 $canonicalUrl = $title->getCanonicalURL( 'oldid=' . $oldid );
705 $url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) );
706 } else {
707 // oldid not available for non existing pages
708 $url = htmlspecialchars( wfExpandIRI( $title->getCanonicalURL() ) );
709 }
710
711 return $this->msg( 'retrievedfrom' )
712 ->rawParams( '<a dir="ltr" href="' . $url . '">' . $url . '</a>' )
713 ->parse();
714 }
715
719 public function getUndeleteLink() {
720 $action = $this->getRequest()->getVal( 'action', 'view' );
721 $title = $this->getTitle();
722 $user = $this->getUser();
723 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
724 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
725
726 if ( ( !$title->exists() || $action == 'history' ) &&
727 $permissionManager->quickUserCan( 'deletedhistory', $user, $title )
728 ) {
729 $n = $title->isDeleted();
730
731 if ( $n ) {
732 if ( $permissionManager->quickUserCan( 'undelete', $user, $title ) ) {
733 $msg = 'thisisdeleted';
734 } else {
735 $msg = 'viewdeleted';
736 }
737
738 $subtitle = $this->msg( $msg )->rawParams(
739 $linkRenderer->makeKnownLink(
740 SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() ),
741 $this->msg( 'restorelink' )->numParams( $n )->text() )
742 )->escaped();
743
744 $links = [];
745 // Add link to page logs, unless we're on the history page (which
746 // already has one)
747 if ( $action !== 'history' ) {
748 $links[] = $linkRenderer->makeKnownLink(
750 $this->msg( 'viewpagelogs-lowercase' )->text(),
751 [],
752 [ 'page' => $title->getPrefixedText() ]
753 );
754 }
755
756 // Allow extensions to add more links
757 $this->getHookRunner()->onUndeletePageToolLinks(
758 $this->getContext(), $linkRenderer, $links );
759
760 if ( $links ) {
761 $subtitle .= ''
762 . $this->msg( 'word-separator' )->escaped()
763 . $this->msg( 'parentheses' )
764 ->rawParams( $this->getLanguage()->pipeList( $links ) )
765 ->escaped();
766 }
767
768 return Html::rawElement( 'div', [ 'class' => 'mw-undelete-subtitle' ], $subtitle );
769 }
770 }
771
772 return '';
773 }
774
779 public function subPageSubtitle( $out = null ) {
780 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
781 $out = $out ?? $this->getOutput();
782 $title = $out->getTitle();
783 $subpages = '';
784
785 if ( !$this->getHookRunner()->onSkinSubPageSubtitle( $subpages, $this, $out ) ) {
786 return $subpages;
787 }
788
789 if (
790 $out->isArticle() && MediaWikiServices::getInstance()->getNamespaceInfo()->
791 hasSubpages( $title->getNamespace() )
792 ) {
793 $ptext = $title->getPrefixedText();
794 if ( strpos( $ptext, '/' ) !== false ) {
795 $links = explode( '/', $ptext );
796 array_pop( $links );
797 $c = 0;
798 $growinglink = '';
799 $display = '';
800 $lang = $this->getLanguage();
801
802 foreach ( $links as $link ) {
803 $growinglink .= $link;
804 $display .= $link;
805 $linkObj = Title::newFromText( $growinglink );
806
807 if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
808 $getlink = $linkRenderer->makeKnownLink(
809 $linkObj, $display
810 );
811
812 $c++;
813
814 if ( $c > 1 ) {
815 $subpages .= $lang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
816 } else {
817 $subpages .= '&lt; ';
818 }
819
820 $subpages .= $getlink;
821 $display = '';
822 } else {
823 $display .= '/';
824 }
825 $growinglink .= '/';
826 }
827 }
828 }
829
830 return $subpages;
831 }
832
836 protected function getSearchLink() {
837 $searchPage = SpecialPage::getTitleFor( 'Search' );
838 return $searchPage->getLocalURL();
839 }
840
845 public function getCopyright( $type = 'detect' ) {
846 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
847 if ( $type == 'detect' ) {
848 if ( !$this->getOutput()->isRevisionCurrent()
849 && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled()
850 ) {
851 $type = 'history';
852 } else {
853 $type = 'normal';
854 }
855 }
856
857 if ( $type == 'history' ) {
858 $msg = 'history_copyright';
859 } else {
860 $msg = 'copyright';
861 }
862
863 $config = $this->getConfig();
864
865 if ( $config->get( 'RightsPage' ) ) {
866 $title = Title::newFromText( $config->get( 'RightsPage' ) );
867 $link = $linkRenderer->makeKnownLink(
868 $title, new HtmlArmor( $config->get( 'RightsText' ) )
869 );
870 } elseif ( $config->get( 'RightsUrl' ) ) {
871 $link = Linker::makeExternalLink( $config->get( 'RightsUrl' ), $config->get( 'RightsText' ) );
872 } elseif ( $config->get( 'RightsText' ) ) {
873 $link = $config->get( 'RightsText' );
874 } else {
875 # Give up now
876 return '';
877 }
878
879 // Allow for site and per-namespace customization of copyright notice.
880 $this->getHookRunner()->onSkinCopyrightFooter( $this->getTitle(), $type, $msg, $link );
881
882 return $this->msg( $msg )->rawParams( $link )->text();
883 }
884
888 protected function getCopyrightIcon() {
889 $out = '';
890 $config = $this->getConfig();
891
892 $footerIcons = $config->get( 'FooterIcons' );
893 if ( $footerIcons['copyright']['copyright'] ) {
894 $out = $footerIcons['copyright']['copyright'];
895 } elseif ( $config->get( 'RightsIcon' ) ) {
896 $icon = htmlspecialchars( $config->get( 'RightsIcon' ) );
897 $url = $config->get( 'RightsUrl' );
898
899 if ( $url ) {
900 $out .= '<a href="' . htmlspecialchars( $url ) . '">';
901 }
902
903 $text = htmlspecialchars( $config->get( 'RightsText' ) );
904 $out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
905
906 if ( $url ) {
907 $out .= '</a>';
908 }
909 }
910
911 return $out;
912 }
913
918 protected function getPoweredBy() {
919 $resourceBasePath = $this->getConfig()->get( 'ResourceBasePath' );
920 $url1 = htmlspecialchars(
921 "$resourceBasePath/resources/assets/poweredby_mediawiki_88x31.png"
922 );
923 $url1_5 = htmlspecialchars(
924 "$resourceBasePath/resources/assets/poweredby_mediawiki_132x47.png"
925 );
926 $url2 = htmlspecialchars(
927 "$resourceBasePath/resources/assets/poweredby_mediawiki_176x62.png"
928 );
929 $text = '<a href="https://www.mediawiki.org/"><img src="' . $url1
930 . '" srcset="' . $url1_5 . ' 1.5x, ' . $url2 . ' 2x" '
931 . 'height="31" width="88" alt="Powered by MediaWiki" loading="lazy" /></a>';
932 $this->getHookRunner()->onSkinGetPoweredBy( $text, $this );
933 return $text;
934 }
935
941 protected function lastModified() {
942 $timestamp = $this->getOutput()->getRevisionTimestamp();
943 $user = $this->getUser();
944 $language = $this->getLanguage();
945
946 # No cached timestamp, load it from the database
947 if ( $timestamp === null ) {
948 $timestamp = MediaWikiServices::getInstance()
949 ->getRevisionLookup()
950 ->getTimestampFromId( $this->getOutput()->getRevisionId() );
951 }
952
953 if ( $timestamp ) {
954 $d = $language->userDate( $timestamp, $user );
955 $t = $language->userTime( $timestamp, $user );
956 $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->parse();
957 } else {
958 $s = '';
959 }
960
961 if ( MediaWikiServices::getInstance()->getDBLoadBalancer()->getLaggedReplicaMode() ) {
962 $s .= ' <strong>' . $this->msg( 'laggedreplicamode' )->parse() . '</strong>';
963 }
964
965 return $s;
966 }
967
972 public function logoText( $align = '' ) {
973 if ( $align != '' ) {
974 $a = " style='float: {$align};'";
975 } else {
976 $a = '';
977 }
978
979 $mp = $this->msg( 'mainpage' )->escaped();
980 $mptitle = Title::newMainPage();
981 $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
982
983 $logourl = $this->getLogo();
984 return "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
985 }
986
995 public function makeFooterIcon( $icon, $withImage = 'withImage' ) {
996 if ( is_string( $icon ) ) {
997 $html = $icon;
998 } else { // Assuming array
999 $url = $icon['url'] ?? null;
1000 unset( $icon['url'] );
1001 if ( isset( $icon['src'] ) && $withImage === 'withImage' ) {
1002 // Lazy-load footer icons, since they're not part of the printed view.
1003 $icon['loading'] = 'lazy';
1004 // do this the lazy way, just pass icon data as an attribute array
1005 $html = Html::element( 'img', $icon );
1006 } else {
1007 $html = htmlspecialchars( $icon['alt'] ?? '' );
1008 }
1009 if ( $url ) {
1010 $html = Html::rawElement( 'a',
1011 [ 'href' => $url, 'target' => $this->getConfig()->get( 'ExternalLinkTarget' ) ],
1012 $html );
1013 }
1014 }
1015 return $html;
1016 }
1017
1022 public function mainPageLink() {
1023 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1024 $s = $linkRenderer->makeKnownLink(
1025 Title::newMainPage(),
1026 $this->msg( 'mainpage' )->text()
1027 );
1028
1029 return $s;
1030 }
1031
1038 public function footerLink( $desc, $page ) {
1039 $title = $this->footerLinkTitle( $desc, $page );
1040 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1041 if ( !$title ) {
1042 return '';
1043 }
1044
1045 return $linkRenderer->makeKnownLink(
1046 $title,
1047 $this->msg( $desc )->text()
1048 );
1049 }
1050
1056 private function footerLinkTitle( $desc, $page ) {
1057 // If the link description has been set to "-" in the default language,
1058 if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
1059 // then it is disabled, for all languages.
1060 return null;
1061 }
1062 // Otherwise, we display the link for the user, described in their
1063 // language (which may or may not be the same as the default language),
1064 // but we make the link target be the one site-wide page.
1065 $title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
1066
1067 return $title ?: null;
1068 }
1069
1076 public function getSiteFooterLinks() {
1077 $callback = function () {
1078 return [
1079 'privacy' => $this->privacyLink(),
1080 'about' => $this->aboutLink(),
1081 'disclaimer' => $this->disclaimerLink()
1082 ];
1083 };
1084
1085 $services = MediaWikiServices::getInstance();
1086 $msgCache = $services->getMessageCache();
1087 $wanCache = $services->getMainWANObjectCache();
1088 $config = $this->getConfig();
1089
1090 return ( $config->get( 'FooterLinkCacheExpiry' ) > 0 )
1091 ? $wanCache->getWithSetCallback(
1092 $wanCache->makeKey( 'footer-links' ),
1093 $config->get( 'FooterLinkCacheExpiry' ),
1094 $callback,
1095 [
1096 'checkKeys' => [
1097 // Unless there is both no exact $code override nor an i18n definition
1098 // in the software, the only MediaWiki page to check is for $code.
1099 $msgCache->getCheckKey( $this->getLanguage()->getCode() )
1100 ],
1101 'lockTSE' => 30
1102 ]
1103 )
1104 : $callback();
1105 }
1106
1111 public function privacyLink() {
1112 return $this->footerLink( 'privacy', 'privacypage' );
1113 }
1114
1119 public function aboutLink() {
1120 return $this->footerLink( 'aboutsite', 'aboutpage' );
1121 }
1122
1127 public function disclaimerLink() {
1128 return $this->footerLink( 'disclaimers', 'disclaimerpage' );
1129 }
1130
1138 public function editUrlOptions() {
1139 $options = [ 'action' => 'edit' ];
1140 $out = $this->getOutput();
1141
1142 if ( !$out->isRevisionCurrent() ) {
1143 $options['oldid'] = intval( $out->getRevisionId() );
1144 }
1145
1146 return $options;
1147 }
1148
1153 public function showEmailUser( $id ) {
1154 if ( $id instanceof User ) {
1155 $targetUser = $id;
1156 } else {
1157 $targetUser = User::newFromId( $id );
1158 }
1159
1160 # The sending user must have a confirmed email address and the receiving
1161 # user must accept emails from the sender.
1162 return $this->getUser()->canSendEmail()
1163 && SpecialEmailUser::validateTarget( $targetUser, $this->getUser() ) === '';
1164 }
1165
1177 public function getSkinStylePath( $name ) {
1178 if ( $this->stylename === null ) {
1179 $class = static::class;
1180 throw new MWException( "$class::\$stylename must be set to use getSkinStylePath()" );
1181 }
1182
1183 return $this->getConfig()->get( 'StylePath' ) . "/{$this->stylename}/$name";
1184 }
1185
1186 /* these are used extensively in SkinTemplate, but also some other places */
1187
1192 public static function makeMainPageUrl( $urlaction = '' ) {
1193 $title = Title::newMainPage();
1194 self::checkTitle( $title, '' );
1195
1196 return $title->getLinkURL( $urlaction );
1197 }
1198
1210 public static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
1212 if ( $proto === null ) {
1213 return $title->getLocalURL( $urlaction );
1214 } else {
1215 return $title->getFullURL( $urlaction, false, $proto );
1216 }
1217 }
1218
1225 public static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
1226 $title = SpecialPage::getSafeTitleFor( $name, $subpage );
1227 return $title->getLocalURL( $urlaction );
1228 }
1229
1236 public static function makeI18nUrl( $name, $urlaction = '' ) {
1237 wfDeprecated( __METHOD__, '1.35' );
1238 $title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() );
1239 self::checkTitle( $title, $name );
1240 return $title->getLocalURL( $urlaction );
1241 }
1242
1248 public static function makeUrl( $name, $urlaction = '' ) {
1249 $title = Title::newFromText( $name );
1250 self::checkTitle( $title, $name );
1251
1252 return $title->getLocalURL( $urlaction );
1253 }
1254
1261 public static function makeInternalOrExternalUrl( $name ) {
1262 if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
1263 return $name;
1264 } else {
1265 return self::makeUrl( $name );
1266 }
1267 }
1268
1277 public static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
1278 wfDeprecated( __METHOD__, '1.35' );
1279 $title = Title::makeTitleSafe( $namespace, $name );
1280 self::checkTitle( $title, $name );
1281
1282 return $title->getLocalURL( $urlaction );
1283 }
1284
1291 protected static function makeUrlDetails( $name, $urlaction = '' ) {
1292 $title = Title::newFromText( $name );
1293 self::checkTitle( $title, $name );
1294
1295 return [
1296 'href' => $title->getLocalURL( $urlaction ),
1297 'exists' => $title->isKnown(),
1298 ];
1299 }
1300
1307 protected static function makeKnownUrlDetails( $name, $urlaction = '' ) {
1308 $title = Title::newFromText( $name );
1309 self::checkTitle( $title, $name );
1310
1311 return [
1312 'href' => $title->getLocalURL( $urlaction ),
1313 'exists' => true
1314 ];
1315 }
1316
1323 public static function checkTitle( &$title, $name ) {
1324 if ( !is_object( $title ) ) {
1325 $title = Title::newFromText( $name );
1326 if ( !is_object( $title ) ) {
1327 $title = Title::newFromText( '--error: link target missing--' );
1328 }
1329 }
1330 }
1331
1340 public function mapInterwikiToLanguage( $code ) {
1341 $map = $this->getConfig()->get( 'InterlanguageLinkCodeMap' );
1342 return $map[ $code ] ?? $code;
1343 }
1344
1353 public function getLanguages() {
1354 if ( $this->getConfig()->get( 'HideInterlanguageLinks' ) ) {
1355 return [];
1356 }
1357 $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
1358
1359 $userLang = $this->getLanguage();
1360 $languageLinks = [];
1361 $langNameUtils = MediaWikiServices::getInstance()->getLanguageNameUtils();
1362
1363 foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) {
1364 $class = 'interlanguage-link interwiki-' . explode( ':', $languageLinkText, 2 )[0];
1365
1366 $languageLinkTitle = Title::newFromText( $languageLinkText );
1367 if ( !$languageLinkTitle ) {
1368 continue;
1369 }
1370
1371 $ilInterwikiCode = $this->mapInterwikiToLanguage( $languageLinkTitle->getInterwiki() );
1372
1373 $ilLangName = $langNameUtils->getLanguageName( $ilInterwikiCode );
1374
1375 if ( strval( $ilLangName ) === '' ) {
1376 $ilDisplayTextMsg = $this->msg( "interlanguage-link-$ilInterwikiCode" );
1377 if ( !$ilDisplayTextMsg->isDisabled() ) {
1378 // Use custom MW message for the display text
1379 $ilLangName = $ilDisplayTextMsg->text();
1380 } else {
1381 // Last resort: fallback to the language link target
1382 $ilLangName = $languageLinkText;
1383 }
1384 } else {
1385 // Use the language autonym as display text
1386 $ilLangName = $this->getLanguage()->ucfirst( $ilLangName );
1387 }
1388
1389 // CLDR extension or similar is required to localize the language name;
1390 // otherwise we'll end up with the autonym again.
1391 $ilLangLocalName = $langNameUtils->getLanguageName(
1392 $ilInterwikiCode,
1393 $userLang->getCode()
1394 );
1395
1396 $languageLinkTitleText = $languageLinkTitle->getText();
1397 if ( $ilLangLocalName === '' ) {
1398 $ilFriendlySiteName = $this->msg( "interlanguage-link-sitename-$ilInterwikiCode" );
1399 if ( !$ilFriendlySiteName->isDisabled() ) {
1400 if ( $languageLinkTitleText === '' ) {
1401 $ilTitle = $this->msg(
1402 'interlanguage-link-title-nonlangonly',
1403 $ilFriendlySiteName->text()
1404 )->text();
1405 } else {
1406 $ilTitle = $this->msg(
1407 'interlanguage-link-title-nonlang',
1408 $languageLinkTitleText,
1409 $ilFriendlySiteName->text()
1410 )->text();
1411 }
1412 } else {
1413 // we have nothing friendly to put in the title, so fall back to
1414 // displaying the interlanguage link itself in the title text
1415 // (similar to what is done in page content)
1416 $ilTitle = $languageLinkTitle->getInterwiki() .
1417 ":$languageLinkTitleText";
1418 }
1419 } elseif ( $languageLinkTitleText === '' ) {
1420 $ilTitle = $this->msg(
1421 'interlanguage-link-title-langonly',
1422 $ilLangLocalName
1423 )->text();
1424 } else {
1425 $ilTitle = $this->msg(
1426 'interlanguage-link-title',
1427 $languageLinkTitleText,
1428 $ilLangLocalName
1429 )->text();
1430 }
1431
1432 $ilInterwikiCodeBCP47 = LanguageCode::bcp47( $ilInterwikiCode );
1433 $languageLink = [
1434 'href' => $languageLinkTitle->getFullURL(),
1435 'text' => $ilLangName,
1436 'title' => $ilTitle,
1437 'class' => $class,
1438 'link-class' => 'interlanguage-link-target',
1439 'lang' => $ilInterwikiCodeBCP47,
1440 'hreflang' => $ilInterwikiCodeBCP47,
1441 ];
1442 $hookContainer->run(
1443 'SkinTemplateGetLanguageLink',
1444 [ &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() ],
1445 []
1446 );
1447 $languageLinks[] = $languageLink;
1448 }
1449
1450 return $languageLinks;
1451 }
1452
1459 protected function buildNavUrls() {
1460 $out = $this->getOutput();
1461 $request = $this->getRequest();
1462 $title = $this->getTitle();
1463 $thispage = $title->getPrefixedDBkey();
1464 $uploadNavigationUrl = $this->getConfig()->get( 'UploadNavigationUrl' );
1465
1466 $nav_urls = [];
1467 $nav_urls['mainpage'] = [ 'href' => self::makeMainPageUrl() ];
1468 if ( $uploadNavigationUrl ) {
1469 $nav_urls['upload'] = [ 'href' => $uploadNavigationUrl ];
1470 } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) {
1471 $nav_urls['upload'] = [ 'href' => self::makeSpecialUrl( 'Upload' ) ];
1472 } else {
1473 $nav_urls['upload'] = false;
1474 }
1475 $nav_urls['specialpages'] = [ 'href' => self::makeSpecialUrl( 'Specialpages' ) ];
1476
1477 $nav_urls['print'] = false;
1478 $nav_urls['permalink'] = false;
1479 $nav_urls['info'] = false;
1480 $nav_urls['whatlinkshere'] = false;
1481 $nav_urls['recentchangeslinked'] = false;
1482 $nav_urls['contributions'] = false;
1483 $nav_urls['log'] = false;
1484 $nav_urls['blockip'] = false;
1485 $nav_urls['mute'] = false;
1486 $nav_urls['emailuser'] = false;
1487 $nav_urls['userrights'] = false;
1488
1489 // A print stylesheet is attached to all pages, but nobody ever
1490 // figures that out. :) Add a link...
1491 if ( !$out->isPrintable() && ( $out->isArticle() || $title->isSpecialPage() ) ) {
1492 $nav_urls['print'] = [
1493 'text' => $this->msg( 'printableversion' )->text(),
1494 'href' => 'javascript:print();'
1495 ];
1496 }
1497
1498 if ( $out->isArticle() ) {
1499 // Also add a "permalink" while we're at it
1500 $revid = $out->getRevisionId();
1501 if ( $revid ) {
1502 $nav_urls['permalink'] = [
1503 'text' => $this->msg( 'permalink' )->text(),
1504 'href' => $title->getLocalURL( "oldid=$revid" )
1505 ];
1506 }
1507 }
1508
1509 if ( $out->isArticleRelated() ) {
1510 $nav_urls['whatlinkshere'] = [
1511 'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $thispage )->getLocalURL()
1512 ];
1513
1514 $nav_urls['info'] = [
1515 'text' => $this->msg( 'pageinfo-toolboxlink' )->text(),
1516 'href' => $title->getLocalURL( "action=info" )
1517 ];
1518
1519 if ( $title->exists() || $title->inNamespace( NS_CATEGORY ) ) {
1520 $nav_urls['recentchangeslinked'] = [
1521 'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $thispage )->getLocalURL()
1522 ];
1523 }
1524 }
1525
1526 $user = $this->getRelevantUser();
1527 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
1528
1529 // The relevant user should only be set if it exists. However, if it exists but is hidden,
1530 // and the viewer cannot see hidden users, this exposes the fact that the user exists;
1531 // pretend like the user does not exist in such cases, by setting $user to null, which
1532 // is what getRelevantUser returns if there is no user set (though it is documented as
1533 // always returning a User...) See T120883
1534 if ( $user && $user->isRegistered() && $user->isHidden() &&
1535 !$permissionManager->userHasRight( $this->getUser(), 'hideuser' )
1536 ) {
1537 $user = null;
1538 }
1539
1540 if ( $user ) {
1541 $rootUser = $user->getName();
1542
1543 $nav_urls['contributions'] = [
1544 'text' => $this->msg( 'contributions', $rootUser )->text(),
1545 'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser ),
1546 'tooltip-params' => [ $rootUser ],
1547 ];
1548
1549 $nav_urls['log'] = [
1550 'href' => self::makeSpecialUrlSubpage( 'Log', $rootUser )
1551 ];
1552
1553 if ( $permissionManager->userHasRight( $this->getUser(), 'block' ) ) {
1554 $nav_urls['blockip'] = [
1555 'text' => $this->msg( 'blockip', $rootUser )->text(),
1556 'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser )
1557 ];
1558 }
1559
1560 if ( $this->showEmailUser( $user ) ) {
1561 $nav_urls['emailuser'] = [
1562 'text' => $this->msg( 'tool-link-emailuser', $rootUser )->text(),
1563 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser ),
1564 'tooltip-params' => [ $rootUser ],
1565 ];
1566 }
1567
1568 if ( !$user->isAnon() ) {
1569 if ( $this->getUser()->isRegistered() && $this->getConfig()->get( 'EnableSpecialMute' ) ) {
1570 $nav_urls['mute'] = [
1571 'text' => $this->msg( 'mute-preferences' )->text(),
1572 'href' => self::makeSpecialUrlSubpage( 'Mute', $rootUser )
1573 ];
1574 }
1575
1576 $sur = new UserrightsPage;
1577 $sur->setContext( $this->getContext() );
1578 $canChange = $sur->userCanChangeRights( $user );
1579 $nav_urls['userrights'] = [
1580 'text' => $this->msg(
1581 $canChange ? 'tool-link-userrights' : 'tool-link-userrights-readonly',
1582 $rootUser
1583 )->text(),
1584 'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser )
1585 ];
1586 }
1587 }
1588
1589 return $nav_urls;
1590 }
1591
1597 final protected function buildFeedUrls() {
1598 $feeds = [];
1599 $out = $this->getOutput();
1600 if ( $out->isSyndicated() ) {
1601 foreach ( $out->getSyndicationLinks() as $format => $link ) {
1602 $feeds[$format] = [
1603 // Messages: feed-atom, feed-rss
1604 'text' => $this->msg( "feed-$format" )->text(),
1605 'href' => $link
1606 ];
1607 }
1608 }
1609 return $feeds;
1610 }
1611
1637 public function buildSidebar() {
1638 $services = MediaWikiServices::getInstance();
1639 $callback = function ( $old = null, &$ttl = null ) {
1640 $bar = [];
1641 $this->addToSidebar( $bar, 'sidebar' );
1642 $this->getHookRunner()->onSkinBuildSidebar( $this, $bar );
1643 $msgCache = MediaWikiServices::getInstance()->getMessageCache();
1644 if ( $msgCache->isDisabled() ) {
1645 $ttl = WANObjectCache::TTL_UNCACHEABLE; // bug T133069
1646 }
1647
1648 return $bar;
1649 };
1650
1651 $msgCache = $services->getMessageCache();
1652 $wanCache = $services->getMainWANObjectCache();
1653 $config = $this->getConfig();
1654 $languageCode = $this->getLanguage()->getCode();
1655
1656 $sidebar = $config->get( 'EnableSidebarCache' )
1657 ? $wanCache->getWithSetCallback(
1658 $wanCache->makeKey( 'sidebar', $languageCode ),
1659 $config->get( 'SidebarCacheExpiry' ),
1660 $callback,
1661 [
1662 'checkKeys' => [
1663 // Unless there is both no exact $code override nor an i18n definition
1664 // in the software, the only MediaWiki page to check is for $code.
1665 $msgCache->getCheckKey( $languageCode )
1666 ],
1667 'lockTSE' => 30
1668 ]
1669 )
1670 : $callback();
1671
1672 $sidebar['TOOLBOX'] = $this->makeToolbox(
1673 $this->buildNavUrls(),
1674 $this->buildFeedUrls()
1675 );
1676 $sidebar['LANGUAGES'] = $this->getLanguages();
1677 // Apply post-processing to the cached value
1678 $this->getHookRunner()->onSidebarBeforeOutput( $this, $sidebar );
1679
1680 return $sidebar;
1681 }
1682
1692 public function addToSidebar( &$bar, $message ) {
1693 $this->addToSidebarPlain( $bar, $this->msg( $message )->inContentLanguage()->plain() );
1694 }
1695
1703 public function addToSidebarPlain( &$bar, $text ) {
1704 $lines = explode( "\n", $text );
1705
1706 $heading = '';
1707 $config = $this->getConfig();
1708 $messageTitle = $config->get( 'EnableSidebarCache' )
1709 ? Title::newMainPage() : $this->getTitle();
1710 $messageCache = MediaWikiServices::getInstance()->getMessageCache();
1711
1712 foreach ( $lines as $line ) {
1713 if ( strpos( $line, '*' ) !== 0 ) {
1714 continue;
1715 }
1716 $line = rtrim( $line, "\r" ); // for Windows compat
1717
1718 if ( strpos( $line, '**' ) !== 0 ) {
1719 $heading = trim( $line, '* ' );
1720 if ( !array_key_exists( $heading, $bar ) ) {
1721 $bar[$heading] = [];
1722 }
1723 } else {
1724 $line = trim( $line, '* ' );
1725
1726 if ( strpos( $line, '|' ) !== false ) { // sanity check
1727 $line = $messageCache->transform( $line, false, null, $messageTitle );
1728 $line = array_map( 'trim', explode( '|', $line, 2 ) );
1729 if ( count( $line ) !== 2 ) {
1730 // Second sanity check, could be hit by people doing
1731 // funky stuff with parserfuncs... (T35321)
1732 continue;
1733 }
1734
1735 $extraAttribs = [];
1736
1737 $msgLink = $this->msg( $line[0] )->title( $messageTitle )->inContentLanguage();
1738 if ( $msgLink->exists() ) {
1739 $link = $msgLink->text();
1740 if ( $link == '-' ) {
1741 continue;
1742 }
1743 } else {
1744 $link = $line[0];
1745 }
1746 $msgText = $this->msg( $line[1] )->title( $messageTitle );
1747 if ( $msgText->exists() ) {
1748 $text = $msgText->text();
1749 } else {
1750 $text = $line[1];
1751 }
1752
1753 if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) {
1754 $href = $link;
1755
1756 // Parser::getExternalLinkAttribs won't work here because of the Namespace things
1757 if ( $config->get( 'NoFollowLinks' ) &&
1758 !wfMatchesDomainList( $href, $config->get( 'NoFollowDomainExceptions' ) )
1759 ) {
1760 $extraAttribs['rel'] = 'nofollow';
1761 }
1762
1763 if ( $config->get( 'ExternalLinkTarget' ) ) {
1764 $extraAttribs['target'] = $config->get( 'ExternalLinkTarget' );
1765 }
1766 } else {
1767 $title = Title::newFromText( $link );
1768
1769 if ( $title ) {
1770 $title = $title->fixSpecialName();
1771 $href = $title->getLinkURL();
1772 } else {
1773 $href = 'INVALID-TITLE';
1774 }
1775 }
1776
1777 $bar[$heading][] = array_merge( [
1778 'text' => $text,
1779 'href' => $href,
1780 'id' => Sanitizer::escapeIdForAttribute( 'n-' . strtr( $line[1], ' ', '-' ) ),
1781 'active' => false,
1782 ], $extraAttribs );
1783 } else {
1784 continue;
1785 }
1786 }
1787 }
1788
1789 return $bar;
1790 }
1791
1797 public function getNewtalks() {
1798 $newMessagesAlert = '';
1799 $user = $this->getUser();
1800 $services = MediaWikiServices::getInstance();
1801 $linkRenderer = $services->getLinkRenderer();
1802 $userHasNewMessages = $services->getTalkPageNotificationManager()
1803 ->userHasNewMessages( $user );
1804 $timestamp = $services->getTalkPageNotificationManager()
1805 ->getLatestSeenMessageTimestamp( $user );
1806 $newtalks = !$userHasNewMessages ? [] : [
1807 [
1808 // TODO: Deprecate adding wiki and link to array and redesign GetNewMessagesAlert hook
1809 'wiki' => WikiMap::getCurrentWikiId(),
1810 'link' => $user->getTalkPage()->getLocalURL(),
1811 'rev' => $timestamp ? $services->getRevisionLookup()
1812 ->getRevisionByTimestamp( $user->getTalkPage(), $timestamp ) : null
1813 ]
1814 ];
1815 $out = $this->getOutput();
1816
1817 // Allow extensions to disable or modify the new messages alert
1818 if ( !$this->getHookRunner()->onGetNewMessagesAlert(
1819 $newMessagesAlert, $newtalks, $user, $out )
1820 ) {
1821 return '';
1822 }
1823 if ( $newMessagesAlert ) {
1824 return $newMessagesAlert;
1825 }
1826
1827 if ( $newtalks !== [] ) {
1828 $uTalkTitle = $user->getTalkPage();
1829 $lastSeenRev = $newtalks[0]['rev'];
1830 $numAuthors = 0;
1831 if ( $lastSeenRev !== null ) {
1832 $plural = true; // Default if we have a last seen revision: if unknown, use plural
1833 $revStore = $services->getRevisionStore();
1834 $latestRev = $revStore->getRevisionByTitle(
1835 $uTalkTitle,
1836 0,
1837 RevisionLookup::READ_NORMAL
1838 );
1839 if ( $latestRev !== null ) {
1840 // Singular if only 1 unseen revision, plural if several unseen revisions.
1841 $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
1842 $numAuthors = $revStore->countAuthorsBetween(
1843 $uTalkTitle->getArticleID(),
1844 $lastSeenRev,
1845 $latestRev,
1846 null,
1847 10,
1848 'include_new'
1849 );
1850 }
1851 } else {
1852 // Singular if no revision -> diff link will show latest change only in any case
1853 $plural = false;
1854 }
1855 $plural = $plural ? 999 : 1;
1856 // 999 signifies "more than one revision". We don't know how many, and even if we did,
1857 // the number of revisions or authors is not necessarily the same as the number of
1858 // "messages".
1859 $newMessagesLink = $linkRenderer->makeKnownLink(
1860 $uTalkTitle,
1861 $this->msg( 'newmessageslinkplural' )->params( $plural )->text(),
1862 [],
1863 $uTalkTitle->isRedirect() ? [ 'redirect' => 'no' ] : []
1864 );
1865
1866 $newMessagesDiffLink = $linkRenderer->makeKnownLink(
1867 $uTalkTitle,
1868 $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->text(),
1869 [],
1870 $lastSeenRev !== null
1871 ? [ 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' ]
1872 : [ 'diff' => 'cur' ]
1873 );
1874
1875 if ( $numAuthors >= 1 && $numAuthors <= 10 ) {
1876 $newMessagesAlert = $this->msg(
1877 'youhavenewmessagesfromusers',
1878 $newMessagesLink,
1879 $newMessagesDiffLink
1880 )->numParams( $numAuthors, $plural );
1881 } else {
1882 // $numAuthors === 11 signifies "11 or more" ("more than 10")
1883 $newMessagesAlert = $this->msg(
1884 $numAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
1885 $newMessagesLink,
1886 $newMessagesDiffLink
1887 )->numParams( $plural );
1888 }
1889 $newMessagesAlert = $newMessagesAlert->text();
1890 // Disable CDN cache
1891 $out->setCdnMaxage( 0 );
1892 }
1893
1894 return $newMessagesAlert;
1895 }
1896
1904 private function getCachedNotice( $name ) {
1905 $config = $this->getConfig();
1906
1907 if ( $name === 'default' ) {
1908 // special case
1909 $notice = $config->get( 'SiteNotice' );
1910 if ( empty( $notice ) ) {
1911 return false;
1912 }
1913 } else {
1914 $msg = $this->msg( $name )->inContentLanguage();
1915 if ( $msg->isBlank() ) {
1916 return '';
1917 } elseif ( $msg->isDisabled() ) {
1918 return false;
1919 }
1920 $notice = $msg->plain();
1921 }
1922
1923 $services = MediaWikiServices::getInstance();
1924 $cache = $services->getMainWANObjectCache();
1925 $parsed = $cache->getWithSetCallback(
1926 // Use the extra hash appender to let eg SSL variants separately cache
1927 // Key is verified with md5 hash of unparsed wikitext
1928 $cache->makeKey( $name, $config->get( 'RenderHashAppend' ), md5( $notice ) ),
1929 // TTL in seconds
1930 600,
1931 function () use ( $notice ) {
1932 return $this->getOutput()->parseAsInterface( $notice );
1933 }
1934 );
1935
1936 $contLang = $services->getContentLanguage();
1937 return Html::rawElement(
1938 'div',
1939 [
1940 'id' => 'localNotice',
1941 'lang' => $contLang->getHtmlCode(),
1942 'dir' => $contLang->getDir()
1943 ],
1944 $parsed
1945 );
1946 }
1947
1953 public function getSiteNotice() {
1954 $siteNotice = '';
1955
1956 if ( $this->getHookRunner()->onSiteNoticeBefore( $siteNotice, $this ) ) {
1957 if ( $this->getUser()->isLoggedIn() ) {
1958 $siteNotice = $this->getCachedNotice( 'sitenotice' );
1959 } else {
1960 $anonNotice = $this->getCachedNotice( 'anonnotice' );
1961 if ( $anonNotice === false ) {
1962 $siteNotice = $this->getCachedNotice( 'sitenotice' );
1963 } else {
1964 $siteNotice = $anonNotice;
1965 }
1966 }
1967 if ( $siteNotice === false ) {
1968 $siteNotice = $this->getCachedNotice( 'default' ) ?: '';
1969 }
1970 }
1971
1972 $this->getHookRunner()->onSiteNoticeAfter( $siteNotice, $this );
1973 return $siteNotice;
1974 }
1975
1989 public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
1990 // HTML generated here should probably have userlangattributes
1991 // added to it for LTR text on RTL pages
1992
1993 $attribs = [];
1994 if ( $tooltip !== null ) {
1995 $attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
1996 ->inLanguage( $lang )->text();
1997 }
1998
1999 $links = [
2000 'editsection' => [
2001 'text' => $this->msg( 'editsection' )->inLanguage( $lang )->text(),
2002 'targetTitle' => $nt,
2003 'attribs' => $attribs,
2004 'query' => [ 'action' => 'edit', 'section' => $section ]
2005 ]
2006 ];
2007
2008 $this->getHookRunner()->onSkinEditSectionLinks( $this, $nt, $section, $tooltip, $links, $lang );
2009
2010 $result = '<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>';
2011
2012 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2013 $linksHtml = [];
2014 foreach ( $links as $k => $linkDetails ) {
2015 $linksHtml[] = $linkRenderer->makeKnownLink(
2016 $linkDetails['targetTitle'],
2017 $linkDetails['text'],
2018 $linkDetails['attribs'],
2019 $linkDetails['query']
2020 );
2021 }
2022
2023 $result .= implode(
2024 '<span class="mw-editsection-divider">'
2025 . $this->msg( 'pipe-separator' )->inLanguage( $lang )->escaped()
2026 . '</span>',
2027 $linksHtml
2028 );
2029
2030 $result .= '<span class="mw-editsection-bracket">]</span></span>';
2031 return $result;
2032 }
2033
2043 public function makeToolbox( $navUrls, $feedUrls ) {
2044 $toolbox = [];
2045 if ( $navUrls['whatlinkshere'] ?? null ) {
2046 $toolbox['whatlinkshere'] = $navUrls['whatlinkshere'];
2047 $toolbox['whatlinkshere']['id'] = 't-whatlinkshere';
2048 }
2049 if ( $navUrls['recentchangeslinked'] ?? null ) {
2050 $toolbox['recentchangeslinked'] = $navUrls['recentchangeslinked'];
2051 $toolbox['recentchangeslinked']['msg'] = 'recentchangeslinked-toolbox';
2052 $toolbox['recentchangeslinked']['id'] = 't-recentchangeslinked';
2053 $toolbox['recentchangeslinked']['rel'] = 'nofollow';
2054 }
2055 if ( $feedUrls ) {
2056 $toolbox['feeds']['id'] = 'feedlinks';
2057 $toolbox['feeds']['links'] = [];
2058 foreach ( $feedUrls as $key => $feed ) {
2059 $toolbox['feeds']['links'][$key] = $feed;
2060 $toolbox['feeds']['links'][$key]['id'] = "feed-$key";
2061 $toolbox['feeds']['links'][$key]['rel'] = 'alternate';
2062 $toolbox['feeds']['links'][$key]['type'] = "application/{$key}+xml";
2063 $toolbox['feeds']['links'][$key]['class'] = 'feedlink';
2064 }
2065 }
2066 foreach ( [ 'contributions', 'log', 'blockip', 'emailuser', 'mute',
2067 'userrights', 'upload', 'specialpages' ] as $special
2068 ) {
2069 if ( $navUrls[$special] ?? null ) {
2070 $toolbox[$special] = $navUrls[$special];
2071 $toolbox[$special]['id'] = "t-$special";
2072 }
2073 }
2074 if ( $navUrls['print'] ?? null ) {
2075 $toolbox['print'] = $navUrls['print'];
2076 $toolbox['print']['id'] = 't-print';
2077 $toolbox['print']['rel'] = 'alternate';
2078 $toolbox['print']['msg'] = 'printableversion';
2079 }
2080 if ( $navUrls['permalink'] ?? null ) {
2081 $toolbox['permalink'] = $navUrls['permalink'];
2082 $toolbox['permalink']['id'] = 't-permalink';
2083 }
2084 if ( $navUrls['info'] ?? null ) {
2085 $toolbox['info'] = $navUrls['info'];
2086 $toolbox['info']['id'] = 't-info';
2087 }
2088
2089 return $toolbox;
2090 }
2091
2108 final public function getIndicatorsHTML( $indicators ) {
2109 $out = "<div class=\"mw-indicators mw-body-content\">\n";
2110 foreach ( $this->getIndicatorsData( $indicators ) as $indicatorData ) {
2111 $out .= Html::rawElement(
2112 'div',
2113 [
2114 'id' => $indicatorData['id'],
2115 'class' => $indicatorData['class']
2116 ],
2117 $indicatorData['html']
2118 ) . "\n";
2119 }
2120 $out .= "</div>\n";
2121 return $out;
2122 }
2123
2130 protected function getIndicatorsData( $indicators ) {
2131 $indicatorData = [];
2132 foreach ( $indicators as $id => $content ) {
2133 $indicatorData[] = [
2134 'id' => Sanitizer::escapeIdForAttribute( "mw-indicator-$id" ),
2135 'class' => 'mw-indicator',
2136 'html' => $content,
2137 ];
2138 }
2139 return $indicatorData;
2140 }
2141
2154 final public function getPersonalToolsForMakeListItem( $urls ) {
2155 $personal_tools = [];
2156 foreach ( $urls as $key => $plink ) {
2157 # The class on a personal_urls item is meant to go on the <a> instead
2158 # of the <li> so we have to use a single item "links" array instead
2159 # of using most of the personal_url's keys directly.
2160 $ptool = [
2161 'links' => [
2162 [ 'single-id' => "pt-$key" ],
2163 ],
2164 'id' => "pt-$key",
2165 ];
2166 if ( isset( $plink['active'] ) ) {
2167 $ptool['active'] = $plink['active'];
2168 }
2169 foreach ( [
2170 'href',
2171 'class',
2172 'text',
2173 'dir',
2174 'data',
2175 'exists',
2176 'data-mw'
2177 ] as $k ) {
2178 if ( isset( $plink[$k] ) ) {
2179 $ptool['links'][0][$k] = $plink[$k];
2180 }
2181 }
2182 $personal_tools[$key] = $ptool;
2183 }
2184 return $personal_tools;
2185 }
2186
2240 final public function makeLink( $key, $item, $options = [] ) {
2241 $text = $item['text'] ?? $this->msg( $item['msg'] ?? $key )->text();
2242
2243 $html = htmlspecialchars( $text );
2244
2245 if ( isset( $options['text-wrapper'] ) ) {
2246 $wrapper = $options['text-wrapper'];
2247 if ( isset( $wrapper['tag'] ) ) {
2248 $wrapper = [ $wrapper ];
2249 }
2250 while ( count( $wrapper ) > 0 ) {
2251 $element = array_pop( $wrapper );
2252 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
2253 $html = Html::rawElement( $element['tag'], $element['attributes'] ?? null, $html );
2254 }
2255 }
2256
2257 if ( isset( $item['href'] ) || isset( $options['link-fallback'] ) ) {
2258 $attrs = $item;
2259 foreach ( [ 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary',
2260 'tooltip-params', 'exists' ] as $k ) {
2261 unset( $attrs[$k] );
2262 }
2263
2264 if ( isset( $attrs['data'] ) ) {
2265 foreach ( $attrs['data'] as $key => $value ) {
2266 $attrs[ 'data-' . $key ] = $value;
2267 }
2268 unset( $attrs[ 'data' ] );
2269 }
2270
2271 if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
2272 $item['single-id'] = $item['id'];
2273 }
2274
2275 $tooltipParams = [];
2276 if ( isset( $item['tooltip-params'] ) ) {
2277 $tooltipParams = $item['tooltip-params'];
2278 }
2279
2280 if ( isset( $item['single-id'] ) ) {
2281 $tooltipOption = isset( $item['exists'] ) && $item['exists'] === false ? 'nonexisting' : null;
2282
2283 if ( isset( $item['tooltiponly'] ) && $item['tooltiponly'] ) {
2284 $title = Linker::titleAttrib( $item['single-id'], $tooltipOption, $tooltipParams );
2285 if ( $title !== false ) {
2286 $attrs['title'] = $title;
2287 }
2288 } else {
2290 $item['single-id'],
2291 $tooltipParams,
2292 $tooltipOption
2293 );
2294 if ( isset( $tip['title'] ) && $tip['title'] !== false ) {
2295 $attrs['title'] = $tip['title'];
2296 }
2297 if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) {
2298 $attrs['accesskey'] = $tip['accesskey'];
2299 }
2300 }
2301 }
2302 if ( isset( $options['link-class'] ) ) {
2303 if ( isset( $attrs['class'] ) ) {
2304 $attrs['class'] .= " {$options['link-class']}";
2305 } else {
2306 $attrs['class'] = $options['link-class'];
2307 }
2308 }
2309 $html = Html::rawElement( isset( $attrs['href'] )
2310 ? 'a'
2311 : $options['link-fallback'], $attrs, $html );
2312 }
2313
2314 return $html;
2315 }
2316
2349 final public function makeListItem( $key, $item, $options = [] ) {
2350 // In case this is still set from SkinTemplate, we don't want it to appear in
2351 // the HTML output (normally removed in SkinTemplate::buildContentActionUrls())
2352 unset( $item['redundant'] );
2353
2354 if ( isset( $item['links'] ) ) {
2355 $links = [];
2356 foreach ( $item['links'] as $linkKey => $link ) {
2357 $links[] = $this->makeLink( $linkKey, $link, $options );
2358 }
2359 $html = implode( ' ', $links );
2360 } else {
2361 $link = $item;
2362 // These keys are used by makeListItem and shouldn't be passed on to the link
2363 foreach ( [ 'id', 'class', 'active', 'tag', 'itemtitle' ] as $k ) {
2364 unset( $link[$k] );
2365 }
2366 if ( isset( $item['id'] ) && !isset( $item['single-id'] ) ) {
2367 // The id goes on the <li> not on the <a> for single links
2368 // but makeSidebarLink still needs to know what id to use when
2369 // generating tooltips and accesskeys.
2370 $link['single-id'] = $item['id'];
2371 }
2372 if ( isset( $link['link-class'] ) ) {
2373 // link-class should be set on the <a> itself,
2374 // so pass it in as 'class'
2375 $link['class'] = $link['link-class'];
2376 unset( $link['link-class'] );
2377 }
2378 $html = $this->makeLink( $key, $link, $options );
2379 }
2380
2381 $attrs = [];
2382 foreach ( [ 'id', 'class' ] as $attr ) {
2383 if ( isset( $item[$attr] ) ) {
2384 $attrs[$attr] = $item[$attr];
2385 }
2386 }
2387 if ( isset( $item['active'] ) && $item['active'] ) {
2388 if ( !isset( $attrs['class'] ) ) {
2389 $attrs['class'] = '';
2390 }
2391 $attrs['class'] .= ' active';
2392 $attrs['class'] = trim( $attrs['class'] );
2393 }
2394 if ( isset( $item['itemtitle'] ) ) {
2395 $attrs['title'] = $item['itemtitle'];
2396 }
2397 return Html::rawElement( $options['tag'] ?? 'li', $attrs, $html );
2398 }
2399
2406 final public function makeSearchInput( $attrs = [] ) {
2407 $realAttrs = [
2408 'type' => 'search',
2409 'name' => 'search',
2410 'placeholder' => $this->msg( 'searchsuggest-search' )->text(),
2411 ];
2412 $realAttrs = array_merge( $realAttrs, Linker::tooltipAndAccesskeyAttribs( 'search' ), $attrs );
2413 return Html::element( 'input', $realAttrs );
2414 }
2415
2423 final public function makeSearchButton( $mode, $attrs = [] ) {
2424 switch ( $mode ) {
2425 case 'go':
2426 case 'fulltext':
2427 $realAttrs = [
2428 'type' => 'submit',
2429 'name' => $mode,
2430 'value' => $this->msg( $mode == 'go' ? 'searcharticle' : 'searchbutton' )->text(),
2431 ];
2432 $realAttrs = array_merge(
2433 $realAttrs,
2434 Linker::tooltipAndAccesskeyAttribs( "search-$mode" ),
2435 $attrs
2436 );
2437 return Html::element( 'input', $realAttrs );
2438 case 'image':
2439 $buttonAttrs = [
2440 'type' => 'submit',
2441 'name' => 'button',
2442 ];
2443 $buttonAttrs = array_merge(
2444 $buttonAttrs,
2445 Linker::tooltipAndAccesskeyAttribs( 'search-fulltext' ),
2446 $attrs
2447 );
2448 unset( $buttonAttrs['src'] );
2449 unset( $buttonAttrs['alt'] );
2450 unset( $buttonAttrs['width'] );
2451 unset( $buttonAttrs['height'] );
2452 $imgAttrs = [
2453 'src' => $attrs['src'],
2454 'alt' => $attrs['alt'] ?? $this->msg( 'searchbutton' )->text(),
2455 'width' => $attrs['width'] ?? null,
2456 'height' => $attrs['height'] ?? null,
2457 ];
2458 return Html::rawElement( 'button', $buttonAttrs, Html::element( 'img', $imgAttrs ) );
2459 default:
2460 throw new MWException( 'Unknown mode passed to BaseTemplate::makeSearchButton' );
2461 }
2462 }
2463
2474 public function getAfterPortlet( string $name ) : string {
2475 $html = '';
2476
2477 $this->getHookRunner()->onSkinAfterPortlet( $this, $name, $html );
2478
2479 return $html;
2480 }
2481
2493 protected function getFooterLinks(): array {
2494 $out = $this->getOutput();
2495 $title = $out->getTitle();
2496 $titleExists = $title->exists();
2497 $config = $this->getConfig();
2498 $maxCredits = $config->get( 'MaxCredits' );
2499 $showCreditsIfMax = $config->get( 'ShowCreditsIfMax' );
2500 $useCredits = $titleExists
2501 && $out->isArticle()
2502 && $out->isRevisionCurrent()
2503 && $maxCredits !== 0;
2504
2506 if ( $titleExists ) {
2507 $article = Article::newFromWikiPage( $this->getWikiPage(), $this );
2508 $action = Action::factory( 'credits', $article, $this );
2509 }
2510
2511 '@phan-var CreditsAction $action';
2512 $data = [
2513 'info' => [
2514 'lastmod' => !$useCredits ? $this->lastModified() : null,
2515 'numberofwatchingusers' => null,
2516 'credits' => $useCredits ?
2517 $action->getCredits( $maxCredits, $showCreditsIfMax ) : null,
2518 'copyright' => $titleExists &&
2519 $out->showsCopyright() ? $this->getCopyright() : null,
2520 ],
2521 'places' => $this->getSiteFooterLinks(),
2522 ];
2523 foreach ( $data as $key => $existingItems ) {
2524 $newItems = [];
2525 $this->getHookRunner()->onSkinAddFooterLinks( $this, $key, $newItems );
2526 $data[$key] = $existingItems + $newItems;
2527 }
2528 return $data;
2529 }
2530
2531}
getPermissionManager()
getUser()
$wgFallbackSkin
Fallback skin used when the skin defined by $wgDefaultSkin can't be found.
$wgSkipSkins
Specify the names of skins that should not be presented in the list of available skins in user prefer...
$wgDefaultSkin
Default skin, for new users and anonymous visitors.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfExpandIRI( $url)
Take a URL, make sure it's expanded to fully qualified, and replace any encoded non-ASCII Unicode cha...
wfUrlProtocols( $includeProtocolRelative=true)
Returns a regular expression of url protocols.
wfMatchesDomainList( $url, $domains)
Check whether a given URL has a domain that occurs in a given set of domains.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
$fallback
getContext()
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition Language.php:41
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
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:2120
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition Linker.php:856
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2304
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
This is one of the Core classes and should be read at least once by any new developers.
static getAvailableLogos( $conf)
Return an array of all available logos that a skin may use.
static makeInlineScript( $script, $nonce=null)
Make an HTML script that runs given JS code after startup and base modules.
static makeConfigSetScript(array $configuration)
Return JS code which will set the MediaWiki configuration array to the given value.
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:41
aboutLink()
Gets the link to the wiki's about page.
Definition Skin.php:1119
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
Definition Skin.php:333
getFooterLinks()
Get template representation of the footer containing site footer links as well as standard footer lin...
Definition Skin.php:2493
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition Skin.php:1261
afterContentHook()
This runs a hook to allow extensions placing their stuff after content and article metadata (e....
Definition Skin.php:639
buildFeedUrls()
Build data structure representing syndication links.
Definition Skin.php:1597
string $stylename
Stylesheets set to use.
Definition Skin.php:60
getNewtalks()
Gets new talk page messages for the current user and returns an appropriate alert message (or an empt...
Definition Skin.php:1797
mainPageLink()
Gets the link to the wiki's main page.
Definition Skin.php:1022
static getAllowedSkins()
Fetch the list of user-selectable skins in regards to $wgSkipSkins.
Definition Skin.php:78
doEditSectionLink(Title $nt, $section, $tooltip, Language $lang)
Create a section edit link.
Definition Skin.php:1989
getSkinStylePath( $name)
Return a fully resolved style path URL to images or styles stored in the current skin's folder.
Definition Skin.php:1177
getCachedNotice( $name)
Get a cached notice.
Definition Skin.php:1904
makeLink( $key, $item, $options=[])
Makes a link, usually used by makeListItem to generate a link for an item in a list used in navigatio...
Definition Skin.php:2240
static makeMainPageUrl( $urlaction='')
Definition Skin.php:1192
static makeKnownUrlDetails( $name, $urlaction='')
Make URL details where the article exists (or at least it's convenient to think so)
Definition Skin.php:1307
generateDebugHTML()
Generate debug data HTML for displaying at the bottom of the main content area.
Definition Skin.php:667
getCopyrightIcon()
Definition Skin.php:888
privacyLink()
Gets the link to the wiki's privacy policy page.
Definition Skin.php:1111
footerLinkTitle( $desc, $page)
Definition Skin.php:1056
getSiteNotice()
Get the site notice.
Definition Skin.php:1953
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:489
static getSkinNames()
Fetch the set of available skins.
Definition Skin.php:66
static normalizeKey( $key)
Normalize a skin preference value to a form that can be loaded.
Definition Skin.php:103
getSkinName()
Definition Skin.php:168
getSiteFooterLinks()
Gets the link to the wiki's privacy policy, about page, and disclaimer page.
Definition Skin.php:1076
getUndeleteLink()
Definition Skin.php:719
getPoweredBy()
Gets the powered by MediaWiki icon.
Definition Skin.php:918
outputPage()
Outputs the HTML generated by other functions.
getCategoryLinks()
Definition Skin.php:510
mapInterwikiToLanguage( $code)
Allows correcting the language of interlanguage links which, mostly due to legacy reasons,...
Definition Skin.php:1340
static makeUrlDetails( $name, $urlaction='')
these return an array with the 'href' and boolean 'exists'
Definition Skin.php:1291
getLogo()
URL to the default square logo (1x key) Please use ResourceLoaderSkinModule::getAvailableLogos.
Definition Skin.php:503
$mRelevantTitle
Definition Skin.php:53
addToSidebar(&$bar, $message)
Add content from a sidebar system message Currently only used for MediaWiki:Sidebar (but may be used ...
Definition Skin.php:1692
static makeSpecialUrlSubpage( $name, $subpage, $urlaction='')
Definition Skin.php:1225
array $options
Skin options passed into constructor.
Definition Skin.php:52
__construct( $options=null)
Definition Skin.php:152
getRelevantUser()
Return the "relevant" user.
Definition Skin.php:377
getIndicatorsData( $indicators)
Return an array of indicator data.
Definition Skin.php:2130
setRelevantTitle( $t)
Set the "relevant" title.
Definition Skin.php:342
logoText( $align='')
Definition Skin.php:972
$mRelevantUser
Definition Skin.php:54
drawCategoryBrowser( $tree)
Render the array as a series of links.
Definition Skin.php:582
getIndicatorsHTML( $indicators)
Get the suggested HTML for page status indicators: icons (or short text snippets) usually displayed i...
Definition Skin.php:2108
static checkTitle(&$title, $name)
make sure we have some title to operate on
Definition Skin.php:1323
static makeUrl( $name, $urlaction='')
Definition Skin.php:1248
getPageClasses( $title)
TODO: document.
Definition Skin.php:451
getSearchLink()
Definition Skin.php:836
editUrlOptions()
Return URL options for the 'edit page' link.
Definition Skin.php:1138
showEmailUser( $id)
Definition Skin.php:1153
buildNavUrls()
Build array of common navigation links.
Definition Skin.php:1459
static makeNSUrl( $name, $urlaction='', $namespace=NS_MAIN)
this can be passed the NS number as defined in Language.php
Definition Skin.php:1277
buildSidebar()
Build an array that represents the sidebar(s), the navigation bar among them.
Definition Skin.php:1637
getCopyright( $type='detect')
Definition Skin.php:845
printSource()
Text with the permalink to the source page, usually shown on the footer of a printed page.
Definition Skin.php:700
getLanguages()
Generates array of language links for the current page.
Definition Skin.php:1353
string null $skinname
Definition Skin.php:47
setRelevantUser( $u)
Set the "relevant" user.
Definition Skin.php:365
makeSearchInput( $attrs=[])
Definition Skin.php:2406
setupSkinUserCss(OutputPage $out)
Hook point for adding style modules to OutputPage.
Definition Skin.php:442
makeSearchButton( $mode, $attrs=[])
Definition Skin.php:2423
makeFooterIcon( $icon, $withImage='withImage')
Renders a $wgFooterIcons icon according to the method's arguments.
Definition Skin.php:995
getRevisionId()
Get the current revision ID.
Definition Skin.php:323
getAfterPortlet(string $name)
Allows extensions to hook into known portlets and add stuff to them.
Definition Skin.php:2474
preloadExistence()
Preload the existence of three commonly-requested pages in a single query.
Definition Skin.php:275
bottomScripts()
This gets called shortly before the "</body>" tag.
Definition Skin.php:677
disclaimerLink()
Gets the link to the wiki's general disclaimers page.
Definition Skin.php:1127
getDefaultModules()
Defines the ResourceLoader modules that should be added to the skin It is recommended that skins wish...
Definition Skin.php:191
static makeSpecialUrl( $name, $urlaction='', $proto=null)
Make a URL for a Special Page using the given query and protocol.
Definition Skin.php:1210
lastModified()
Get the timestamp of the latest revision, formatted in user language.
Definition Skin.php:941
static makeVariablesScript( $data, $nonce=null)
Definition Skin.php:412
initPage(OutputPage $out)
Stable to override.
Definition Skin.php:176
makeListItem( $key, $item, $options=[])
Generates a list item for a navigation, portlet, portal, sidebar... list.
Definition Skin.php:2349
static getDynamicStylesheetQuery()
Get the query to generate a dynamic stylesheet.
Definition Skin.php:428
subPageSubtitle( $out=null)
Definition Skin.php:779
addToSidebarPlain(&$bar, $text)
Add content from plain text.
Definition Skin.php:1703
footerLink( $desc, $page)
Returns an HTML link for use in the footer.
Definition Skin.php:1038
static makeI18nUrl( $name, $urlaction='')
Definition Skin.php:1236
getPersonalToolsForMakeListItem( $urls)
Create an array of personal tools items from the data in the quicktemplate stored by SkinTemplate.
Definition Skin.php:2154
getRelevantTitle()
Return the "relevant" title.
Definition Skin.php:356
getCategories()
Definition Skin.php:606
makeToolbox( $navUrls, $feedUrls)
Create an array of common toolbox items from the data in the quicktemplate stored by SkinTemplate.
Definition Skin.php:2043
static validateTarget( $target, User $sender)
Validate target User.
setContext( $context)
Sets the context this SpecialPage is executed in.
static getSafeTitleFor( $name, $subpage=false)
Get a localised Title object for a page name with a possibly unvalidated subpage.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:951
Special page to allow managing user group membership.
const NS_USER
Definition Defines.php:72
const NS_MAIN
Definition Defines.php:70
const NS_CATEGORY
Definition Defines.php:84
Service for looking up page revisions.
$line
Definition mcc.php:119
$cache
Definition mcc.php:33
$content
Definition router.php:76
return true
Definition router.php:92
if(!isset( $args[0])) $lang
if(!file_exists( $CREDITS)) $lines